blob: b1f998ab9b42433c22303ee69b2fd9c9c8869643 [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 /**
Wu-cheng Lifcc7cf72010-04-04 17:14:23 +080062 * regex pattern to use when matching double-quoted reference text
63 */
64 private static final Pattern QUOTE_PATTERN
65 = Pattern.compile("^\"([^\"]*)\"[ \n\r\t]*$");
66
67 /**
The Android Open Source Project88b60792009-03-03 19:28:42 -080068 * Parse and resolve a link string.
69 *
70 * @param text the original text
71 * @param base the class or whatever that this link is on
72 * @param pos the original position in the source document
73 * @return a new link reference. It always returns something. If there was an
74 * error, it logs it and fills in href and label with error text.
75 */
76 public static LinkReference parse(String text, ContainerInfo base, SourcePositionInfo pos,
77 boolean printOnErrors) {
78 LinkReference result = new LinkReference();
79 result.text = text;
80
81 int index;
82 int len = text.length();
83 int pairs = 0;
84 int pound = -1;
85 // split the string
86 done: {
87 for (index=0; index<len; index++) {
88 char c = text.charAt(index);
89 switch (c)
90 {
91 case '(':
92 pairs++;
93 break;
94 case '[':
95 pairs++;
96 break;
97 case ')':
98 pairs--;
99 break;
100 case ']':
101 pairs--;
102 break;
103 case ' ':
104 case '\t':
105 case '\r':
106 case '\n':
107 if (pairs == 0) {
108 break done;
109 }
110 break;
111 case '#':
112 if (pound < 0) {
113 pound = index;
114 }
115 break;
116 }
117 }
118 }
119 if (index == len && pairs != 0) {
120 Errors.error(Errors.UNRESOLVED_LINK, pos,
121 "unable to parse link/see tag: " + text.trim());
122 return result;
123 }
124
125 int linkend = index;
126
127 for (; index<len; index++) {
128 char c = text.charAt(index);
129 if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) {
130 break;
131 }
132 }
133
134 result.label = text.substring(index);
135
136 String ref;
137 String mem;
138 if (pound == 0) {
139 ref = null;
140 mem = text.substring(1, linkend);
141 }
142 else if (pound > 0) {
143 ref = text.substring(0, pound);
144 mem = text.substring(pound+1, linkend);
145 }
146 else {
147 ref = text.substring(0, linkend);
148 mem = null;
149 }
150
151 // parse parameters, if any
152 String[] params = null;
153 String[] paramDimensions = null;
154 if (mem != null) {
155 index = mem.indexOf('(');
156 if (index > 0) {
157 ArrayList<String> paramList = new ArrayList<String>();
158 ArrayList<String> paramDimensionList = new ArrayList<String>();
159 len = mem.length();
160 int start = index+1;
161 final int START = 0;
162 final int TYPE = 1;
163 final int NAME = 2;
164 int dimension = 0;
165 int arraypair = 0;
166 int state = START;
167 int typestart = 0;
168 int typeend = -1;
169 for (int i=start; i<len; i++) {
170 char c = mem.charAt(i);
171 switch (state)
172 {
173 case START:
174 if (c!=' ' && c!='\t' && c!='\r' && c!='\n') {
175 state = TYPE;
176 typestart = i;
177 }
178 break;
179 case TYPE:
180 if (c == '[') {
181 if (typeend < 0) {
182 typeend = i;
183 }
184 dimension++;
185 arraypair++;
186 }
187 else if (c == ']') {
188 arraypair--;
189 }
190 else if (c==' ' || c=='\t' || c=='\r' || c=='\n') {
191 if (typeend < 0) {
192 typeend = i;
193 }
194 }
195 else {
196 if (typeend >= 0 || c == ')' || c == ',') {
197 if (typeend < 0) {
198 typeend = i;
199 }
200 String s = mem.substring(typestart, typeend);
201 paramList.add(s);
202 s = "";
203 for (int j=0; j<dimension; j++) {
204 s += "[]";
205 }
206 paramDimensionList.add(s);
207 state = START;
208 typeend = -1;
209 dimension = 0;
210 if (c == ',' || c == ')') {
211 state = START;
212 } else {
213 state = NAME;
214 }
215 }
216 }
217 break;
218 case NAME:
219 if (c == ',' || c == ')') {
220 state = START;
221 }
222 break;
223 }
224
225 }
226 params = paramList.toArray(new String[paramList.size()]);
227 paramDimensions = paramDimensionList.toArray(new String[paramList.size()]);
228 mem = mem.substring(0, index);
229 }
230 }
231
232 ClassInfo cl = null;
233 if (base instanceof ClassInfo) {
234 cl = (ClassInfo)base;
235 }
236
237 if (ref == null) {
238 // no class or package was provided, assume it's this class
239 if (cl != null) {
240 result.classInfo = cl;
241 }
242 } else {
243 // they provided something, maybe it's a class or a package
244 if (cl != null) {
245 result.classInfo = cl.extendedFindClass(ref);
246 if (result.classInfo == null) {
247 result.classInfo = cl.findClass(ref);
248 }
249 if (result.classInfo == null) {
250 result.classInfo = cl.findInnerClass(ref);
251 }
252 }
253 if (result.classInfo == null) {
254 result.classInfo = Converter.obtainClass(ref);
255 }
256 if (result.classInfo == null) {
257 result.packageInfo = Converter.obtainPackage(ref);
258 }
259 }
260
261 if (result.classInfo != null && mem != null) {
262 // it's either a field or a method, prefer a field
263 if (params == null) {
264 FieldInfo field = result.classInfo.findField(mem);
265 // findField looks in containing classes, so it might actually
266 // be somewhere else; link to where it really is, not what they
267 // typed.
268 if (field != null) {
269 result.classInfo = field.containingClass();
270 result.memberInfo = field;
271 }
272 }
273 if (result.memberInfo == null) {
274 MethodInfo method = result.classInfo.findMethod(mem, params, paramDimensions);
275 if (method != null) {
276 result.classInfo = method.containingClass();
277 result.memberInfo = method;
278 }
279 }
280 }
281
282 result.referencedMemberName = mem;
283 if (params != null) {
284 result.referencedMemberName = result.referencedMemberName + '(';
285 len = params.length;
286 if (len > 0) {
287 len--;
288 for (int i=0; i<len; i++) {
289 result.referencedMemberName = result.referencedMemberName + params[i]
290 + paramDimensions[i] + ", ";
291 }
292 result.referencedMemberName = result.referencedMemberName + params[len]
293 + paramDimensions[len];
294 }
295 result.referencedMemberName = result.referencedMemberName + ")";
296 }
297
298 // debugging spew
299 if (false) {
300 result.label = result.label + "/" + ref + "/" + mem + '/';
301 if (params != null) {
302 for (int i=0; i<params.length; i++) {
303 result.label += params[i] + "|";
304 }
305 }
306
307 FieldInfo f = (result.memberInfo instanceof FieldInfo)
308 ? (FieldInfo)result.memberInfo
309 : null;
310 MethodInfo m = (result.memberInfo instanceof MethodInfo)
311 ? (MethodInfo)result.memberInfo
312 : null;
313 result.label = result.label
314 + "/package=" + (result.packageInfo!=null?result.packageInfo.name():"")
315 + "/class=" + (result.classInfo!=null?result.classInfo.qualifiedName():"")
316 + "/field=" + (f!=null?f.name():"")
317 + "/method=" + (m!=null?m.name():"");
318
319 }
320
321 MethodInfo method = null;
322 boolean skipHref = false;
323
324 if (result.memberInfo != null && result.memberInfo.isExecutable()) {
325 method = (MethodInfo)result.memberInfo;
326 }
327
328 if (text.startsWith("\"")) {
329 // literal quoted reference (e.g., a book title)
Wu-cheng Lifcc7cf72010-04-04 17:14:23 +0800330 Matcher matcher = QUOTE_PATTERN.matcher(text);
331 if (! matcher.matches()) {
The Android Open Source Project88b60792009-03-03 19:28:42 -0800332 Errors.error(Errors.UNRESOLVED_LINK, pos,
333 "unbalanced quoted link/see tag: " + text.trim());
334 result.makeError();
335 return result;
336 }
Wu-cheng Lifcc7cf72010-04-04 17:14:23 +0800337 skipHref = true;
338 result.label = matcher.group(1);
The Android Open Source Project88b60792009-03-03 19:28:42 -0800339 result.kind = "@seeJustLabel";
340 }
341 else if (text.startsWith("<")) {
342 // explicit "<a href" form
343 Matcher matcher = HREF_PATTERN.matcher(text);
344 if (! matcher.matches()) {
345 Errors.error(Errors.UNRESOLVED_LINK, pos,
346 "invalid <a> link/see tag: " + text.trim());
347 result.makeError();
348 return result;
349 }
350 result.href = matcher.group(1);
351 result.label = matcher.group(2);
352 result.kind = "@seeHref";
353 }
354 else if (result.packageInfo != null) {
355 result.href = result.packageInfo.htmlPage();
356 if (result.label.length() == 0) {
357 result.href = result.packageInfo.htmlPage();
358 result.label = result.packageInfo.name();
359 }
360 }
361 else if (result.classInfo != null && result.referencedMemberName == null) {
362 // class reference
363 if (result.label.length() == 0) {
364 result.label = result.classInfo.name();
365 }
366 result.href = result.classInfo.htmlPage();
367 }
368 else if (result.memberInfo != null) {
369 // member reference
370 ClassInfo containing = result.memberInfo.containingClass();
371 if (result.memberInfo.isExecutable()) {
372 if (result.referencedMemberName.indexOf('(') < 0) {
373 result.referencedMemberName += method.flatSignature();
374 }
375 }
376 if (result.label.length() == 0) {
377 result.label = result.referencedMemberName;
378 }
379 result.href = containing.htmlPage() + '#' + result.memberInfo.anchor();
380 }
381
382 if (result.href == null && !skipHref) {
383 if (printOnErrors && (base == null || base.checkLevel())) {
384 Errors.error(Errors.UNRESOLVED_LINK, pos,
385 "Unresolved link/see tag \"" + text.trim()
386 + "\" in " + ((base != null) ? base.qualifiedName() : "[null]"));
387 }
388 result.makeError();
389 }
390 else if (result.memberInfo != null && !result.memberInfo.checkLevel()) {
391 if (printOnErrors && (base == null || base.checkLevel())) {
392 Errors.error(Errors.HIDDEN_LINK, pos,
393 "Link to hidden member: " + text.trim());
394 result.href = null;
395 }
396 result.kind = "@seeJustLabel";
397 }
398 else if (result.classInfo != null && !result.classInfo.checkLevel()) {
399 if (printOnErrors && (base == null || base.checkLevel())) {
400 Errors.error(Errors.HIDDEN_LINK, pos,
401 "Link to hidden class: " + text.trim() + " label=" + result.label);
402 result.href = null;
403 }
404 result.kind = "@seeJustLabel";
405 }
406 else if (result.packageInfo != null && !result.packageInfo.checkLevel()) {
407 if (printOnErrors && (base == null || base.checkLevel())) {
408 Errors.error(Errors.HIDDEN_LINK, pos,
409 "Link to hidden package: " + text.trim());
410 result.href = null;
411 }
412 result.kind = "@seeJustLabel";
413 }
414
415 result.good = true;
416
417 return result;
418 }
419
420 public boolean checkLevel() {
421 if (memberInfo != null) {
422 return memberInfo.checkLevel();
423 }
424 if (classInfo != null) {
425 return classInfo.checkLevel();
426 }
427 if (packageInfo != null) {
428 return packageInfo.checkLevel();
429 }
430 return false;
431 }
432
433 /** turn this LinkReference into one with an error message */
434 private void makeError() {
435 //this.href = "ERROR(" + this.text.trim() + ")";
436 this.href = null;
437 if (this.label == null) {
438 this.label = "";
439 }
440 this.label = "ERROR(" + this.label + "/" + text.trim() + ")";
441 }
442
443 /** private. **/
444 private LinkReference() {
445 }
446}