blob: bbcd4db3fa72fe8644c501a358655723dc84e1a5 [file] [log] [blame]
The Android Open Source Project88b60792009-03-03 19:28:42 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import java.util.regex.Pattern;
18import java.util.regex.Matcher;
19import java.util.ArrayList;
20
21/**
22 * Class that represents what you see in an link or see tag. This is
23 * factored out of SeeTagInfo so it can be used elsewhere (like AttrTagInfo).
24 */
25public class LinkReference {
26
27 /** The original text. */
28 public String text;
29
30 /** The kind of this tag, if we have a new suggestion after parsing. */
31 public String kind;
32
33 /** The user visible text. */
34 public String label;
35
36 /** The link. */
37 public String href;
38
39 /** The {@link PackageInfo} if any. */
40 public PackageInfo packageInfo;
41
42 /** The {@link ClassInfo} if any. */
43 public ClassInfo classInfo;
44
45 /** The {@link MemberInfo} if any. */
46 public MemberInfo memberInfo;
47
48 /** The name of the referenced member PackageInfo} if any. */
49 public String referencedMemberName;
50
51 /** Set to true if everything is a-ok */
52 public boolean good;
53
54 /**
55 * regex pattern to use when matching explicit "<a href" reference text
56 */
57 private static final Pattern HREF_PATTERN
58 = Pattern.compile("^<a href=\"([^\"]*)\">([^<]*)</a>[ \n\r\t]*$",
59 Pattern.CASE_INSENSITIVE);
60
61 /**
62 * Parse and resolve a link string.
63 *
64 * @param text the original text
65 * @param base the class or whatever that this link is on
66 * @param pos the original position in the source document
67 * @return a new link reference. It always returns something. If there was an
68 * error, it logs it and fills in href and label with error text.
69 */
70 public static LinkReference parse(String text, ContainerInfo base, SourcePositionInfo pos,
71 boolean printOnErrors) {
72 LinkReference result = new LinkReference();
73 result.text = text;
74
75 int index;
76 int len = text.length();
77 int pairs = 0;
78 int pound = -1;
79 // split the string
80 done: {
81 for (index=0; index<len; index++) {
82 char c = text.charAt(index);
83 switch (c)
84 {
85 case '(':
86 pairs++;
87 break;
88 case '[':
89 pairs++;
90 break;
91 case ')':
92 pairs--;
93 break;
94 case ']':
95 pairs--;
96 break;
97 case ' ':
98 case '\t':
99 case '\r':
100 case '\n':
101 if (pairs == 0) {
102 break done;
103 }
104 break;
105 case '#':
106 if (pound < 0) {
107 pound = index;
108 }
109 break;
110 }
111 }
112 }
113 if (index == len && pairs != 0) {
114 Errors.error(Errors.UNRESOLVED_LINK, pos,
115 "unable to parse link/see tag: " + text.trim());
116 return result;
117 }
118
119 int linkend = index;
120
121 for (; index<len; index++) {
122 char c = text.charAt(index);
123 if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) {
124 break;
125 }
126 }
127
128 result.label = text.substring(index);
129
130 String ref;
131 String mem;
132 if (pound == 0) {
133 ref = null;
134 mem = text.substring(1, linkend);
135 }
136 else if (pound > 0) {
137 ref = text.substring(0, pound);
138 mem = text.substring(pound+1, linkend);
139 }
140 else {
141 ref = text.substring(0, linkend);
142 mem = null;
143 }
144
145 // parse parameters, if any
146 String[] params = null;
147 String[] paramDimensions = null;
148 if (mem != null) {
149 index = mem.indexOf('(');
150 if (index > 0) {
151 ArrayList<String> paramList = new ArrayList<String>();
152 ArrayList<String> paramDimensionList = new ArrayList<String>();
153 len = mem.length();
154 int start = index+1;
155 final int START = 0;
156 final int TYPE = 1;
157 final int NAME = 2;
158 int dimension = 0;
159 int arraypair = 0;
160 int state = START;
161 int typestart = 0;
162 int typeend = -1;
163 for (int i=start; i<len; i++) {
164 char c = mem.charAt(i);
165 switch (state)
166 {
167 case START:
168 if (c!=' ' && c!='\t' && c!='\r' && c!='\n') {
169 state = TYPE;
170 typestart = i;
171 }
172 break;
173 case TYPE:
174 if (c == '[') {
175 if (typeend < 0) {
176 typeend = i;
177 }
178 dimension++;
179 arraypair++;
180 }
181 else if (c == ']') {
182 arraypair--;
183 }
184 else if (c==' ' || c=='\t' || c=='\r' || c=='\n') {
185 if (typeend < 0) {
186 typeend = i;
187 }
188 }
189 else {
190 if (typeend >= 0 || c == ')' || c == ',') {
191 if (typeend < 0) {
192 typeend = i;
193 }
194 String s = mem.substring(typestart, typeend);
195 paramList.add(s);
196 s = "";
197 for (int j=0; j<dimension; j++) {
198 s += "[]";
199 }
200 paramDimensionList.add(s);
201 state = START;
202 typeend = -1;
203 dimension = 0;
204 if (c == ',' || c == ')') {
205 state = START;
206 } else {
207 state = NAME;
208 }
209 }
210 }
211 break;
212 case NAME:
213 if (c == ',' || c == ')') {
214 state = START;
215 }
216 break;
217 }
218
219 }
220 params = paramList.toArray(new String[paramList.size()]);
221 paramDimensions = paramDimensionList.toArray(new String[paramList.size()]);
222 mem = mem.substring(0, index);
223 }
224 }
225
226 ClassInfo cl = null;
227 if (base instanceof ClassInfo) {
228 cl = (ClassInfo)base;
229 }
230
231 if (ref == null) {
232 // no class or package was provided, assume it's this class
233 if (cl != null) {
234 result.classInfo = cl;
235 }
236 } else {
237 // they provided something, maybe it's a class or a package
238 if (cl != null) {
239 result.classInfo = cl.extendedFindClass(ref);
240 if (result.classInfo == null) {
241 result.classInfo = cl.findClass(ref);
242 }
243 if (result.classInfo == null) {
244 result.classInfo = cl.findInnerClass(ref);
245 }
246 }
247 if (result.classInfo == null) {
248 result.classInfo = Converter.obtainClass(ref);
249 }
250 if (result.classInfo == null) {
251 result.packageInfo = Converter.obtainPackage(ref);
252 }
253 }
254
255 if (result.classInfo != null && mem != null) {
256 // it's either a field or a method, prefer a field
257 if (params == null) {
258 FieldInfo field = result.classInfo.findField(mem);
259 // findField looks in containing classes, so it might actually
260 // be somewhere else; link to where it really is, not what they
261 // typed.
262 if (field != null) {
263 result.classInfo = field.containingClass();
264 result.memberInfo = field;
265 }
266 }
267 if (result.memberInfo == null) {
268 MethodInfo method = result.classInfo.findMethod(mem, params, paramDimensions);
269 if (method != null) {
270 result.classInfo = method.containingClass();
271 result.memberInfo = method;
272 }
273 }
274 }
275
276 result.referencedMemberName = mem;
277 if (params != null) {
278 result.referencedMemberName = result.referencedMemberName + '(';
279 len = params.length;
280 if (len > 0) {
281 len--;
282 for (int i=0; i<len; i++) {
283 result.referencedMemberName = result.referencedMemberName + params[i]
284 + paramDimensions[i] + ", ";
285 }
286 result.referencedMemberName = result.referencedMemberName + params[len]
287 + paramDimensions[len];
288 }
289 result.referencedMemberName = result.referencedMemberName + ")";
290 }
291
292 // debugging spew
293 if (false) {
294 result.label = result.label + "/" + ref + "/" + mem + '/';
295 if (params != null) {
296 for (int i=0; i<params.length; i++) {
297 result.label += params[i] + "|";
298 }
299 }
300
301 FieldInfo f = (result.memberInfo instanceof FieldInfo)
302 ? (FieldInfo)result.memberInfo
303 : null;
304 MethodInfo m = (result.memberInfo instanceof MethodInfo)
305 ? (MethodInfo)result.memberInfo
306 : null;
307 result.label = result.label
308 + "/package=" + (result.packageInfo!=null?result.packageInfo.name():"")
309 + "/class=" + (result.classInfo!=null?result.classInfo.qualifiedName():"")
310 + "/field=" + (f!=null?f.name():"")
311 + "/method=" + (m!=null?m.name():"");
312
313 }
314
315 MethodInfo method = null;
316 boolean skipHref = false;
317
318 if (result.memberInfo != null && result.memberInfo.isExecutable()) {
319 method = (MethodInfo)result.memberInfo;
320 }
321
322 if (text.startsWith("\"")) {
323 // literal quoted reference (e.g., a book title)
324 result.label = text.substring(1);
325 skipHref = true;
326 if (!result.label.endsWith("\"")) {
327 Errors.error(Errors.UNRESOLVED_LINK, pos,
328 "unbalanced quoted link/see tag: " + text.trim());
329 result.makeError();
330 return result;
331 }
332 result.label = result.label.substring(0, result.label.length() - 1);
333 result.kind = "@seeJustLabel";
334 }
335 else if (text.startsWith("<")) {
336 // explicit "<a href" form
337 Matcher matcher = HREF_PATTERN.matcher(text);
338 if (! matcher.matches()) {
339 Errors.error(Errors.UNRESOLVED_LINK, pos,
340 "invalid <a> link/see tag: " + text.trim());
341 result.makeError();
342 return result;
343 }
344 result.href = matcher.group(1);
345 result.label = matcher.group(2);
346 result.kind = "@seeHref";
347 }
348 else if (result.packageInfo != null) {
349 result.href = result.packageInfo.htmlPage();
350 if (result.label.length() == 0) {
351 result.href = result.packageInfo.htmlPage();
352 result.label = result.packageInfo.name();
353 }
354 }
355 else if (result.classInfo != null && result.referencedMemberName == null) {
356 // class reference
357 if (result.label.length() == 0) {
358 result.label = result.classInfo.name();
359 }
360 result.href = result.classInfo.htmlPage();
361 }
362 else if (result.memberInfo != null) {
363 // member reference
364 ClassInfo containing = result.memberInfo.containingClass();
365 if (result.memberInfo.isExecutable()) {
366 if (result.referencedMemberName.indexOf('(') < 0) {
367 result.referencedMemberName += method.flatSignature();
368 }
369 }
370 if (result.label.length() == 0) {
371 result.label = result.referencedMemberName;
372 }
373 result.href = containing.htmlPage() + '#' + result.memberInfo.anchor();
374 }
375
376 if (result.href == null && !skipHref) {
377 if (printOnErrors && (base == null || base.checkLevel())) {
378 Errors.error(Errors.UNRESOLVED_LINK, pos,
379 "Unresolved link/see tag \"" + text.trim()
380 + "\" in " + ((base != null) ? base.qualifiedName() : "[null]"));
381 }
382 result.makeError();
383 }
384 else if (result.memberInfo != null && !result.memberInfo.checkLevel()) {
385 if (printOnErrors && (base == null || base.checkLevel())) {
386 Errors.error(Errors.HIDDEN_LINK, pos,
387 "Link to hidden member: " + text.trim());
388 result.href = null;
389 }
390 result.kind = "@seeJustLabel";
391 }
392 else if (result.classInfo != null && !result.classInfo.checkLevel()) {
393 if (printOnErrors && (base == null || base.checkLevel())) {
394 Errors.error(Errors.HIDDEN_LINK, pos,
395 "Link to hidden class: " + text.trim() + " label=" + result.label);
396 result.href = null;
397 }
398 result.kind = "@seeJustLabel";
399 }
400 else if (result.packageInfo != null && !result.packageInfo.checkLevel()) {
401 if (printOnErrors && (base == null || base.checkLevel())) {
402 Errors.error(Errors.HIDDEN_LINK, pos,
403 "Link to hidden package: " + text.trim());
404 result.href = null;
405 }
406 result.kind = "@seeJustLabel";
407 }
408
409 result.good = true;
410
411 return result;
412 }
413
414 public boolean checkLevel() {
415 if (memberInfo != null) {
416 return memberInfo.checkLevel();
417 }
418 if (classInfo != null) {
419 return classInfo.checkLevel();
420 }
421 if (packageInfo != null) {
422 return packageInfo.checkLevel();
423 }
424 return false;
425 }
426
427 /** turn this LinkReference into one with an error message */
428 private void makeError() {
429 //this.href = "ERROR(" + this.text.trim() + ")";
430 this.href = null;
431 if (this.label == null) {
432 this.label = "";
433 }
434 this.label = "ERROR(" + this.label + "/" + text.trim() + ")";
435 }
436
437 /** private. **/
438 private LinkReference() {
439 }
440}