• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 Google Inc.
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 package com.google.doclava;
18 
19 import java.util.regex.Pattern;
20 import java.util.regex.Matcher;
21 import java.util.ArrayList;
22 
23 /**
24  * Class that represents what you see in an link or see tag. This is factored out of SeeTagInfo so
25  * it can be used elsewhere (like AttrTagInfo).
26  */
27 public class LinkReference {
28 
29   private static final boolean DBG = false;
30 
31   /** The original text. */
32   public String text;
33 
34   /** The kind of this tag, if we have a new suggestion after parsing. */
35   public String kind;
36 
37   /** The user visible text. */
38   public String label;
39 
40   /** The link. */
41   public String href;
42 
43   /** Non-null for federated links */
44   public String federatedSite;
45 
46   /** The {@link PackageInfo} if any. */
47   public PackageInfo packageInfo;
48 
49   /** The {@link ClassInfo} if any. */
50   public ClassInfo classInfo;
51 
52   /** The {@link MemberInfo} if any. */
53   public MemberInfo memberInfo;
54 
55   /** The name of the referenced member PackageInfo} if any. */
56   public String referencedMemberName;
57 
58   /** Set to true if everything is a-ok */
59   public boolean good;
60 
61   /**
62    * regex pattern to use when matching explicit 'a href' reference text
63    */
64   private static final Pattern HREF_PATTERN =
65       Pattern.compile("^<a href=\"([^\"]*)\">([^<]*)</a>[ \n\r\t]*$", Pattern.CASE_INSENSITIVE);
66 
67   /**
68    * regex pattern to use when matching double-quoted reference text
69    */
70   private static final Pattern QUOTE_PATTERN = Pattern.compile("^\"([^\"]*)\"[ \n\r\t]*$");
71 
72   /**
73    * Parse and resolve a link string.
74    *
75    * @param text the original text
76    * @param base the class or whatever that this link is on
77    * @param pos the original position in the source document
78    * @return a new link reference. It always returns something. If there was an error, it logs it
79    *         and fills in href and label with error text.
80    */
parse(String text, ContainerInfo base, SourcePositionInfo pos, boolean printOnErrors)81   public static LinkReference parse(String text, ContainerInfo base, SourcePositionInfo pos,
82       boolean printOnErrors) {
83     LinkReference result = new LinkReference();
84     result.text = text;
85 
86     int index;
87     int len = text.length();
88     int pairs = 0;
89     int pound = -1;
90     // split the string
91     done: {
92       for (index = 0; index < len; index++) {
93         char c = text.charAt(index);
94         switch (c) {
95           case '(':
96             pairs++;
97             break;
98           case '[':
99             pairs++;
100             break;
101           case ')':
102             pairs--;
103             break;
104           case ']':
105             pairs--;
106             break;
107           case ' ':
108           case '\t':
109           case '\r':
110           case '\n':
111             if (pairs == 0) {
112               break done;
113             }
114             break;
115           case '#':
116             if (pound < 0) {
117               pound = index;
118             }
119             break;
120         }
121       }
122     }
123     if (index == len && pairs != 0) {
124       Errors.error(Errors.UNRESOLVED_LINK, pos, "unable to parse link/see tag: " + text.trim());
125       return result;
126     }
127 
128     int linkend = index;
129 
130     for (; index < len; index++) {
131       char c = text.charAt(index);
132       if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) {
133         break;
134       }
135     }
136 
137     result.label = text.substring(index);
138 
139     String ref;
140     String mem;
141     if (pound == 0) {
142       ref = null;
143       mem = text.substring(1, linkend);
144     } else if (pound > 0) {
145       ref = text.substring(0, pound);
146       mem = text.substring(pound + 1, linkend);
147     } else {
148       ref = text.substring(0, linkend);
149       mem = null;
150     }
151 
152     // parse parameters, if any
153     String[] params = null;
154     String[] paramDimensions = null;
155     boolean varargs = false;
156     if (mem != null) {
157       index = mem.indexOf('(');
158       if (index > 0) {
159         ArrayList<String> paramList = new ArrayList<String>();
160         ArrayList<String> paramDimensionList = new ArrayList<String>();
161         len = mem.length();
162         int start = index + 1;
163         final int START = 0;
164         final int TYPE = 1;
165         final int NAME = 2;
166         int dimension = 0;
167         int arraypair = 0;
168         int state = START;
169         int typestart = 0;
170         int typeend = -1;
171         for (int i = start; i < len; i++) {
172           char c = mem.charAt(i);
173           switch (state) {
174             case START:
175               if (c != ' ' && c != '\t' && c != '\r' && c != '\n') {
176                 state = TYPE;
177                 typestart = i;
178               }
179               break;
180             case TYPE:
181               if (c == '.') {
182                 if (mem.length() > i+2 && mem.charAt(i+1) == '.' && mem.charAt(i+2) == '.') {
183                   if (typeend < 0) {
184                     typeend = i;
185                   }
186                   varargs = true;
187                 }
188               } else if (c == '[') {
189                 if (typeend < 0) {
190                   typeend = i;
191                 }
192                 dimension++;
193                 arraypair++;
194               } else if (c == ']') {
195                 arraypair--;
196               } else if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
197                 if (typeend < 0) {
198                   typeend = i;
199                 }
200               } else {
201                 if (typeend >= 0 || c == ')' || c == ',') {
202                   if (typeend < 0) {
203                     typeend = i;
204                   }
205                   String s = mem.substring(typestart, typeend);
206                   paramList.add(s);
207                   s = "";
208                   for (int j = 0; j < dimension; j++) {
209                     s += "[]";
210                   }
211                   paramDimensionList.add(s);
212                   state = START;
213                   typeend = -1;
214                   dimension = 0;
215                   if (c == ',' || c == ')') {
216                     state = START;
217                   } else {
218                     state = NAME;
219                   }
220                 }
221               }
222               break;
223             case NAME:
224               if (c == ',' || c == ')') {
225                 state = START;
226               }
227               break;
228           }
229 
230         }
231         params = paramList.toArray(new String[paramList.size()]);
232         paramDimensions = paramDimensionList.toArray(new String[paramList.size()]);
233         mem = mem.substring(0, index);
234       }
235     }
236 
237     ClassInfo cl = null;
238     if (base instanceof ClassInfo) {
239       cl = (ClassInfo) base;
240       if (DBG) System.out.println("-- chose base as classinfo");
241     }
242 
243     if (ref == null) {
244       if (DBG) System.out.println("-- ref == null");
245       // no class or package was provided, assume it's this class
246       if (cl != null) {
247         if (DBG) System.out.println("-- assumed to be cl");
248         result.classInfo = cl;
249       }
250     } else {
251       if (DBG) System.out.println("-- they provided ref = " + ref);
252       // they provided something, maybe it's a class or a package
253       if (cl != null) {
254         try {
255           if (DBG) System.out.println("-- cl non-null");
256           result.classInfo = cl.extendedFindClass(ref);
257           if (result.classInfo == null) {
258             if (DBG) System.out.println("-- cl.extendedFindClass was null");
259             result.classInfo = cl.findClass(ref);
260           }
261           if (result.classInfo == null) {
262             if (DBG) System.out.println("-- cl.findClass was null");
263             result.classInfo = cl.findInnerClass(ref);
264             if (DBG) if (result.classInfo == null) System.out.println("-- cl.findInnerClass was null");
265           }
266         } catch (RuntimeException e) {
267           throw new RuntimeException("Failed to resolve class at " + pos, e);
268         }
269       }
270       if (result.classInfo == null) {
271         if (DBG) System.out.println("-- hitting up the Converter.obtainclass");
272         result.classInfo = Converter.obtainClass(ref);
273       }
274       if (result.classInfo == null) {
275         if (DBG) System.out.println("-- Converter.obtainClass was null");
276         result.packageInfo = Converter.obtainPackage(ref);
277       }
278     }
279 
280     if (result.classInfo == null) {
281         if (DBG) System.out.println("-- NO CLASS INFO");
282     } else {
283         Doclava.federationTagger.tag(result.classInfo);
284         for (FederatedSite site : result.classInfo.getFederatedReferences()) {
285           if (DBG) System.out.println("-- reg link = " + result.classInfo.htmlPage());
286           if (DBG) System.out.println("-- fed link = " +
287               site.linkFor(result.classInfo.htmlPage()));
288         }
289     }
290 
291     if (result.classInfo != null && mem != null) {
292       // it's either a field or a method, prefer a field
293       if (params == null) {
294         FieldInfo field = result.classInfo.findField(mem);
295         // findField looks in containing classes, so it might actually
296         // be somewhere else; link to where it really is, not what they
297         // typed.
298         if (field != null) {
299           result.classInfo = field.containingClass();
300           result.memberInfo = field;
301         }
302       }
303       if (result.memberInfo == null) {
304         MethodInfo method = result.classInfo.findMethod(mem, params, paramDimensions, varargs);
305         if (method != null) {
306           result.classInfo = method.containingClass();
307           result.memberInfo = method;
308         }
309       }
310     }
311 
312     result.referencedMemberName = mem;
313     if (params != null) {
314       result.referencedMemberName = result.referencedMemberName + '(';
315       len = params.length;
316       if (len > 0) {
317         len--;
318         for (int i = 0; i < len; i++) {
319           result.referencedMemberName =
320               result.referencedMemberName + params[i] + paramDimensions[i] + ", ";
321         }
322         result.referencedMemberName =
323             result.referencedMemberName + params[len] + paramDimensions[len];
324       }
325       result.referencedMemberName = result.referencedMemberName + ")";
326     }
327 
328     // debugging spew
329     if (false) {
330       result.label = result.label + "/" + ref + "/" + mem + '/';
331       if (params != null) {
332         for (int i = 0; i < params.length; i++) {
333           result.label += params[i] + "|";
334         }
335       }
336 
337       FieldInfo f = (result.memberInfo instanceof FieldInfo) ? (FieldInfo) result.memberInfo : null;
338       MethodInfo m =
339           (result.memberInfo instanceof MethodInfo) ? (MethodInfo) result.memberInfo : null;
340       result.label =
341           result.label + "/package="
342               + (result.packageInfo != null ? result.packageInfo.name() : "") + "/class="
343               + (result.classInfo != null ? result.classInfo.qualifiedName() : "") + "/field="
344               + (f != null ? f.name() : "") + "/method=" + (m != null ? m.name() : "");
345 
346     }
347 
348     MethodInfo method = null;
349     boolean skipHref = false;
350 
351     if (result.memberInfo != null && result.memberInfo.isExecutable()) {
352       method = (MethodInfo) result.memberInfo;
353     }
354 
355     if (DBG) System.out.println("----- label = " + result.label + ", text = '" + text + "'");
356     if (text.startsWith("\"")) {
357       // literal quoted reference (e.g., a book title)
358       Matcher matcher = QUOTE_PATTERN.matcher(text);
359       if (!matcher.matches()) {
360         Errors.error(Errors.UNRESOLVED_LINK, pos, "unbalanced quoted link/see tag: " + text.trim());
361         result.makeError();
362         return result;
363       }
364       skipHref = true;
365       result.label = matcher.group(1);
366       result.kind = "@seeJustLabel";
367       if (DBG) System.out.println(" ---- literal quoted reference");
368     } else if (text.startsWith("<")) {
369       // explicit "<a href" form
370       Matcher matcher = HREF_PATTERN.matcher(text);
371       if (!matcher.matches()) {
372         Errors.error(Errors.UNRESOLVED_LINK, pos, "invalid <a> link/see tag: " + text.trim());
373         result.makeError();
374         return result;
375       }
376       result.href = matcher.group(1);
377       result.label = matcher.group(2);
378       result.kind = "@seeHref";
379       if (DBG) System.out.println(" ---- explicit href reference");
380     } else if (result.packageInfo != null) {
381       result.href = result.packageInfo.htmlPage();
382       if (result.label.length() == 0) {
383         result.href = result.packageInfo.htmlPage();
384         result.label = result.packageInfo.name();
385       }
386       if (DBG) System.out.println(" ---- packge reference");
387     } else if (result.classInfo != null && result.referencedMemberName == null) {
388       // class reference
389       if (result.label.length() == 0) {
390         result.label = result.classInfo.name();
391       }
392       setHref(result, result.classInfo, null);
393       if (DBG) System.out.println(" ---- class reference");
394     } else if (result.memberInfo != null) {
395       // member reference
396       ClassInfo containing = result.memberInfo.containingClass();
397       if (result.memberInfo.isExecutable()) {
398         if (result.referencedMemberName.indexOf('(') < 0) {
399           result.referencedMemberName += method.flatSignature();
400         }
401       }
402       if (result.label.length() == 0) {
403         // Qualify labels that link beyond the base context
404         final boolean beyondBase = base != null && containing != null
405             && !base.qualifiedName().equals(containing.qualifiedName());
406         if (beyondBase) {
407           result.label = containing.name() + "." + result.referencedMemberName;
408         } else {
409           result.label = result.referencedMemberName;
410         }
411       }
412       setHref(result, containing, result.memberInfo.anchor());
413       if (DBG) System.out.println(" ---- member reference");
414     }
415     if (DBG) System.out.println("  --- href = '" + result.href + "'");
416 
417     if (result.href == null && !skipHref) {
418       if (printOnErrors && (base == null || base.checkLevel())) {
419         Errors.error(Errors.UNRESOLVED_LINK, pos, "Unresolved link/see tag \"" + text.trim()
420             + "\" in " + ((base != null) ? base.qualifiedName() : "[null]"));
421       }
422       result.makeError();
423     } else if (result.memberInfo != null && !result.memberInfo.checkLevel()) {
424       if (printOnErrors && (base == null || base.checkLevel())) {
425         Errors.error(Errors.HIDDEN_LINK, pos, "Link to hidden member: " + text.trim());
426         result.href = null;
427       }
428       result.kind = "@seeJustLabel";
429     } else if (result.classInfo != null && !result.classInfo.checkLevel()) {
430       if (printOnErrors && (base == null || base.checkLevel())) {
431         Errors.error(Errors.HIDDEN_LINK, pos, "Link to hidden class: " + text.trim() + " label="
432             + result.label);
433         result.href = null;
434       }
435       result.kind = "@seeJustLabel";
436     } else if (result.packageInfo != null && !result.packageInfo.checkLevel()) {
437       if (printOnErrors && (base == null || base.checkLevel())) {
438         Errors.error(Errors.HIDDEN_LINK, pos, "Link to hidden package: " + text.trim());
439         result.href = null;
440       }
441       result.kind = "@seeJustLabel";
442     }
443 
444     result.good = true;
445 
446     return result;
447   }
448 
checkLevel()449   public boolean checkLevel() {
450     if (memberInfo != null) {
451       return memberInfo.checkLevel();
452     }
453     if (classInfo != null) {
454       return classInfo.checkLevel();
455     }
456     if (packageInfo != null) {
457       return packageInfo.checkLevel();
458     }
459     return false;
460   }
461 
462   /** turn this LinkReference into one with an error message */
makeError()463   private void makeError() {
464     // this.href = "ERROR(" + this.text.trim() + ")";
465     this.href = null;
466     if (this.label == null) {
467       this.label = "";
468     }
469     this.label = "ERROR(" + this.label + "/" + text.trim() + ")";
470   }
471 
setHref(LinkReference reference, ClassInfo info, String member)472   static private void setHref(LinkReference reference, ClassInfo info, String member) {
473     String htmlPage = info.htmlPage();
474     if (member != null) {
475       htmlPage = htmlPage + "#" + member;
476     }
477 
478     Doclava.federationTagger.tag(info);
479     if (!info.getFederatedReferences().isEmpty()) {
480       FederatedSite site = info.getFederatedReferences().iterator().next();
481       reference.href = site.linkFor(htmlPage);
482       reference.federatedSite = site.name();
483     } else {
484       reference.href = htmlPage;
485     }
486   }
487 
488   /** private. **/
LinkReference()489   private LinkReference() {}
490 }
491