1 /* |
|
2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package com.sun.tools.javadoc; |
|
27 |
|
28 import java.io.File; |
|
29 import java.util.Locale; |
|
30 |
|
31 import com.sun.javadoc.*; |
|
32 import com.sun.tools.javac.code.Printer; |
|
33 import com.sun.tools.javac.code.Symbol; |
|
34 import com.sun.tools.javac.code.Type.CapturedType; |
|
35 import com.sun.tools.javac.util.*; |
|
36 |
|
37 import static com.sun.tools.javac.code.Kinds.Kind.*; |
|
38 |
|
39 /** |
|
40 * Represents a see also documentation tag. |
|
41 * The @see tag can be plain text, or reference a class or member. |
|
42 * |
|
43 * <p><b>This is NOT part of any supported API. |
|
44 * If you write code that depends on this, you do so at your own risk. |
|
45 * This code and its internal interfaces are subject to change or |
|
46 * deletion without notice.</b> |
|
47 * |
|
48 * @author Kaiyang Liu (original) |
|
49 * @author Robert Field (rewrite) |
|
50 * @author Atul M Dambalkar |
|
51 * |
|
52 */ |
|
53 class SeeTagImpl extends TagImpl implements SeeTag, LayoutCharacters { |
|
54 |
|
55 //### TODO: Searching for classes, fields, and methods |
|
56 //### should follow the normal rules applied by the compiler. |
|
57 |
|
58 /** |
|
59 * where of where#what - i.e. the class name (may be empty) |
|
60 */ |
|
61 private String where; |
|
62 |
|
63 /** |
|
64 * what of where#what - i.e. the member (may be null) |
|
65 */ |
|
66 private String what; |
|
67 |
|
68 private PackageDoc referencedPackage; |
|
69 private ClassDoc referencedClass; |
|
70 private MemberDoc referencedMember; |
|
71 |
|
72 String label = ""; |
|
73 |
|
74 SeeTagImpl(DocImpl holder, String name, String text) { |
|
75 super(holder, name, text); |
|
76 parseSeeString(); |
|
77 if (where != null) { |
|
78 ClassDocImpl container = null; |
|
79 if (holder instanceof MemberDoc) { |
|
80 container = |
|
81 (ClassDocImpl)((ProgramElementDoc)holder).containingClass(); |
|
82 } else if (holder instanceof ClassDoc) { |
|
83 container = (ClassDocImpl)holder; |
|
84 } |
|
85 findReferenced(container); |
|
86 if (showRef) showRef(); |
|
87 } |
|
88 } |
|
89 |
|
90 private static final boolean showRef = false; |
|
91 |
|
92 private void showRef() { |
|
93 Symbol sym; |
|
94 if (referencedMember != null) { |
|
95 if (referencedMember instanceof MethodDocImpl) |
|
96 sym = ((MethodDocImpl) referencedMember).sym; |
|
97 else if (referencedMember instanceof FieldDocImpl) |
|
98 sym = ((FieldDocImpl) referencedMember).sym; |
|
99 else |
|
100 sym = ((ConstructorDocImpl) referencedMember).sym; |
|
101 } else if (referencedClass != null) { |
|
102 sym = ((ClassDocImpl) referencedClass).tsym; |
|
103 } else if (referencedPackage != null) { |
|
104 sym = ((PackageDocImpl) referencedPackage).sym; |
|
105 } else |
|
106 return; |
|
107 |
|
108 final JavacMessages messages = JavacMessages.instance(docenv().context); |
|
109 Locale locale = Locale.getDefault(); |
|
110 Printer printer = new Printer() { |
|
111 int count; |
|
112 @Override |
|
113 protected String localize(Locale locale, String key, Object... args) { |
|
114 return messages.getLocalizedString(locale, key, args); |
|
115 } |
|
116 @Override |
|
117 protected String capturedVarId(CapturedType t, Locale locale) { |
|
118 return "CAP#" + (++count); |
|
119 } |
|
120 }; |
|
121 |
|
122 String s = text.replaceAll("\\s+", " "); // normalize white space |
|
123 int sp = s.indexOf(" "); |
|
124 int lparen = s.indexOf("("); |
|
125 int rparen = s.indexOf(")"); |
|
126 String seetext = (sp == -1) ? s |
|
127 : (lparen == -1 || sp < lparen) ? s.substring(0, sp) |
|
128 : s.substring(0, rparen + 1); |
|
129 |
|
130 File file = new File(holder.position().file().getAbsoluteFile().toURI().normalize()); |
|
131 |
|
132 StringBuilder sb = new StringBuilder(); |
|
133 sb.append("+++ ").append(file).append(": ") |
|
134 .append(name()).append(" ").append(seetext).append(": "); |
|
135 sb.append(sym.getKind()).append(" "); |
|
136 if (sym.kind == MTH || sym.kind == VAR) |
|
137 sb.append(printer.visit(sym.owner, locale)).append("."); |
|
138 sb.append(printer.visit(sym, locale)); |
|
139 |
|
140 System.err.println(sb); |
|
141 } |
|
142 |
|
143 /** |
|
144 * get the class name part of @see, For instance, |
|
145 * if the comment is @see String#startsWith(java.lang.String) . |
|
146 * This function returns String. |
|
147 * Returns null if format was not that of java reference. |
|
148 * Return empty string if class name was not specified.. |
|
149 */ |
|
150 public String referencedClassName() { |
|
151 return where; |
|
152 } |
|
153 |
|
154 /** |
|
155 * get the package referenced by @see. For instance, |
|
156 * if the comment is @see java.lang |
|
157 * This function returns a PackageDocImpl for java.lang |
|
158 * Returns null if no known package found. |
|
159 */ |
|
160 public PackageDoc referencedPackage() { |
|
161 return referencedPackage; |
|
162 } |
|
163 |
|
164 /** |
|
165 * get the class referenced by the class name part of @see, For instance, |
|
166 * if the comment is @see String#startsWith(java.lang.String) . |
|
167 * This function returns a ClassDocImpl for java.lang.String. |
|
168 * Returns null if class is not a class specified on the javadoc command line.. |
|
169 */ |
|
170 public ClassDoc referencedClass() { |
|
171 return referencedClass; |
|
172 } |
|
173 |
|
174 /** |
|
175 * get the name of the member referenced by the prototype part of @see, |
|
176 * For instance, |
|
177 * if the comment is @see String#startsWith(java.lang.String) . |
|
178 * This function returns "startsWith(java.lang.String)" |
|
179 * Returns null if format was not that of java reference. |
|
180 * Return empty string if member name was not specified.. |
|
181 */ |
|
182 public String referencedMemberName() { |
|
183 return what; |
|
184 } |
|
185 |
|
186 /** |
|
187 * get the member referenced by the prototype part of @see, |
|
188 * For instance, |
|
189 * if the comment is @see String#startsWith(java.lang.String) . |
|
190 * This function returns a MethodDocImpl for startsWith. |
|
191 * Returns null if member could not be determined. |
|
192 */ |
|
193 public MemberDoc referencedMember() { |
|
194 return referencedMember; |
|
195 } |
|
196 |
|
197 |
|
198 /** |
|
199 * parse @see part of comment. Determine 'where' and 'what' |
|
200 */ |
|
201 private void parseSeeString() { |
|
202 int len = text.length(); |
|
203 if (len == 0) { |
|
204 return; |
|
205 } |
|
206 switch (text.charAt(0)) { |
|
207 case '<': |
|
208 if (text.charAt(len-1) != '>') { |
|
209 docenv().warning(holder, |
|
210 "tag.see.no_close_bracket_on_url", |
|
211 name, text); |
|
212 } |
|
213 return; |
|
214 case '"': |
|
215 if (len == 1 || text.charAt(len-1) != '"') { |
|
216 docenv().warning(holder, |
|
217 "tag.see.no_close_quote", |
|
218 name, text); |
|
219 } else { |
|
220 // text = text.substring(1,len-1); // strip quotes |
|
221 } |
|
222 return; |
|
223 } |
|
224 |
|
225 // check that the text is one word, with possible parentheses |
|
226 // this part of code doesn't allow |
|
227 // @see <a href=.....>asfd</a> |
|
228 // comment it. |
|
229 |
|
230 // the code assumes that there is no initial white space. |
|
231 int parens = 0; |
|
232 int commentstart = 0; |
|
233 int start = 0; |
|
234 int cp; |
|
235 for (int i = start; i < len ; i += Character.charCount(cp)) { |
|
236 cp = text.codePointAt(i); |
|
237 switch (cp) { |
|
238 case '(': parens++; break; |
|
239 case ')': parens--; break; |
|
240 case '[': case ']': case '.': case '#': break; |
|
241 case ',': |
|
242 if (parens <= 0) { |
|
243 docenv().warning(holder, |
|
244 "tag.see.malformed_see_tag", |
|
245 name, text); |
|
246 return; |
|
247 } |
|
248 break; |
|
249 case ' ': case '\t': case '\n': case CR: |
|
250 if (parens == 0) { //here onwards the comment starts. |
|
251 commentstart = i; |
|
252 i = len; |
|
253 } |
|
254 break; |
|
255 default: |
|
256 if (!Character.isJavaIdentifierPart(cp)) { |
|
257 docenv().warning(holder, |
|
258 "tag.see.illegal_character", |
|
259 name, ""+cp, text); |
|
260 } |
|
261 break; |
|
262 } |
|
263 } |
|
264 if (parens != 0) { |
|
265 docenv().warning(holder, |
|
266 "tag.see.malformed_see_tag", |
|
267 name, text); |
|
268 return; |
|
269 } |
|
270 |
|
271 String seetext = ""; |
|
272 String labeltext = ""; |
|
273 |
|
274 if (commentstart > 0) { |
|
275 seetext = text.substring(start, commentstart); |
|
276 labeltext = text.substring(commentstart + 1); |
|
277 // strip off the white space which can be between seetext and the |
|
278 // actual label. |
|
279 for (int i = 0; i < labeltext.length(); i++) { |
|
280 char ch2 = labeltext.charAt(i); |
|
281 if (!(ch2 == ' ' || ch2 == '\t' || ch2 == '\n')) { |
|
282 label = labeltext.substring(i); |
|
283 break; |
|
284 } |
|
285 } |
|
286 } else { |
|
287 seetext = text; |
|
288 label = ""; |
|
289 } |
|
290 |
|
291 int sharp = seetext.indexOf('#'); |
|
292 if (sharp >= 0) { |
|
293 // class#member |
|
294 where = seetext.substring(0, sharp); |
|
295 what = seetext.substring(sharp + 1); |
|
296 } else { |
|
297 if (seetext.indexOf('(') >= 0) { |
|
298 docenv().warning(holder, |
|
299 "tag.see.missing_sharp", |
|
300 name, text); |
|
301 where = ""; |
|
302 what = seetext; |
|
303 } |
|
304 else { |
|
305 // no member specified, text names class |
|
306 where = seetext; |
|
307 what = null; |
|
308 } |
|
309 } |
|
310 } |
|
311 |
|
312 /** |
|
313 * Find what is referenced by the see also. If possible, sets |
|
314 * referencedClass and referencedMember. |
|
315 * |
|
316 * @param containingClass the class containing the comment containing |
|
317 * the tag. May be null, if, for example, it is a package comment. |
|
318 */ |
|
319 private void findReferenced(ClassDocImpl containingClass) { |
|
320 if (where.length() > 0) { |
|
321 if (containingClass != null) { |
|
322 referencedClass = containingClass.findClass(where); |
|
323 } else { |
|
324 referencedClass = docenv().lookupClass(where); |
|
325 } |
|
326 if (referencedClass == null && holder() instanceof ProgramElementDoc) { |
|
327 referencedClass = docenv().lookupClass( |
|
328 ((ProgramElementDoc) holder()).containingPackage().name() + "." + where); |
|
329 } |
|
330 |
|
331 if (referencedClass == null) { /* may just not be in this run */ |
|
332 // check if it's a package name |
|
333 referencedPackage = docenv().lookupPackage(where); |
|
334 return; |
|
335 } |
|
336 } else { |
|
337 if (containingClass == null) { |
|
338 docenv().warning(holder, |
|
339 "tag.see.class_not_specified", |
|
340 name, text); |
|
341 return; |
|
342 } else { |
|
343 referencedClass = containingClass; |
|
344 } |
|
345 } |
|
346 where = referencedClass.qualifiedName(); |
|
347 |
|
348 if (what == null) { |
|
349 return; |
|
350 } else { |
|
351 int paren = what.indexOf('('); |
|
352 String memName = (paren >= 0 ? what.substring(0, paren) : what); |
|
353 String[] paramarr; |
|
354 if (paren > 0) { |
|
355 // has parameter list -- should be method or constructor |
|
356 paramarr = new ParameterParseMachine(what. |
|
357 substring(paren, what.length())).parseParameters(); |
|
358 if (paramarr != null) { |
|
359 referencedMember = findExecutableMember(memName, paramarr, |
|
360 referencedClass); |
|
361 } else { |
|
362 referencedMember = null; |
|
363 } |
|
364 } else { |
|
365 // no parameter list -- should be field |
|
366 referencedMember = findExecutableMember(memName, null, |
|
367 referencedClass); |
|
368 FieldDoc fd = ((ClassDocImpl)referencedClass). |
|
369 findField(memName); |
|
370 // when no args given, prefer fields over methods |
|
371 if (referencedMember == null || |
|
372 (fd != null && |
|
373 fd.containingClass() |
|
374 .subclassOf(referencedMember.containingClass()))) { |
|
375 referencedMember = fd; |
|
376 } |
|
377 } |
|
378 if (referencedMember == null) { |
|
379 docenv().warning(holder, |
|
380 "tag.see.can_not_find_member", |
|
381 name, what, where); |
|
382 } |
|
383 } |
|
384 } |
|
385 |
|
386 private MemberDoc findReferencedMethod(String memName, String[] paramarr, |
|
387 ClassDoc referencedClass) { |
|
388 MemberDoc meth = findExecutableMember(memName, paramarr, referencedClass); |
|
389 if (meth == null) { |
|
390 for (ClassDoc nestedClass : referencedClass.innerClasses()) { |
|
391 meth = findReferencedMethod(memName, paramarr, nestedClass); |
|
392 if (meth != null) { |
|
393 return meth; |
|
394 } |
|
395 } |
|
396 } |
|
397 return null; |
|
398 } |
|
399 |
|
400 private MemberDoc findExecutableMember(String memName, String[] paramarr, |
|
401 ClassDoc referencedClass) { |
|
402 String className = referencedClass.name(); |
|
403 if (memName.equals(className.substring(className.lastIndexOf(".") + 1))) { |
|
404 return ((ClassDocImpl)referencedClass).findConstructor(memName, |
|
405 paramarr); |
|
406 } else { // it's a method. |
|
407 return ((ClassDocImpl)referencedClass).findMethod(memName, |
|
408 paramarr); |
|
409 } |
|
410 } |
|
411 |
|
412 // separate "int, String" from "(int, String)" |
|
413 // (int i, String s) ==> [0] = "int", [1] = String |
|
414 // (int[][], String[]) ==> [0] = "int[][]" // [1] = "String[]" |
|
415 class ParameterParseMachine { |
|
416 static final int START = 0; |
|
417 static final int TYPE = 1; |
|
418 static final int NAME = 2; |
|
419 static final int TNSPACE = 3; // space between type and name |
|
420 static final int ARRAYDECORATION = 4; |
|
421 static final int ARRAYSPACE = 5; |
|
422 |
|
423 String parameters; |
|
424 |
|
425 StringBuilder typeId; |
|
426 |
|
427 ListBuffer<String> paramList; |
|
428 |
|
429 ParameterParseMachine(String parameters) { |
|
430 this.parameters = parameters; |
|
431 this.paramList = new ListBuffer<>(); |
|
432 typeId = new StringBuilder(); |
|
433 } |
|
434 |
|
435 public String[] parseParameters() { |
|
436 if (parameters.equals("()")) { |
|
437 return new String[0]; |
|
438 } // now strip off '(' and ')' |
|
439 int state = START; |
|
440 int prevstate = START; |
|
441 parameters = parameters.substring(1, parameters.length() - 1); |
|
442 int cp; |
|
443 for (int index = 0; index < parameters.length(); index += Character.charCount(cp)) { |
|
444 cp = parameters.codePointAt(index); |
|
445 switch (state) { |
|
446 case START: |
|
447 if (Character.isJavaIdentifierStart(cp)) { |
|
448 typeId.append(Character.toChars(cp)); |
|
449 state = TYPE; |
|
450 } |
|
451 prevstate = START; |
|
452 break; |
|
453 case TYPE: |
|
454 if (Character.isJavaIdentifierPart(cp) || cp == '.') { |
|
455 typeId.append(Character.toChars(cp)); |
|
456 } else if (cp == '[') { |
|
457 typeId.append('['); |
|
458 state = ARRAYDECORATION; |
|
459 } else if (Character.isWhitespace(cp)) { |
|
460 state = TNSPACE; |
|
461 } else if (cp == ',') { // no name, just type |
|
462 addTypeToParamList(); |
|
463 state = START; |
|
464 } |
|
465 prevstate = TYPE; |
|
466 break; |
|
467 case TNSPACE: |
|
468 if (Character.isJavaIdentifierStart(cp)) { // name |
|
469 if (prevstate == ARRAYDECORATION) { |
|
470 docenv().warning(holder, |
|
471 "tag.missing_comma_space", |
|
472 name, |
|
473 "(" + parameters + ")"); |
|
474 return (String[])null; |
|
475 } |
|
476 addTypeToParamList(); |
|
477 state = NAME; |
|
478 } else if (cp == '[') { |
|
479 typeId.append('['); |
|
480 state = ARRAYDECORATION; |
|
481 } else if (cp == ',') { // just the type |
|
482 addTypeToParamList(); |
|
483 state = START; |
|
484 } // consume rest all |
|
485 prevstate = TNSPACE; |
|
486 break; |
|
487 case ARRAYDECORATION: |
|
488 if (cp == ']') { |
|
489 typeId.append(']'); |
|
490 state = TNSPACE; |
|
491 } else if (!Character.isWhitespace(cp)) { |
|
492 docenv().warning(holder, |
|
493 "tag.illegal_char_in_arr_dim", |
|
494 name, |
|
495 "(" + parameters + ")"); |
|
496 return (String[])null; |
|
497 } |
|
498 prevstate = ARRAYDECORATION; |
|
499 break; |
|
500 case NAME: |
|
501 if (cp == ',') { // just consume everything till ',' |
|
502 state = START; |
|
503 } |
|
504 prevstate = NAME; |
|
505 break; |
|
506 } |
|
507 } |
|
508 if (state == ARRAYDECORATION || |
|
509 (state == START && prevstate == TNSPACE)) { |
|
510 docenv().warning(holder, |
|
511 "tag.illegal_see_tag", |
|
512 "(" + parameters + ")"); |
|
513 } |
|
514 if (typeId.length() > 0) { |
|
515 paramList.append(typeId.toString()); |
|
516 } |
|
517 return paramList.toArray(new String[paramList.length()]); |
|
518 } |
|
519 |
|
520 void addTypeToParamList() { |
|
521 if (typeId.length() > 0) { |
|
522 paramList.append(typeId.toString()); |
|
523 typeId.setLength(0); |
|
524 } |
|
525 } |
|
526 } |
|
527 |
|
528 /** |
|
529 * Return the kind of this tag. |
|
530 */ |
|
531 @Override |
|
532 public String kind() { |
|
533 return "@see"; |
|
534 } |
|
535 |
|
536 /** |
|
537 * Return the label of the see tag. |
|
538 */ |
|
539 public String label() { |
|
540 return label; |
|
541 } |
|
542 } |
|