langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/Comment.java
changeset 37938 42baa89d2156
parent 37858 7c04fcb12bd4
child 37939 3eb8c2a89b77
equal deleted inserted replaced
37858:7c04fcb12bd4 37938:42baa89d2156
     1 /*
       
     2  * Copyright (c) 1997, 2013, 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.util.regex.Matcher;
       
    29 import java.util.regex.Pattern;
       
    30 import com.sun.javadoc.*;
       
    31 import com.sun.tools.javac.util.ListBuffer;
       
    32 
       
    33 /**
       
    34  * Comment contains all information in comment part.
       
    35  *      It allows users to get first sentence of this comment, get
       
    36  *      comment for different tags...
       
    37  *
       
    38  *  <p><b>This is NOT part of any supported API.
       
    39  *  If you write code that depends on this, you do so at your own risk.
       
    40  *  This code and its internal interfaces are subject to change or
       
    41  *  deletion without notice.</b>
       
    42  *
       
    43  * @author Kaiyang Liu (original)
       
    44  * @author Robert Field (rewrite)
       
    45  * @author Atul M Dambalkar
       
    46  * @author Neal Gafter (rewrite)
       
    47  */
       
    48 class Comment {
       
    49 
       
    50     /**
       
    51      * sorted comments with different tags.
       
    52      */
       
    53     private final ListBuffer<Tag> tagList = new ListBuffer<>();
       
    54 
       
    55     /**
       
    56      * text minus any tags.
       
    57      */
       
    58     private String text;
       
    59 
       
    60     /**
       
    61      * Doc environment
       
    62      */
       
    63     private final DocEnv docenv;
       
    64 
       
    65     /**
       
    66      * constructor of Comment.
       
    67      */
       
    68     Comment(final DocImpl holder, final String commentString) {
       
    69         this.docenv = holder.env;
       
    70 
       
    71         /**
       
    72          * Separate the comment into the text part and zero to N tags.
       
    73          * Simple state machine is in one of three states:
       
    74          * <pre>
       
    75          * IN_TEXT: parsing the comment text or tag text.
       
    76          * TAG_NAME: parsing the name of a tag.
       
    77          * TAG_GAP: skipping through the gap between the tag name and
       
    78          * the tag text.
       
    79          * </pre>
       
    80          */
       
    81         @SuppressWarnings("fallthrough")
       
    82         class CommentStringParser {
       
    83             /**
       
    84              * The entry point to the comment string parser
       
    85              */
       
    86             void parseCommentStateMachine() {
       
    87                 final int IN_TEXT = 1;
       
    88                 final int TAG_GAP = 2;
       
    89                 final int TAG_NAME = 3;
       
    90                 int state = TAG_GAP;
       
    91                 boolean newLine = true;
       
    92                 String tagName = null;
       
    93                 int tagStart = 0;
       
    94                 int textStart = 0;
       
    95                 int lastNonWhite = -1;
       
    96                 int len = commentString.length();
       
    97                 for (int inx = 0; inx < len; ++inx) {
       
    98                     char ch = commentString.charAt(inx);
       
    99                     boolean isWhite = Character.isWhitespace(ch);
       
   100                     switch (state)  {
       
   101                         case TAG_NAME:
       
   102                             if (isWhite) {
       
   103                                 tagName = commentString.substring(tagStart, inx);
       
   104                                 state = TAG_GAP;
       
   105                             }
       
   106                             break;
       
   107                         case TAG_GAP:
       
   108                             if (isWhite) {
       
   109                                 break;
       
   110                             }
       
   111                             textStart = inx;
       
   112                             state = IN_TEXT;
       
   113                             /* fall thru */
       
   114                         case IN_TEXT:
       
   115                             if (newLine && ch == '@') {
       
   116                                 parseCommentComponent(tagName, textStart,
       
   117                                                       lastNonWhite+1);
       
   118                                 tagStart = inx;
       
   119                                 state = TAG_NAME;
       
   120                             }
       
   121                             break;
       
   122                     }
       
   123                     if (ch == '\n') {
       
   124                         newLine = true;
       
   125                     } else if (!isWhite) {
       
   126                         lastNonWhite = inx;
       
   127                         newLine = false;
       
   128                     }
       
   129                 }
       
   130                 // Finish what's currently being processed
       
   131                 switch (state)  {
       
   132                     case TAG_NAME:
       
   133                         tagName = commentString.substring(tagStart, len);
       
   134                         /* fall thru */
       
   135                     case TAG_GAP:
       
   136                         textStart = len;
       
   137                         /* fall thru */
       
   138                     case IN_TEXT:
       
   139                         parseCommentComponent(tagName, textStart, lastNonWhite+1);
       
   140                         break;
       
   141                 }
       
   142             }
       
   143 
       
   144             /**
       
   145              * Save away the last parsed item.
       
   146              */
       
   147             void parseCommentComponent(String tagName,
       
   148                                        int from, int upto) {
       
   149                 String tx = upto <= from ? "" : commentString.substring(from, upto);
       
   150                 if (tagName == null) {
       
   151                     text = tx;
       
   152                 } else {
       
   153                     TagImpl tag;
       
   154                     switch (tagName) {
       
   155                         case "@exception":
       
   156                         case "@throws":
       
   157                             warnIfEmpty(tagName, tx);
       
   158                             tag = new ThrowsTagImpl(holder, tagName, tx);
       
   159                             break;
       
   160                         case "@param":
       
   161                             warnIfEmpty(tagName, tx);
       
   162                             tag = new ParamTagImpl(holder, tagName, tx);
       
   163                             break;
       
   164                         case "@see":
       
   165                             warnIfEmpty(tagName, tx);
       
   166                             tag = new SeeTagImpl(holder, tagName, tx);
       
   167                             break;
       
   168                         case "@serialField":
       
   169                             warnIfEmpty(tagName, tx);
       
   170                             tag = new SerialFieldTagImpl(holder, tagName, tx);
       
   171                             break;
       
   172                         case "@return":
       
   173                             warnIfEmpty(tagName, tx);
       
   174                             tag = new TagImpl(holder, tagName, tx);
       
   175                             break;
       
   176                         case "@author":
       
   177                             warnIfEmpty(tagName, tx);
       
   178                             tag = new TagImpl(holder, tagName, tx);
       
   179                             break;
       
   180                         case "@version":
       
   181                             warnIfEmpty(tagName, tx);
       
   182                             tag = new TagImpl(holder, tagName, tx);
       
   183                             break;
       
   184                         default:
       
   185                             tag = new TagImpl(holder, tagName, tx);
       
   186                             break;
       
   187                     }
       
   188                     tagList.append(tag);
       
   189                 }
       
   190             }
       
   191 
       
   192             void warnIfEmpty(String tagName, String tx) {
       
   193                 if (tx.length() == 0) {
       
   194                     docenv.warning(holder, "tag.tag_has_no_arguments", tagName);
       
   195                 }
       
   196             }
       
   197 
       
   198         }
       
   199 
       
   200         new CommentStringParser().parseCommentStateMachine();
       
   201     }
       
   202 
       
   203     /**
       
   204      * Return the text of the comment.
       
   205      */
       
   206     String commentText() {
       
   207         return text;
       
   208     }
       
   209 
       
   210     /**
       
   211      * Return all tags in this comment.
       
   212      */
       
   213     Tag[] tags() {
       
   214         return tagList.toArray(new Tag[tagList.length()]);
       
   215     }
       
   216 
       
   217     /**
       
   218      * Return tags of the specified kind in this comment.
       
   219      */
       
   220     Tag[] tags(String tagname) {
       
   221         ListBuffer<Tag> found = new ListBuffer<>();
       
   222         String target = tagname;
       
   223         if (target.charAt(0) != '@') {
       
   224             target = "@" + target;
       
   225         }
       
   226         for (Tag tag : tagList) {
       
   227             if (tag.kind().equals(target)) {
       
   228                 found.append(tag);
       
   229             }
       
   230         }
       
   231         return found.toArray(new Tag[found.length()]);
       
   232     }
       
   233 
       
   234     /**
       
   235      * Return throws tags in this comment.
       
   236      */
       
   237     ThrowsTag[] throwsTags() {
       
   238         ListBuffer<ThrowsTag> found = new ListBuffer<>();
       
   239         for (Tag next : tagList) {
       
   240             if (next instanceof ThrowsTag) {
       
   241                 found.append((ThrowsTag)next);
       
   242             }
       
   243         }
       
   244         return found.toArray(new ThrowsTag[found.length()]);
       
   245     }
       
   246 
       
   247     /**
       
   248      * Return param tags (excluding type param tags) in this comment.
       
   249      */
       
   250     ParamTag[] paramTags() {
       
   251         return paramTags(false);
       
   252     }
       
   253 
       
   254     /**
       
   255      * Return type param tags in this comment.
       
   256      */
       
   257     ParamTag[] typeParamTags() {
       
   258         return paramTags(true);
       
   259     }
       
   260 
       
   261     /**
       
   262      * Return param tags in this comment.  If typeParams is true
       
   263      * include only type param tags, otherwise include only ordinary
       
   264      * param tags.
       
   265      */
       
   266     private ParamTag[] paramTags(boolean typeParams) {
       
   267         ListBuffer<ParamTag> found = new ListBuffer<>();
       
   268         for (Tag next : tagList) {
       
   269             if (next instanceof ParamTag) {
       
   270                 ParamTag p = (ParamTag)next;
       
   271                 if (typeParams == p.isTypeParameter()) {
       
   272                     found.append(p);
       
   273                 }
       
   274             }
       
   275         }
       
   276         return found.toArray(new ParamTag[found.length()]);
       
   277     }
       
   278 
       
   279     /**
       
   280      * Return see also tags in this comment.
       
   281      */
       
   282     SeeTag[] seeTags() {
       
   283         ListBuffer<SeeTag> found = new ListBuffer<>();
       
   284         for (Tag next : tagList) {
       
   285             if (next instanceof SeeTag) {
       
   286                 found.append((SeeTag)next);
       
   287             }
       
   288         }
       
   289         return found.toArray(new SeeTag[found.length()]);
       
   290     }
       
   291 
       
   292     /**
       
   293      * Return serialField tags in this comment.
       
   294      */
       
   295     SerialFieldTag[] serialFieldTags() {
       
   296         ListBuffer<SerialFieldTag> found = new ListBuffer<>();
       
   297         for (Tag next : tagList) {
       
   298             if (next instanceof SerialFieldTag) {
       
   299                 found.append((SerialFieldTag)next);
       
   300             }
       
   301         }
       
   302         return found.toArray(new SerialFieldTag[found.length()]);
       
   303     }
       
   304 
       
   305     /**
       
   306      * Return array of tags with text and inline See Tags for a Doc comment.
       
   307      */
       
   308     static Tag[] getInlineTags(DocImpl holder, String inlinetext) {
       
   309         ListBuffer<Tag> taglist = new ListBuffer<>();
       
   310         int delimend = 0, textstart = 0, len = inlinetext.length();
       
   311         boolean inPre = false;
       
   312         DocEnv docenv = holder.env;
       
   313 
       
   314         if (len == 0) {
       
   315             return taglist.toArray(new Tag[taglist.length()]);
       
   316         }
       
   317         while (true) {
       
   318             int linkstart;
       
   319             if ((linkstart = inlineTagFound(holder, inlinetext,
       
   320                                             textstart)) == -1) {
       
   321                 taglist.append(new TagImpl(holder, "Text",
       
   322                                            inlinetext.substring(textstart)));
       
   323                 break;
       
   324             } else {
       
   325                 inPre = scanForPre(inlinetext, textstart, linkstart, inPre);
       
   326                 int seetextstart = linkstart;
       
   327                 for (int i = linkstart; i < inlinetext.length(); i++) {
       
   328                     char c = inlinetext.charAt(i);
       
   329                     if (Character.isWhitespace(c) ||
       
   330                         c == '}') {
       
   331                         seetextstart = i;
       
   332                         break;
       
   333                      }
       
   334                 }
       
   335                 String linkName = inlinetext.substring(linkstart+2, seetextstart);
       
   336                 if (!(inPre && (linkName.equals("code") || linkName.equals("literal")))) {
       
   337                     //Move past the white space after the inline tag name.
       
   338                     while (Character.isWhitespace(inlinetext.
       
   339                                                       charAt(seetextstart))) {
       
   340                         if (inlinetext.length() <= seetextstart) {
       
   341                             taglist.append(new TagImpl(holder, "Text",
       
   342                                                        inlinetext.substring(textstart, seetextstart)));
       
   343                             docenv.warning(holder,
       
   344                                            "tag.Improper_Use_Of_Link_Tag",
       
   345                                            inlinetext);
       
   346                             return taglist.toArray(new Tag[taglist.length()]);
       
   347                         } else {
       
   348                             seetextstart++;
       
   349                         }
       
   350                     }
       
   351                 }
       
   352                 taglist.append(new TagImpl(holder, "Text",
       
   353                                            inlinetext.substring(textstart, linkstart)));
       
   354                 textstart = seetextstart;   // this text is actually seetag
       
   355                 if ((delimend = findInlineTagDelim(inlinetext, textstart)) == -1) {
       
   356                     //Missing closing '}' character.
       
   357                     // store the text as it is with the {@link.
       
   358                     taglist.append(new TagImpl(holder, "Text",
       
   359                                                inlinetext.substring(textstart)));
       
   360                     docenv.warning(holder,
       
   361                                    "tag.End_delimiter_missing_for_possible_SeeTag",
       
   362                                    inlinetext);
       
   363                     return taglist.toArray(new Tag[taglist.length()]);
       
   364                 } else {
       
   365                     //Found closing '}' character.
       
   366                     if (linkName.equals("see")
       
   367                            || linkName.equals("link")
       
   368                            || linkName.equals("linkplain")) {
       
   369                         taglist.append( new SeeTagImpl(holder, "@" + linkName,
       
   370                               inlinetext.substring(textstart, delimend)));
       
   371                     } else {
       
   372                         taglist.append( new TagImpl(holder, "@" + linkName,
       
   373                               inlinetext.substring(textstart, delimend)));
       
   374                     }
       
   375                     textstart = delimend + 1;
       
   376                 }
       
   377             }
       
   378             if (textstart == inlinetext.length()) {
       
   379                 break;
       
   380             }
       
   381         }
       
   382         return taglist.toArray(new Tag[taglist.length()]);
       
   383     }
       
   384 
       
   385     /** regex for case-insensitive match for {@literal <pre> } and  {@literal </pre> }. */
       
   386     private static final Pattern prePat = Pattern.compile("(?i)<(/?)pre>");
       
   387 
       
   388     private static boolean scanForPre(String inlinetext, int start, int end, boolean inPre) {
       
   389         Matcher m = prePat.matcher(inlinetext).region(start, end);
       
   390         while (m.find()) {
       
   391             inPre = m.group(1).isEmpty();
       
   392         }
       
   393         return inPre;
       
   394     }
       
   395 
       
   396     /**
       
   397      * Recursively find the index of the closing '}' character for an inline tag
       
   398      * and return it.  If it can't be found, return -1.
       
   399      * @param inlineText the text to search in.
       
   400      * @param searchStart the index of the place to start searching at.
       
   401      * @return the index of the closing '}' character for an inline tag.
       
   402      * If it can't be found, return -1.
       
   403      */
       
   404     private static int findInlineTagDelim(String inlineText, int searchStart) {
       
   405         int delimEnd, nestedOpenBrace;
       
   406         if ((delimEnd = inlineText.indexOf("}", searchStart)) == -1) {
       
   407             return -1;
       
   408         } else if (((nestedOpenBrace = inlineText.indexOf("{", searchStart)) != -1) &&
       
   409             nestedOpenBrace < delimEnd){
       
   410             //Found a nested open brace.
       
   411             int nestedCloseBrace = findInlineTagDelim(inlineText, nestedOpenBrace + 1);
       
   412             return (nestedCloseBrace != -1) ?
       
   413                 findInlineTagDelim(inlineText, nestedCloseBrace + 1) :
       
   414                 -1;
       
   415         } else {
       
   416             return delimEnd;
       
   417         }
       
   418     }
       
   419 
       
   420     /**
       
   421      * Recursively search for the characters '{', '@', followed by
       
   422      * name of inline tag and white space,
       
   423      * if found
       
   424      *    return the index of the text following the white space.
       
   425      * else
       
   426      *    return -1.
       
   427      */
       
   428     private static int inlineTagFound(DocImpl holder, String inlinetext, int start) {
       
   429         DocEnv docenv = holder.env;
       
   430         int linkstart = inlinetext.indexOf("{@", start);
       
   431         if (start == inlinetext.length() || linkstart == -1) {
       
   432             return -1;
       
   433         } else if (inlinetext.indexOf('}', linkstart) == -1) {
       
   434             //Missing '}'.
       
   435             docenv.warning(holder, "tag.Improper_Use_Of_Link_Tag",
       
   436                     inlinetext.substring(linkstart, inlinetext.length()));
       
   437             return -1;
       
   438         } else {
       
   439             return linkstart;
       
   440         }
       
   441     }
       
   442 
       
   443 
       
   444     /**
       
   445      * Return array of tags for the locale specific first sentence in the text.
       
   446      */
       
   447     static Tag[] firstSentenceTags(DocImpl holder, String text) {
       
   448         DocLocale doclocale = holder.env.doclocale;
       
   449         return getInlineTags(holder,
       
   450                              doclocale.localeSpecificFirstSentence(holder, text));
       
   451     }
       
   452 
       
   453     /**
       
   454      * Return text for this Doc comment.
       
   455      */
       
   456     @Override
       
   457     public String toString() {
       
   458         return text;
       
   459     }
       
   460 }