Sat, 24 Oct 2020 16:44:00 +0800
Merge
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 */
26 package com.sun.tools.javadoc;
28 import java.io.File;
29 import java.util.Locale;
31 import com.sun.javadoc.*;
32 import com.sun.tools.javac.code.Kinds;
33 import com.sun.tools.javac.code.Printer;
34 import com.sun.tools.javac.code.Symbol;
35 import com.sun.tools.javac.code.Type.CapturedType;
36 import com.sun.tools.javac.util.*;
38 /**
39 * Represents a see also documentation tag.
40 * The @see tag can be plain text, or reference a class or member.
41 *
42 * <p><b>This is NOT part of any supported API.
43 * If you write code that depends on this, you do so at your own risk.
44 * This code and its internal interfaces are subject to change or
45 * deletion without notice.</b>
46 *
47 * @author Kaiyang Liu (original)
48 * @author Robert Field (rewrite)
49 * @author Atul M Dambalkar
50 *
51 */
52 class SeeTagImpl extends TagImpl implements SeeTag, LayoutCharacters {
54 //### TODO: Searching for classes, fields, and methods
55 //### should follow the normal rules applied by the compiler.
57 /**
58 * where of where#what - i.e. the class name (may be empty)
59 */
60 private String where;
62 /**
63 * what of where#what - i.e. the member (may be null)
64 */
65 private String what;
67 private PackageDoc referencedPackage;
68 private ClassDoc referencedClass;
69 private MemberDoc referencedMember;
71 String label = "";
73 SeeTagImpl(DocImpl holder, String name, String text) {
74 super(holder, name, text);
75 parseSeeString();
76 if (where != null) {
77 ClassDocImpl container = null;
78 if (holder instanceof MemberDoc) {
79 container =
80 (ClassDocImpl)((ProgramElementDoc)holder).containingClass();
81 } else if (holder instanceof ClassDoc) {
82 container = (ClassDocImpl)holder;
83 }
84 findReferenced(container);
85 if (showRef) showRef();
86 }
87 }
89 private static final boolean showRef = false;
91 private void showRef() {
92 Symbol sym;
93 if (referencedMember != null) {
94 if (referencedMember instanceof MethodDocImpl)
95 sym = ((MethodDocImpl) referencedMember).sym;
96 else if (referencedMember instanceof FieldDocImpl)
97 sym = ((FieldDocImpl) referencedMember).sym;
98 else
99 sym = ((ConstructorDocImpl) referencedMember).sym;
100 } else if (referencedClass != null) {
101 sym = ((ClassDocImpl) referencedClass).tsym;
102 } else if (referencedPackage != null) {
103 sym = ((PackageDocImpl) referencedPackage).sym;
104 } else
105 return;
107 final JavacMessages messages = JavacMessages.instance(docenv().context);
108 Locale locale = Locale.getDefault();
109 Printer printer = new Printer() {
110 int count;
111 @Override
112 protected String localize(Locale locale, String key, Object... args) {
113 return messages.getLocalizedString(locale, key, args);
114 }
115 @Override
116 protected String capturedVarId(CapturedType t, Locale locale) {
117 return "CAP#" + (++count);
118 }
119 };
121 String s = text.replaceAll("\\s+", " "); // normalize white space
122 int sp = s.indexOf(" ");
123 int lparen = s.indexOf("(");
124 int rparen = s.indexOf(")");
125 String seetext = (sp == -1) ? s
126 : (lparen == -1 || sp < lparen) ? s.substring(0, sp)
127 : s.substring(0, rparen + 1);
129 File file = new File(holder.position().file().getAbsoluteFile().toURI().normalize());
131 StringBuilder sb = new StringBuilder();
132 sb.append("+++ ").append(file).append(": ")
133 .append(name()).append(" ").append(seetext).append(": ");
134 sb.append(sym.getKind()).append(" ");
135 if (sym.kind == Kinds.MTH || sym.kind == Kinds.VAR)
136 sb.append(printer.visit(sym.owner, locale)).append(".");
137 sb.append(printer.visit(sym, locale));
139 System.err.println(sb);
140 }
142 /**
143 * get the class name part of @see, For instance,
144 * if the comment is @see String#startsWith(java.lang.String) .
145 * This function returns String.
146 * Returns null if format was not that of java reference.
147 * Return empty string if class name was not specified..
148 */
149 public String referencedClassName() {
150 return where;
151 }
153 /**
154 * get the package referenced by @see. For instance,
155 * if the comment is @see java.lang
156 * This function returns a PackageDocImpl for java.lang
157 * Returns null if no known package found.
158 */
159 public PackageDoc referencedPackage() {
160 return referencedPackage;
161 }
163 /**
164 * get the class referenced by the class name part of @see, For instance,
165 * if the comment is @see String#startsWith(java.lang.String) .
166 * This function returns a ClassDocImpl for java.lang.String.
167 * Returns null if class is not a class specified on the javadoc command line..
168 */
169 public ClassDoc referencedClass() {
170 return referencedClass;
171 }
173 /**
174 * get the name of the member referenced by the prototype part of @see,
175 * For instance,
176 * if the comment is @see String#startsWith(java.lang.String) .
177 * This function returns "startsWith(java.lang.String)"
178 * Returns null if format was not that of java reference.
179 * Return empty string if member name was not specified..
180 */
181 public String referencedMemberName() {
182 return what;
183 }
185 /**
186 * get the member referenced by the prototype part of @see,
187 * For instance,
188 * if the comment is @see String#startsWith(java.lang.String) .
189 * This function returns a MethodDocImpl for startsWith.
190 * Returns null if member could not be determined.
191 */
192 public MemberDoc referencedMember() {
193 return referencedMember;
194 }
197 /**
198 * parse @see part of comment. Determine 'where' and 'what'
199 */
200 private void parseSeeString() {
201 int len = text.length();
202 if (len == 0) {
203 return;
204 }
205 switch (text.charAt(0)) {
206 case '<':
207 if (text.charAt(len-1) != '>') {
208 docenv().warning(holder,
209 "tag.see.no_close_bracket_on_url",
210 name, text);
211 }
212 return;
213 case '"':
214 if (len == 1 || text.charAt(len-1) != '"') {
215 docenv().warning(holder,
216 "tag.see.no_close_quote",
217 name, text);
218 } else {
219 // text = text.substring(1,len-1); // strip quotes
220 }
221 return;
222 }
224 // check that the text is one word, with possible parentheses
225 // this part of code doesn't allow
226 // @see <a href=.....>asfd</a>
227 // comment it.
229 // the code assumes that there is no initial white space.
230 int parens = 0;
231 int commentstart = 0;
232 int start = 0;
233 int cp;
234 for (int i = start; i < len ; i += Character.charCount(cp)) {
235 cp = text.codePointAt(i);
236 switch (cp) {
237 case '(': parens++; break;
238 case ')': parens--; break;
239 case '[': case ']': case '.': case '#': break;
240 case ',':
241 if (parens <= 0) {
242 docenv().warning(holder,
243 "tag.see.malformed_see_tag",
244 name, text);
245 return;
246 }
247 break;
248 case ' ': case '\t': case '\n': case CR:
249 if (parens == 0) { //here onwards the comment starts.
250 commentstart = i;
251 i = len;
252 }
253 break;
254 default:
255 if (!Character.isJavaIdentifierPart(cp)) {
256 docenv().warning(holder,
257 "tag.see.illegal_character",
258 name, ""+cp, text);
259 }
260 break;
261 }
262 }
263 if (parens != 0) {
264 docenv().warning(holder,
265 "tag.see.malformed_see_tag",
266 name, text);
267 return;
268 }
270 String seetext = "";
271 String labeltext = "";
273 if (commentstart > 0) {
274 seetext = text.substring(start, commentstart);
275 labeltext = text.substring(commentstart + 1);
276 // strip off the white space which can be between seetext and the
277 // actual label.
278 for (int i = 0; i < labeltext.length(); i++) {
279 char ch2 = labeltext.charAt(i);
280 if (!(ch2 == ' ' || ch2 == '\t' || ch2 == '\n')) {
281 label = labeltext.substring(i);
282 break;
283 }
284 }
285 } else {
286 seetext = text;
287 label = "";
288 }
290 int sharp = seetext.indexOf('#');
291 if (sharp >= 0) {
292 // class#member
293 where = seetext.substring(0, sharp);
294 what = seetext.substring(sharp + 1);
295 } else {
296 if (seetext.indexOf('(') >= 0) {
297 docenv().warning(holder,
298 "tag.see.missing_sharp",
299 name, text);
300 where = "";
301 what = seetext;
302 }
303 else {
304 // no member specified, text names class
305 where = seetext;
306 what = null;
307 }
308 }
309 }
311 /**
312 * Find what is referenced by the see also. If possible, sets
313 * referencedClass and referencedMember.
314 *
315 * @param containingClass the class containing the comment containing
316 * the tag. May be null, if, for example, it is a package comment.
317 */
318 private void findReferenced(ClassDocImpl containingClass) {
319 if (where.length() > 0) {
320 if (containingClass != null) {
321 referencedClass = containingClass.findClass(where);
322 } else {
323 referencedClass = docenv().lookupClass(where);
324 }
325 if (referencedClass == null && holder() instanceof ProgramElementDoc) {
326 referencedClass = docenv().lookupClass(
327 ((ProgramElementDoc) holder()).containingPackage().name() + "." + where);
328 }
330 if (referencedClass == null) { /* may just not be in this run */
331 // check if it's a package name
332 referencedPackage = docenv().lookupPackage(where);
333 return;
334 }
335 } else {
336 if (containingClass == null) {
337 docenv().warning(holder,
338 "tag.see.class_not_specified",
339 name, text);
340 return;
341 } else {
342 referencedClass = containingClass;
343 }
344 }
345 where = referencedClass.qualifiedName();
347 if (what == null) {
348 return;
349 } else {
350 int paren = what.indexOf('(');
351 String memName = (paren >= 0 ? what.substring(0, paren) : what);
352 String[] paramarr;
353 if (paren > 0) {
354 // has parameter list -- should be method or constructor
355 paramarr = new ParameterParseMachine(what.
356 substring(paren, what.length())).parseParameters();
357 if (paramarr != null) {
358 referencedMember = findExecutableMember(memName, paramarr,
359 referencedClass);
360 } else {
361 referencedMember = null;
362 }
363 } else {
364 // no parameter list -- should be field
365 referencedMember = findExecutableMember(memName, null,
366 referencedClass);
367 FieldDoc fd = ((ClassDocImpl)referencedClass).
368 findField(memName);
369 // when no args given, prefer fields over methods
370 if (referencedMember == null ||
371 (fd != null &&
372 fd.containingClass()
373 .subclassOf(referencedMember.containingClass()))) {
374 referencedMember = fd;
375 }
376 }
377 if (referencedMember == null) {
378 docenv().warning(holder,
379 "tag.see.can_not_find_member",
380 name, what, where);
381 }
382 }
383 }
385 private MemberDoc findReferencedMethod(String memName, String[] paramarr,
386 ClassDoc referencedClass) {
387 MemberDoc meth = findExecutableMember(memName, paramarr, referencedClass);
388 ClassDoc[] nestedclasses = referencedClass.innerClasses();
389 if (meth == null) {
390 for (int i = 0; i < nestedclasses.length; i++) {
391 meth = findReferencedMethod(memName, paramarr, nestedclasses[i]);
392 if (meth != null) {
393 return meth;
394 }
395 }
396 }
397 return null;
398 }
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 }
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;
423 String parameters;
425 StringBuilder typeId;
427 ListBuffer<String> paramList;
429 ParameterParseMachine(String parameters) {
430 this.parameters = parameters;
431 this.paramList = new ListBuffer<String>();
432 typeId = new StringBuilder();
433 }
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 }
520 void addTypeToParamList() {
521 if (typeId.length() > 0) {
522 paramList.append(typeId.toString());
523 typeId.setLength(0);
524 }
525 }
526 }
528 /**
529 * Return the kind of this tag.
530 */
531 @Override
532 public String kind() {
533 return "@see";
534 }
536 /**
537 * Return the label of the see tag.
538 */
539 public String label() {
540 return label;
541 }
542 }