• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 import java.util.regex.Pattern;
18 import java.util.regex.Matcher;
19 import 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  */
25 public 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      */
parse(String text, ContainerInfo base, SourcePositionInfo pos, boolean printOnErrors)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 
checkLevel()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 */
makeError()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. **/
LinkReference()438     private LinkReference() {
439     }
440 }
441