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 
SeeTagImpl(DocImpl holder, String name, String text)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 
showRef()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      */
referencedClassName()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      */
referencedPackage()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      */
referencedClass()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      */
referencedMemberName()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      */
referencedMember()195     public MemberDoc referencedMember() {
196         return referencedMember;
197     }
198 
199 
200     /**
201      * parse @see part of comment. Determine 'where' and 'what'
202      */
parseSeeString()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      */
findReferenced(ClassDocImpl containingClass)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 
findReferencedMethod(String memName, String[] paramarr, ClassDoc referencedClass)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 
findExecutableMember(String memName, String[] paramarr, ClassDoc referencedClass)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 
ParameterParseMachine(String parameters)431         ParameterParseMachine(String parameters) {
432             this.parameters = parameters;
433             this.paramList = new ListBuffer<>();
434             typeId = new StringBuilder();
435         }
436 
parseParameters()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 
addTypeToParamList()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
kind()534     public String kind() {
535         return "@see";
536     }
537 
538     /**
539      * Return the label of the see tag.
540      */
label()541     public String label() {
542         return label;
543     }
544 }
545