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