1 /*
   2  * Copyright (c) 2015, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package sampleapi.generator;
  25 
  26 import java.io.File;
  27 import java.io.BufferedInputStream;
  28 import java.io.IOException;
  29 import java.util.Set;
  30 import javax.lang.model.element.Modifier;
  31 
  32 import com.sun.tools.javac.tree.JCTree;
  33 import com.sun.tools.javac.tree.JCTree.*;
  34 import com.sun.tools.javac.util.List;
  35 
  36 class DocCommentGenerator {
  37 
  38     public enum Text {
  39         BROWNFOX(BROWNFOX_TEXT),
  40         NOWISTHETIME(NOWISTHETIME_TEXT),
  41         THISPANGRAM(THISPANGRAM_TEXT),
  42         LOREMIPSUM(LOREMIPSUM_TEXT),
  43         LIEUROPANLINGUES(LIEUROPANLINGUES_TEXT),
  44         CODE(CODE_TEXT);
  45 
  46         String commentText;
  47 
  48         Text(String text) {
  49             commentText = text;
  50         }
  51 
  52         @Override
  53         public String toString() {
  54             return commentText;
  55         }
  56     }
  57 
  58     public enum Tag {
  59         AUTHOR("@author", "Cody J. Writer"),
  60         PARAM("@param", ""),
  61         RETURN("@return", Text.NOWISTHETIME.toString()),
  62         SINCE("@since", "1.0"),
  63         THROWS("@throws", "If problem detected"),
  64         EXCEPTION("@exception", "If problem detected"),
  65         SERIAL("@serial", ""),
  66         SERIALDATA("@serialData", "The types and order of data could be here."),
  67         SERIALFIELD("@serialField", "\n        Serial field in special array"),
  68         FX_PROPSETTER("@propertySetter", "Set the property"),
  69         FX_PROPGETTER("@propertyGetter", "Get the property"),
  70         FX_PROPDESC("@propertyDescription", ""),
  71         FX_DEFVALUE("@defaultValue", ""),
  72         FX_TREATASPRIVATE("@treatAsPrivate", "");
  73 
  74         String tagName;
  75         String tagValue;
  76 
  77         Tag(String tagName, String tagValue) {
  78             this.tagName = tagName;
  79             this.tagValue = tagValue;
  80         }
  81 
  82         public String toString() {
  83             return value("", "");
  84         }
  85 
  86         public String value(String value) {
  87             return value(value, "");
  88         }
  89 
  90         public String value(String value, String extra) {
  91             return tagName
  92                    + ((value.length() != 0) ? " " + value : "")
  93                    + ((tagValue.length() != 0) ? " " + tagValue : "")
  94                    + ((extra.length() != 0) ? " " + extra : "");
  95         }
  96     }
  97 
  98     public enum InlineTag {
  99         LITERAL("@literal", "Use < and > brackets instead of &lt; and &gt; escapes."),
 100         CODE("@code", "(i) -> new Abc<Object>((i > 0) ? (i << 1) : 0)"),
 101         LINK("@link", ""),
 102         VALUE("@value", "");
 103 
 104         String tagName;
 105         String tagValue;
 106 
 107         InlineTag(String tagName, String tagValue) {
 108             this.tagName = tagName;
 109             this.tagValue = tagValue;
 110         }
 111 
 112         public String toString() {
 113             return value("");
 114         }
 115 
 116         public String value(String value) {
 117             return "{" + tagName
 118                    + ((tagValue.length() != 0) ? " " + tagValue : "")
 119                    + ((value.length() != 0) ? " " + value : "")
 120                    + "}";
 121         }
 122     }
 123 
 124     public static class LinkTag {
 125 
 126         static String[] links = new String[] {
 127             "Boolean",
 128             "Boolean#TRUE",
 129             "Boolean#Boolean(boolean)",
 130             "Boolean#Boolean(String s)",
 131             "Boolean#compare(boolean, boolean)",
 132             "Boolean#compareTo(Boolean b)",
 133             "java.util.Vector",
 134             "java.util.Vector#elementCount",
 135             "java.util.Vector#Vector(int)",
 136             "java.util.Vector#Vector(int initialCapacity, int capacityIncrement)",
 137             "java.util.Vector#indexOf(Object)",
 138             "java.util.Vector#indexOf(Object o, int index)",
 139             "java.lang.annotation" };
 140 
 141         static int index = 0;
 142 
 143         public static String nextSee() {
 144             String next = "@see " + links[index];
 145             index = (index + 1) % links.length;
 146             return next;
 147         }
 148 
 149         public static String nextLink() {
 150             String next = "Also check "
 151                           + (((index % 2) == 0) ? "{@link " : "{@linkplain ")
 152                           + links[index]
 153                           + "} for details.\n";
 154             index = (index + 1) % links.length;
 155             return next;
 156         }
 157     }
 158 
 159     public static class VersionTag {
 160 
 161         static String[] versions = new String[] {
 162             "1.5, 09/01/04",
 163             "1.6, 12/11/06",
 164             "1.7, 07/28/11",
 165             "1.8, 04/19/14",
 166             "9,   06/03/16" };
 167 
 168         static int index = 0;
 169 
 170         public static String nextVersion() {
 171             String next = "@version " + versions[index];
 172             index = (index + 1) % versions.length;
 173             return next;
 174         }
 175     }
 176 
 177     //
 178     // getters (build comments for entities)
 179     //
 180 
 181     public String getPackageComment() {
 182         return Text.LOREMIPSUM
 183                + "\n <p>" + Text.LIEUROPANLINGUES
 184                + "\n" + Text.CODE
 185                + "\n" + LinkTag.nextLink()
 186                + "\n" + LinkTag.nextSee()
 187                + "\n" + Tag.SINCE;
 188     }
 189 
 190     String[] serialVals = new String[] { "include", "exclude" };
 191     // static index to roll over "include/exclude"
 192     static int serialValIdx = 0;
 193 
 194     public String getBaseComment(JCClassDecl baseDecl, boolean toplevel) {
 195         String buildComment = Text.LIEUROPANLINGUES + "\n";
 196 
 197         buildComment += "<p>It is possible to see inlined code:\n"
 198                         + InlineTag.CODE
 199                         + " is the example.\n\n";
 200 
 201         buildComment += "<p>Literal use example.\n"
 202                         + InlineTag.LITERAL + "\n\n";
 203 
 204         buildComment += "<p>" + Text.THISPANGRAM + "\n"; // make comment longer
 205 
 206         buildComment += "<p>" + LinkTag.nextLink() + "\n";
 207 
 208         if (toplevel)
 209             buildComment += "\n" + VersionTag.nextVersion() + "\n";
 210 
 211         // @param for type params
 212         List<JCTypeParameter> params = baseDecl.getTypeParameters();
 213         int paramIndex = 0;
 214         for (JCTypeParameter paramDecl : params) {
 215             buildComment += Tag.PARAM.value(
 216                                 "<" + paramDecl.getName().toString() + ">",
 217                                 "the type of value set #" + paramIndex++)
 218                             + "\n";
 219         }
 220 
 221         buildComment += "\n" + LinkTag.nextSee();
 222 
 223         buildComment += "\n";
 224 
 225         if (toplevel)
 226             buildComment += Tag.AUTHOR + "\n";
 227 
 228         buildComment += Tag.SINCE + "\n";
 229 
 230         // for serial; currently no need to dig too deep
 231         if (isSerializable(baseDecl) || isErrorOrException(baseDecl)) {
 232             buildComment += "\n" + Tag.SERIAL.value(serialVals[serialValIdx]);
 233             serialValIdx = (serialValIdx + 1) % 2;
 234         }
 235 
 236         return buildComment;
 237     }
 238 
 239     public String getConstComment() {
 240         String buildComment = Text.NOWISTHETIME + " " + Text.BROWNFOX + "\n";
 241 
 242         buildComment += LinkTag.nextLink() + "\n";
 243         buildComment += LinkTag.nextSee() + "\n";
 244         buildComment += Tag.SINCE + "\n";
 245 
 246         return buildComment;
 247     }
 248 
 249     public String getFieldComment(JCClassDecl baseDecl,
 250                                   JCVariableDecl varDecl,
 251                                   boolean isFxStyle) {
 252         String buildComment = Text.BROWNFOX + "<p>" + Text.NOWISTHETIME + "\n";
 253 
 254         Set<Modifier> mods = varDecl.getModifiers().getFlags();
 255         String varName = varDecl.getName().toString();
 256 
 257         if (mods.contains(Modifier.STATIC) && mods.contains(Modifier.FINAL)) {
 258             JCExpression init = varDecl.getInitializer();
 259             if (init != null
 260                 && !"null".equals(init.toString())
 261                 && !"serialPersistentFields".equals(varName))
 262                 buildComment += "<p>The value of this constant is "
 263                                 + InlineTag.VALUE
 264                                 + ".\n";
 265         }
 266 
 267         buildComment += "<p>" + LinkTag.nextLink() + "\n";
 268 
 269         if (isSerializable(baseDecl)) {
 270             if (isErrorOrException(baseDecl)) {
 271                 buildComment += Tag.SERIAL + "\n";
 272             } else if ("serialPersistentFields".equals(varName)) {
 273                 JCNewArray sfList = (JCNewArray)varDecl.getInitializer();
 274                 for (JCExpression sf : sfList.getInitializers()) {
 275                     List<JCExpression> args = ((JCNewClass)sf).getArguments();
 276                     String sfName = ((JCLiteral)args.get(0)).getValue().toString();
 277                     String sfClass = ((JCIdent)args.get(1)).getName().toString();
 278                     String sfType = sfClass.substring(0, sfClass.indexOf(".class"));
 279 
 280                     buildComment += Tag.SERIALFIELD.value(sfName + "    " + sfType)
 281                                     + "\n";
 282                 }
 283             } else {
 284                 buildComment += Tag.SERIAL.value("Very important value.") + "\n";
 285             }
 286         }
 287 
 288         if (isFxStyle) {
 289             // set default value
 290             String varType = varDecl.getType().toString();
 291             buildComment += Tag.FX_DEFVALUE.value(defValue(varType)) + "\n";
 292         }
 293 
 294         buildComment += LinkTag.nextSee() + "\n";
 295 
 296         return buildComment;
 297     }
 298 
 299     public String getMethodComment(JCClassDecl baseDecl,
 300                                    JCMethodDecl methodDecl,
 301                                    boolean isFxStyle) {
 302         String buildComment = Text.BROWNFOX + "\n<p>" + Text.THISPANGRAM + "\n";
 303 
 304         buildComment += "<p>" + LinkTag.nextLink() + "\n";
 305 
 306         buildComment += "<p>Literal use example.\n"
 307                         + InlineTag.LITERAL + "\n\n";
 308 
 309         // @param for type params
 310         List<JCTypeParameter> tparams = methodDecl.getTypeParameters();
 311         int tparamIndex = 0;
 312         for (JCTypeParameter paramDecl : tparams) {
 313             String paramDeclString = paramDecl.getName().toString();
 314             // simplify it (could contain 'extend'/'super' clauses
 315             int spacePos = paramDeclString.indexOf(' ');
 316             if (spacePos != -1)
 317                 paramDeclString = paramDeclString.substring(0, spacePos);
 318             buildComment += Tag.PARAM.value(
 319                                 "<" + paramDeclString + ">",
 320                                 "the type of value set #" + tparamIndex++)
 321                             + "\n";
 322         }
 323 
 324         // @param
 325         List<JCVariableDecl> params =  methodDecl.getParameters();
 326         int paramIndex = 0;
 327         for (JCVariableDecl paramDecl : params) {
 328             buildComment += Tag.PARAM.value(
 329                                 paramDecl.getName().toString(),
 330                                 "an income parameter #" + paramIndex++)
 331                             + "\n";
 332         }
 333 
 334         // @return
 335         JCTree retType = methodDecl.getReturnType(); // null for constructors
 336         if (retType != null && !"void".equals(retType.toString()))
 337             buildComment += Tag.RETURN + "\n";
 338 
 339         // @throws/@exception
 340         Tag t = isDerived(baseDecl) ? Tag.EXCEPTION : Tag.THROWS;
 341         List<JCExpression> throwTypes = methodDecl.getThrows();
 342         for (JCExpression throwsExp : throwTypes) {
 343             buildComment += t.value(throwsExp.toString()) + "\n";
 344         }
 345 
 346         if (isSerializable(baseDecl)) {
 347             switch (methodDecl.getName().toString()) {
 348                 case "writeObject":
 349                 case "readObject":
 350                 case "writeExternal":
 351                 case "readExternal":
 352                 case "writeReplace":
 353                 case "readResolve":
 354                     buildComment += Tag.SERIALDATA + "\n";
 355                     break;
 356                 default:
 357             }
 358         }
 359 
 360         if (isFxStyle) {
 361             // @propertySetter/Getter + Description
 362             if ("void".equals(retType.toString())) {
 363                 buildComment += Tag.FX_PROPSETTER + "\n";
 364             } else {
 365                 buildComment += Tag.FX_PROPGETTER + "\n";
 366                 buildComment += Tag.FX_DEFVALUE.value(defValue(retType.toString()))
 367                                 + "\n";
 368             }
 369             buildComment += Tag.FX_PROPDESC.value(Text.BROWNFOX.toString()) + "\n";
 370 
 371             // @treatAsPrivate
 372             if (methodDecl.getModifiers().getFlags().contains(Modifier.PUBLIC))
 373                 buildComment += Tag.FX_TREATASPRIVATE + "\n";
 374         }
 375 
 376         // @see
 377         buildComment += LinkTag.nextSee() + "\n";
 378 
 379         // @since
 380         buildComment += Tag.SINCE + "\n";
 381 
 382         return buildComment;
 383     }
 384 
 385     //
 386     // util methods
 387     //
 388 
 389     private boolean isErrorOrException(JCClassDecl baseDecl) {
 390         JCExpression ext = baseDecl.getExtendsClause();
 391         if (ext != null) {
 392             String extClassName = ext.toString();
 393             if (extClassName.contains("Error") || extClassName.contains("Exception"))
 394                 return true;
 395         }
 396         return false;
 397     }
 398 
 399     private boolean isSerializable(JCClassDecl baseDecl) {
 400         List<JCExpression> impls = baseDecl.getImplementsClause();
 401         for (JCExpression impl : impls) {
 402             if (impl.toString().contains("Serializable"))
 403                 return true;
 404         }
 405         return false;
 406     }
 407 
 408     private boolean isDerived(JCClassDecl baseDecl) {
 409         return (baseDecl.getExtendsClause() == null) ? false : true;
 410     }
 411 
 412     private String defValue(String type) {
 413         switch (type) {
 414             case "boolean":
 415                 return "true";
 416             case "byte": case "char": case "int": case "long":
 417             case "Integer": case "Long":
 418                 return "1";
 419             case "float": case "double": case "Float": case "Double":
 420                 return "1.0";
 421             case "String":
 422                 return "string";
 423             default:
 424                 return "null";
 425         }
 426     }
 427 
 428     private static final String BROWNFOX_TEXT =
 429         "The quick brown fox jumps over the lazy dog.\n";
 430     private static final String NOWISTHETIME_TEXT =
 431         "Now is the time for all good men to come to the aid of the party.\n";
 432     private static final String THISPANGRAM_TEXT =
 433         "This pangram contains four a's, one b, two c's, one d, thirty e's,\n" +
 434         "six f's, five g's, seven h's, eleven i's, one j, one k, two l's,\n" +
 435         "two m's, eighteen n's, fifteen o's, two p's, one q, five r's,\n" +
 436         "twenty-seven s's, eighteen t's, two u's, seven v's, eight w's,\n" +
 437         "two x's, three y's, &amp; one z.";
 438     private static final String LOREMIPSUM_TEXT =
 439         "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do\n" +
 440         "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut\n" +
 441         "enim ad minim veniam, quis nostrud exercitation ullamco laboris\n" +
 442         "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor\n" +
 443         "in reprehenderit in voluptate velit esse cillum dolore eu fugiat\n" +
 444         "nulla pariatur. Excepteur sint occaecat cupidatat non proident,\n" +
 445         "sunt in culpa qui officia deserunt mollit anim id est laborum.\n";
 446     private static final String LIEUROPANLINGUES_TEXT =
 447         "Li Europan lingues es membres del sam familie. Lor separat existentie\n" +
 448         "es un myth. Por scientie, musica, sport etc, litot Europa usa li sam\n" +
 449         "vocabular. Li lingues differe solmen in li grammatica, li pronunciation\n" +
 450         "e li plu commun vocabules. Omnicos directe al desirabilite de un nov\n" +
 451         "lingua franca: On refusa continuar payar custosi traductores.\n" +
 452         "\n" +
 453         "<p>At solmen va esser necessi far uniform grammatica, pronunciation\n" +
 454         "e plu commun paroles. Ma quande lingues coalesce, li grammatica del\n" +
 455         "resultant lingue es plu simplic e regulari quam ti del coalescent\n" +
 456         "lingues. Li nov lingua franca va esser plu simplic e regulari quam\n" +
 457         "li existent Europan lingues. It va esser tam simplic quam Occidental\n" +
 458         "in fact, it va esser Occidental. A un Angleso it va semblar un simplificat\n" +
 459         "Angles, quam un skeptic Cambridge amico dit me que Occidental es.\n";
 460     private static final String CODE_TEXT =
 461         "<pre>\n" +
 462         "public void checkLanguage(Language lang) throws Exception {\n" +
 463         "    if (lang.getName().equals(\"Java\")) {\n" +
 464         "        System.out.println(\"Warning! Hot!\");\n" +
 465         "    else {\n" +
 466         "        throw new LooserException();\n" +
 467         "    }\n" +
 468         "}\n" +
 469         "</pre>\n";
 470 }