blob: 816bdb137a7b7ff2d982f824c8707aac68a14fc3 [file] [log] [blame]
Ben Dodson920dbbb2010-08-04 15:21:06 -07001/*
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
17package com.google.doclava;
18
19import java.util.regex.Pattern;
20import java.util.regex.Matcher;
21import 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 */
27public class LinkReference {
28
Jeff Hamilton72ce82e2012-08-28 15:08:11 -050029 private static final boolean DBG = false;
Jeff Hamilton1e0d3702012-06-21 04:21:54 -050030
Ben Dodson920dbbb2010-08-04 15:21:06 -070031 /** 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
Jeff Hamilton9c584842012-06-25 13:24:14 -050043 /** Non-null for federated links */
44 public String federatedSite;
45
Ben Dodson920dbbb2010-08-04 15:21:06 -070046 /** 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 /**
Dirk Doughertyc770a6e2014-02-07 20:04:27 -080062 * regex pattern to use when matching explicit 'a href' reference text
Ben Dodson920dbbb2010-08-04 15:21:06 -070063 */
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 */
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;
Jeff Hamilton9c584842012-06-25 13:24:14 -0500240 if (DBG) System.out.println("-- chose base as classinfo");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700241 }
242
243 if (ref == null) {
Jeff Hamilton9c584842012-06-25 13:24:14 -0500244 if (DBG) System.out.println("-- ref == null");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700245 // no class or package was provided, assume it's this class
246 if (cl != null) {
Jeff Hamilton9c584842012-06-25 13:24:14 -0500247 if (DBG) System.out.println("-- assumed to be cl");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700248 result.classInfo = cl;
249 }
250 } else {
Jeff Hamilton9c584842012-06-25 13:24:14 -0500251 if (DBG) System.out.println("-- they provided ref = " + ref);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700252 // they provided something, maybe it's a class or a package
253 if (cl != null) {
Jeff Hamilton9c584842012-06-25 13:24:14 -0500254 if (DBG) System.out.println("-- cl non-null");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700255 result.classInfo = cl.extendedFindClass(ref);
256 if (result.classInfo == null) {
Jeff Hamilton9c584842012-06-25 13:24:14 -0500257 if (DBG) System.out.println("-- cl.extendedFindClass was null");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700258 result.classInfo = cl.findClass(ref);
259 }
260 if (result.classInfo == null) {
Jeff Hamilton9c584842012-06-25 13:24:14 -0500261 if (DBG) System.out.println("-- cl.findClass was null");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700262 result.classInfo = cl.findInnerClass(ref);
Jeff Hamilton9c584842012-06-25 13:24:14 -0500263 if (DBG) if (result.classInfo == null) System.out.println("-- cl.findInnerClass was null");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700264 }
265 }
266 if (result.classInfo == null) {
Jeff Hamilton9c584842012-06-25 13:24:14 -0500267 if (DBG) System.out.println("-- hitting up the Converter.obtainclass");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700268 result.classInfo = Converter.obtainClass(ref);
269 }
270 if (result.classInfo == null) {
Jeff Hamilton9c584842012-06-25 13:24:14 -0500271 if (DBG) System.out.println("-- Converter.obtainClass was null");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700272 result.packageInfo = Converter.obtainPackage(ref);
273 }
274 }
275
Jeff Hamilton9c584842012-06-25 13:24:14 -0500276 if (result.classInfo == null) {
277 if (DBG) System.out.println("-- NO CLASS INFO");
278 } else {
279 Doclava.federationTagger.tag(result.classInfo);
280 for (FederatedSite site : result.classInfo.getFederatedReferences()) {
Jeff Hamilton72ce82e2012-08-28 15:08:11 -0500281 if (DBG) System.out.println("-- reg link = " + result.classInfo.htmlPage());
282 if (DBG) System.out.println("-- fed link = " +
283 site.linkFor(result.classInfo.htmlPage()));
Jeff Hamilton9c584842012-06-25 13:24:14 -0500284 }
285 }
286
Ben Dodson920dbbb2010-08-04 15:21:06 -0700287 if (result.classInfo != null && mem != null) {
288 // it's either a field or a method, prefer a field
289 if (params == null) {
290 FieldInfo field = result.classInfo.findField(mem);
291 // findField looks in containing classes, so it might actually
292 // be somewhere else; link to where it really is, not what they
293 // typed.
294 if (field != null) {
295 result.classInfo = field.containingClass();
296 result.memberInfo = field;
297 }
298 }
299 if (result.memberInfo == null) {
300 MethodInfo method = result.classInfo.findMethod(mem, params, paramDimensions, varargs);
301 if (method != null) {
302 result.classInfo = method.containingClass();
303 result.memberInfo = method;
304 }
305 }
306 }
307
308 result.referencedMemberName = mem;
309 if (params != null) {
310 result.referencedMemberName = result.referencedMemberName + '(';
311 len = params.length;
312 if (len > 0) {
313 len--;
314 for (int i = 0; i < len; i++) {
315 result.referencedMemberName =
316 result.referencedMemberName + params[i] + paramDimensions[i] + ", ";
317 }
318 result.referencedMemberName =
319 result.referencedMemberName + params[len] + paramDimensions[len];
320 }
321 result.referencedMemberName = result.referencedMemberName + ")";
322 }
323
324 // debugging spew
Jeff Hamilton9c584842012-06-25 13:24:14 -0500325 if (false) {
Ben Dodson920dbbb2010-08-04 15:21:06 -0700326 result.label = result.label + "/" + ref + "/" + mem + '/';
327 if (params != null) {
328 for (int i = 0; i < params.length; i++) {
329 result.label += params[i] + "|";
330 }
331 }
332
333 FieldInfo f = (result.memberInfo instanceof FieldInfo) ? (FieldInfo) result.memberInfo : null;
334 MethodInfo m =
335 (result.memberInfo instanceof MethodInfo) ? (MethodInfo) result.memberInfo : null;
336 result.label =
337 result.label + "/package="
338 + (result.packageInfo != null ? result.packageInfo.name() : "") + "/class="
339 + (result.classInfo != null ? result.classInfo.qualifiedName() : "") + "/field="
340 + (f != null ? f.name() : "") + "/method=" + (m != null ? m.name() : "");
341
342 }
343
344 MethodInfo method = null;
345 boolean skipHref = false;
346
347 if (result.memberInfo != null && result.memberInfo.isExecutable()) {
348 method = (MethodInfo) result.memberInfo;
349 }
350
Jeff Hamilton1e0d3702012-06-21 04:21:54 -0500351 if (DBG) System.out.println("----- label = " + result.label + ", text = '" + text + "'");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700352 if (text.startsWith("\"")) {
353 // literal quoted reference (e.g., a book title)
354 Matcher matcher = QUOTE_PATTERN.matcher(text);
355 if (!matcher.matches()) {
356 Errors.error(Errors.UNRESOLVED_LINK, pos, "unbalanced quoted link/see tag: " + text.trim());
357 result.makeError();
358 return result;
359 }
360 skipHref = true;
361 result.label = matcher.group(1);
362 result.kind = "@seeJustLabel";
Jeff Hamilton1e0d3702012-06-21 04:21:54 -0500363 if (DBG) System.out.println(" ---- literal quoted reference");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700364 } else if (text.startsWith("<")) {
365 // explicit "<a href" form
366 Matcher matcher = HREF_PATTERN.matcher(text);
367 if (!matcher.matches()) {
368 Errors.error(Errors.UNRESOLVED_LINK, pos, "invalid <a> link/see tag: " + text.trim());
369 result.makeError();
370 return result;
371 }
372 result.href = matcher.group(1);
373 result.label = matcher.group(2);
374 result.kind = "@seeHref";
Jeff Hamilton1e0d3702012-06-21 04:21:54 -0500375 if (DBG) System.out.println(" ---- explicit href reference");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700376 } else if (result.packageInfo != null) {
377 result.href = result.packageInfo.htmlPage();
378 if (result.label.length() == 0) {
379 result.href = result.packageInfo.htmlPage();
380 result.label = result.packageInfo.name();
381 }
Jeff Hamilton1e0d3702012-06-21 04:21:54 -0500382 if (DBG) System.out.println(" ---- packge reference");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700383 } else if (result.classInfo != null && result.referencedMemberName == null) {
384 // class reference
385 if (result.label.length() == 0) {
386 result.label = result.classInfo.name();
387 }
Jeff Hamilton9c584842012-06-25 13:24:14 -0500388 setHref(result, result.classInfo, null);
Jeff Hamilton1e0d3702012-06-21 04:21:54 -0500389 if (DBG) System.out.println(" ---- class reference");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700390 } else if (result.memberInfo != null) {
391 // member reference
392 ClassInfo containing = result.memberInfo.containingClass();
393 if (result.memberInfo.isExecutable()) {
394 if (result.referencedMemberName.indexOf('(') < 0) {
395 result.referencedMemberName += method.flatSignature();
396 }
397 }
398 if (result.label.length() == 0) {
399 result.label = result.referencedMemberName;
400 }
Jeff Hamilton9c584842012-06-25 13:24:14 -0500401 setHref(result, containing, result.memberInfo.anchor());
Jeff Hamilton1e0d3702012-06-21 04:21:54 -0500402 if (DBG) System.out.println(" ---- member reference");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700403 }
Jeff Hamilton1e0d3702012-06-21 04:21:54 -0500404 if (DBG) System.out.println(" --- href = '" + result.href + "'");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700405
406 if (result.href == null && !skipHref) {
407 if (printOnErrors && (base == null || base.checkLevel())) {
408 Errors.error(Errors.UNRESOLVED_LINK, pos, "Unresolved link/see tag \"" + text.trim()
409 + "\" in " + ((base != null) ? base.qualifiedName() : "[null]"));
410 }
411 result.makeError();
412 } else if (result.memberInfo != null && !result.memberInfo.checkLevel()) {
413 if (printOnErrors && (base == null || base.checkLevel())) {
414 Errors.error(Errors.HIDDEN_LINK, pos, "Link to hidden member: " + text.trim());
415 result.href = null;
416 }
417 result.kind = "@seeJustLabel";
418 } else if (result.classInfo != null && !result.classInfo.checkLevel()) {
419 if (printOnErrors && (base == null || base.checkLevel())) {
420 Errors.error(Errors.HIDDEN_LINK, pos, "Link to hidden class: " + text.trim() + " label="
421 + result.label);
422 result.href = null;
423 }
424 result.kind = "@seeJustLabel";
425 } else if (result.packageInfo != null && !result.packageInfo.checkLevel()) {
426 if (printOnErrors && (base == null || base.checkLevel())) {
427 Errors.error(Errors.HIDDEN_LINK, pos, "Link to hidden package: " + text.trim());
428 result.href = null;
429 }
430 result.kind = "@seeJustLabel";
431 }
432
433 result.good = true;
434
435 return result;
436 }
437
438 public boolean checkLevel() {
439 if (memberInfo != null) {
440 return memberInfo.checkLevel();
441 }
442 if (classInfo != null) {
443 return classInfo.checkLevel();
444 }
445 if (packageInfo != null) {
446 return packageInfo.checkLevel();
447 }
448 return false;
449 }
450
451 /** turn this LinkReference into one with an error message */
452 private void makeError() {
453 // this.href = "ERROR(" + this.text.trim() + ")";
454 this.href = null;
455 if (this.label == null) {
456 this.label = "";
457 }
458 this.label = "ERROR(" + this.label + "/" + text.trim() + ")";
459 }
460
Jeff Hamilton9c584842012-06-25 13:24:14 -0500461 static private void setHref(LinkReference reference, ClassInfo info, String member) {
462 String htmlPage = info.htmlPage();
463 if (member != null) {
464 htmlPage = htmlPage + "#" + member;
465 }
466
467 Doclava.federationTagger.tag(info);
468 if (!info.getFederatedReferences().isEmpty()) {
469 FederatedSite site = info.getFederatedReferences().iterator().next();
470 reference.href = site.linkFor(htmlPage);
471 reference.federatedSite = site.name();
472 } else {
473 reference.href = htmlPage;
474 }
475 }
476
Ben Dodson920dbbb2010-08-04 15:21:06 -0700477 /** private. **/
478 private LinkReference() {}
479}