langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/SeeTagImpl.java
changeset 37938 42baa89d2156
parent 27224 228abfa87080
child 38617 d93a7f64e231
equal deleted inserted replaced
37858:7c04fcb12bd4 37938:42baa89d2156
       
     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.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 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 }