test/langtools/tools/lib/builder/ClassBuilder.java
changeset 49879 601277b1d582
child 52377 907fdbbdf584
equal deleted inserted replaced
49878:2422d4e027b0 49879:601277b1d582
       
     1 /*
       
     2  * Copyright (c) 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 builder;
       
    27 
       
    28 import toolbox.ToolBox;
       
    29 
       
    30 import java.io.IOException;
       
    31 import java.nio.file.Files;
       
    32 import java.nio.file.Path;
       
    33 import java.nio.file.Paths;
       
    34 import java.util.ArrayList;
       
    35 import java.util.List;
       
    36 import java.util.ListIterator;
       
    37 import java.util.regex.Matcher;
       
    38 import java.util.regex.Pattern;
       
    39 import java.util.stream.Collectors;
       
    40 import java.util.stream.Stream;
       
    41 
       
    42 /**
       
    43  * Builder for type declarations.
       
    44  * Note: this implementation does not support everything and is not
       
    45  * exhaustive.
       
    46  */
       
    47 public class ClassBuilder extends AbstractBuilder {
       
    48 
       
    49     private final ToolBox tb;
       
    50     private final String fqn;
       
    51     private final String clsname;
       
    52     private final String typeParameter;
       
    53 
       
    54     private String pkg;
       
    55     private final List<String> imports;
       
    56 
       
    57     private String extendsType;
       
    58     private final List<String> implementsTypes;
       
    59     private final List<MemberBuilder> members;
       
    60     private final List<ClassBuilder> inners;
       
    61     private final List<ClassBuilder> nested;
       
    62 
       
    63 
       
    64     final static Pattern CLASS_RE = Pattern.compile("(.*)(<.*>)");
       
    65 
       
    66     /**
       
    67      * Creates a class builder.
       
    68      * @param tb the toolbox reference
       
    69      * @param name the name of the type
       
    70      */
       
    71     public ClassBuilder(ToolBox tb, String name) {
       
    72         super(new Modifiers(), name);
       
    73         this.tb = tb;
       
    74 
       
    75         Matcher m = CLASS_RE.matcher(name);
       
    76         if (m.matches()) {
       
    77             fqn = m.group(1);
       
    78             typeParameter = m.group(2);
       
    79         } else {
       
    80             fqn = name;
       
    81             typeParameter = null;
       
    82         }
       
    83         if (fqn.contains(".")) {
       
    84             this.pkg = name.substring(0, fqn.lastIndexOf('.'));
       
    85             clsname = fqn.substring(fqn.lastIndexOf('.') + 1);
       
    86         } else {
       
    87             clsname = fqn;
       
    88         }
       
    89         imports = new ArrayList<>();
       
    90         implementsTypes = new ArrayList<>();
       
    91         members = new ArrayList<>();
       
    92         nested = new ArrayList<>();
       
    93         inners = new ArrayList<>();
       
    94     }
       
    95 
       
    96     /**
       
    97      * Adds an import(s).
       
    98      * @param i the import type.
       
    99      * @return this builder.
       
   100      */
       
   101     public ClassBuilder addImports(String i) {
       
   102         imports.add(i);
       
   103         return this;
       
   104     }
       
   105 
       
   106     /**
       
   107      * Sets the modifiers for this builder.
       
   108      * @param modifiers the modifiers
       
   109      * @return this builder
       
   110      */
       
   111     public ClassBuilder setModifiers(String... modifiers) {
       
   112         this.modifiers.setModifiers(modifiers);
       
   113         return this;
       
   114     }
       
   115 
       
   116     /**
       
   117      * Sets the enclosing type's name.
       
   118      *
       
   119      * @param className the enclosing type's name
       
   120      */
       
   121     @Override
       
   122     void setClassName(String className) {
       
   123         cname = className;
       
   124     }
       
   125 
       
   126     /**
       
   127      * Sets a comment for the element.
       
   128      *
       
   129      * @param comments for the element
       
   130      * @return this builder.
       
   131      */
       
   132     @Override
       
   133     public ClassBuilder setComments(String... comments) {
       
   134         super.setComments(comments);
       
   135         return this;
       
   136     }
       
   137 
       
   138     /**
       
   139      * Sets a comment for the element. Typically used to set
       
   140      * the user's preferences whether an automatic comment is
       
   141      * required or no API comment.
       
   142      *
       
   143      * @param kind of comment, automatic or no comment.
       
   144      * @return this builder.
       
   145      */
       
   146     @Override
       
   147     public ClassBuilder setComments(Comment.Kind kind) {
       
   148         super.setComments(kind);
       
   149         return this;
       
   150     }
       
   151 
       
   152     /**
       
   153      * Set the super-type of the type.
       
   154      * @param name of the super type.
       
   155      * @return this builder.
       
   156      */
       
   157     public ClassBuilder setExtends(String name) {
       
   158         extendsType = name;
       
   159         return this;
       
   160     }
       
   161 
       
   162     /**
       
   163      * Adds an implements declaration(s).
       
   164      * @param names the interfaces
       
   165      * @return this builder.
       
   166      */
       
   167     public ClassBuilder addImplements(String... names) {
       
   168         implementsTypes.addAll(List.of(names));
       
   169         return this;
       
   170     }
       
   171 
       
   172     /**
       
   173      * Adds a member(s) to the class declaration.
       
   174      * @param mbs the member builder(s) representing member(s).
       
   175      * @return this builder
       
   176      */
       
   177     public ClassBuilder addMembers(MemberBuilder... mbs) {
       
   178         for (MemberBuilder mb : mbs) {
       
   179             members.add(mb);
       
   180             mb.setClassName(fqn);
       
   181         }
       
   182         return this;
       
   183     }
       
   184 
       
   185     /**
       
   186      * Adds nested-classes, to an outer class to an outer builder.
       
   187      * @param cbs class builder(s) of the nested classes.
       
   188      * @return this builder.
       
   189      */
       
   190     public ClassBuilder addNestedClasses(ClassBuilder... cbs) {
       
   191         Stream.of(cbs).forEach(cb -> {
       
   192             nested.add(cb);
       
   193             cb.setClassName(fqn);
       
   194         });
       
   195         return this;
       
   196     }
       
   197 
       
   198     /**
       
   199      * Adds inner-classes, to an an outer class builder.
       
   200      * @param cbs class builder(s) of the inner classes.
       
   201      * @return this builder.
       
   202      */
       
   203     public ClassBuilder addInnerClasses(ClassBuilder... cbs) {
       
   204         Stream.of(cbs).forEach(cb -> {
       
   205             inners.add(cb);
       
   206             cb.setClassName(fqn);
       
   207         });
       
   208         return this;
       
   209     }
       
   210 
       
   211     @Override
       
   212     public String toString() {
       
   213         OutputWriter ow = new OutputWriter();
       
   214         if (pkg != null)
       
   215             ow.println("package " + pkg + ";");
       
   216         imports.forEach(i -> ow.println("import " + i + ";"));
       
   217         switch (comments.kind) {
       
   218             case AUTO:
       
   219                 ow.println("/** Class " + fqn + " */");
       
   220                 break;
       
   221             case USER:
       
   222                 ow.println("/** ");
       
   223                 comments.comments.forEach(c -> ow.println(" * " + c));
       
   224                 ow.println(" */");
       
   225                 break;
       
   226             case NO_API_COMMENT:
       
   227                 ow.println("// NO_API_COMMENT");
       
   228                 break;
       
   229         }
       
   230         ow.print(modifiers.toString());
       
   231         ow.print(clsname);
       
   232         if (typeParameter != null) {
       
   233             ow.print(typeParameter + " ");
       
   234         } else {
       
   235             ow.print(" ");
       
   236         }
       
   237         if (extendsType != null && !extendsType.isEmpty()) {
       
   238             ow.print("extends " + extendsType + " ");
       
   239         }
       
   240         if (!implementsTypes.isEmpty()) {
       
   241             ow.print("implements ");
       
   242 
       
   243             ListIterator<String> iter = implementsTypes.listIterator();
       
   244             while (iter.hasNext()) {
       
   245                 String s = iter.next() ;
       
   246                 ow.print(s);
       
   247                 if (iter.hasNext())
       
   248                     ow.print(", ");
       
   249             }
       
   250         }
       
   251         ow.print("{");
       
   252         if (!nested.isEmpty()) {
       
   253             ow.println("");
       
   254             nested.forEach(m -> ow.println(m.toString()));
       
   255         }
       
   256 
       
   257         if (!members.isEmpty()) {
       
   258             ow.println("");
       
   259             members.forEach(m -> ow.println(m.toString()));
       
   260         }
       
   261 
       
   262         ow.println("}");
       
   263         if (!inners.isEmpty()) {
       
   264             ow.println(" {");
       
   265             inners.forEach(m -> ow.println(m.toString()));
       
   266             ow.println("}");
       
   267         }
       
   268         return ow.toString();
       
   269     }
       
   270 
       
   271     /**
       
   272      * Writes out the java source for a type element. Package directories
       
   273      * will be created as needed as inferred by the type name.
       
   274      * @param srcDir the top level source directory.
       
   275      * @throws IOException if an error occurs.
       
   276      */
       
   277     public void write(Path srcDir) throws IOException {
       
   278         Files.createDirectories(srcDir);
       
   279         Path outDir = srcDir;
       
   280         if (pkg != null && !pkg.isEmpty()) {
       
   281             String pdir = pkg.replace(".", "/");
       
   282             outDir = Paths.get(srcDir.toString(), pdir);
       
   283             Files.createDirectories(outDir);
       
   284         }
       
   285         Path filePath = Paths.get(outDir.toString(), clsname + ".java");
       
   286         tb.writeFile(filePath, this.toString());
       
   287     }
       
   288 
       
   289     /**
       
   290      * The member builder, this is the base class for all types of members.
       
   291      */
       
   292     public static abstract class MemberBuilder extends AbstractBuilder {
       
   293         public MemberBuilder(Modifiers modifiers, String name) {
       
   294             super(modifiers, name);
       
   295         }
       
   296 
       
   297         /**
       
   298          * Sets the enclosing type's name.
       
   299          * @param className the enclosing type's name
       
   300          */
       
   301         @Override
       
   302         void setClassName(String className) {
       
   303             cname = className;
       
   304         }
       
   305 
       
   306         /**
       
   307          * Sets a comment for the element.
       
   308          *
       
   309          * @param comments for any element
       
   310          * @return this builder.
       
   311          */
       
   312         @Override
       
   313         public MemberBuilder setComments(String... comments) {
       
   314             super.setComments(comments);
       
   315             return this;
       
   316         }
       
   317 
       
   318         /**
       
   319          * Sets a comment for the element. Typically used to set user's
       
   320          * preferences whether an automatic comment is required or no API
       
   321          * comment.
       
   322          *
       
   323          * @param kind of comment, automatic or no comment.
       
   324          * @return this builder.
       
   325          */
       
   326         @Override
       
   327         public MemberBuilder setComments(Comment.Kind kind) {
       
   328             super.setComments(kind);
       
   329             return this;
       
   330         }
       
   331 
       
   332         /**
       
   333          * Sets a new modifier.
       
   334          *
       
   335          * @param modifiers
       
   336          * @return this builder.
       
   337          */
       
   338         @Override
       
   339         public MemberBuilder setModifiers(String... modifiers) {
       
   340             super.setModifiers(modifiers);
       
   341             return this;
       
   342         }
       
   343     }
       
   344 
       
   345     /**
       
   346      * The field builder.
       
   347      */
       
   348     public static class FieldBuilder extends MemberBuilder {
       
   349         private String fieldType;
       
   350         private String value;
       
   351 
       
   352         private static final Pattern FIELD_RE = Pattern.compile("(.*)(\\s*=\\s*)(.*)(;)");
       
   353 
       
   354         /**
       
   355          * Constructs a field with the modifiers and name of the field.
       
   356          * The builder by default is configured to auto generate the
       
   357          * comments for the field.
       
   358          * @param name of the field
       
   359          */
       
   360         public FieldBuilder(String name) {
       
   361             super(new Modifiers(), name);
       
   362             this.comments = new Comment(Comment.Kind.AUTO);
       
   363         }
       
   364 
       
   365         /**
       
   366          * Returns a field builder by parsing the string.
       
   367          * ex:  public static String myPlayingField;
       
   368          * @param fieldString describing the field.
       
   369          * @return a field builder.
       
   370          */
       
   371         public static FieldBuilder parse(String fieldString) {
       
   372             String prefix;
       
   373             String value = null;
       
   374             Matcher m = FIELD_RE.matcher(fieldString);
       
   375             if (m.matches()) {
       
   376                 prefix = m.group(1).trim();
       
   377                 value = m.group(3).trim();
       
   378             } else {
       
   379                 int end = fieldString.lastIndexOf(';') > 0
       
   380                         ? fieldString.lastIndexOf(';')
       
   381                         : fieldString.length();
       
   382                 prefix = fieldString.substring(0, end).trim();
       
   383             }
       
   384             List<String> list = Stream.of(prefix.split(" "))
       
   385                     .filter(s -> !s.isEmpty()).collect(Collectors.toList());
       
   386             if (list.size() < 2) {
       
   387                 throw new IllegalArgumentException("incorrect field string: "
       
   388                         + fieldString);
       
   389             }
       
   390             String name = list.get(list.size() - 1);
       
   391             String fieldType = list.get(list.size() - 2);
       
   392 
       
   393             FieldBuilder fb = new FieldBuilder(name);
       
   394             fb.modifiers.setModifiers(list.subList(0, list.size() - 2));
       
   395             fb.setFieldType(fieldType);
       
   396             if (value != null)
       
   397                 fb.setValue(value);
       
   398 
       
   399             return fb;
       
   400         }
       
   401 
       
   402         /**
       
   403          * Sets the modifiers for this builder.
       
   404          *
       
   405          * @param mods
       
   406          * @return this builder
       
   407          */
       
   408         public FieldBuilder setModifiers(String mods) {
       
   409             this.modifiers.setModifiers(mods);
       
   410             return this;
       
   411         }
       
   412 
       
   413         /**
       
   414          * Sets the type of the field.
       
   415          * @param fieldType the name of the type.
       
   416          * @return this field builder.
       
   417          */
       
   418         public FieldBuilder setFieldType(String fieldType) {
       
   419             this.fieldType = fieldType;
       
   420             return this;
       
   421         }
       
   422 
       
   423         public FieldBuilder setValue(String value) {
       
   424             this.value = value;
       
   425             return this;
       
   426         }
       
   427 
       
   428         @Override
       
   429         public String toString() {
       
   430             String indent = "    ";
       
   431             OutputWriter ow = new OutputWriter();
       
   432             switch (comments.kind) {
       
   433                 case AUTO:
       
   434                     ow.println(indent + "/** Field " +
       
   435                             super.name + " in " + super.cname + " */");
       
   436                     break;
       
   437                 case INHERIT_DOC: case USER:
       
   438                     ow.println(indent + "/** " +
       
   439                             comments.toString() + " */");
       
   440                     break;
       
   441                 case NO_API_COMMENT:
       
   442                     ow.println(indent + "// NO_API_COMMENT");
       
   443                     break;
       
   444             }
       
   445             ow.print(indent + super.modifiers.toString() + " ");
       
   446             ow.print(fieldType + " ");
       
   447             ow.print(super.name);
       
   448             if (value != null) {
       
   449                 ow.print(" = " + value);
       
   450             }
       
   451             ow.println(";");
       
   452             return ow.toString();
       
   453         }
       
   454     }
       
   455 
       
   456     /**
       
   457      * The method builder.
       
   458      */
       
   459     public static class MethodBuilder extends MemberBuilder {
       
   460 
       
   461         private final List<Pair> params;
       
   462 
       
   463         private String returnType;
       
   464         private List<String> body;
       
   465 
       
   466         final static Pattern METHOD_RE = Pattern.compile("(.*)(\\()(.*)(\\))(.*)");
       
   467 
       
   468         /**
       
   469          * Constructs a method builder. The builder by default is configured
       
   470          * to auto generate the comments for this method.
       
   471          * @param name of the method.
       
   472          */
       
   473         public MethodBuilder(String name) {
       
   474             super(new Modifiers(), name);
       
   475             comments = new Comment(Comment.Kind.AUTO);
       
   476             params = new ArrayList<>();
       
   477             body = null;
       
   478         }
       
   479 
       
   480         /**
       
   481          * Returns a method builder by parsing a string which
       
   482          * describes a method.
       
   483          * @param methodString the method description.
       
   484          * @return a method builder.
       
   485          */
       
   486         public static MethodBuilder parse(String methodString) {
       
   487             Matcher m = METHOD_RE.matcher(methodString);
       
   488             if (!m.matches())
       
   489                 throw new IllegalArgumentException("string does not match: "
       
   490                         + methodString);
       
   491             String prefix = m.group(1);
       
   492             String params = m.group(3);
       
   493             String suffix = m.group(5).trim();
       
   494 
       
   495             if (prefix.length() < 2) {
       
   496                 throw new IllegalArgumentException("incorrect method string: "
       
   497                         + methodString);
       
   498             }
       
   499 
       
   500             String[] pa = prefix.split(" ");
       
   501             List<String> list = List.of(pa);
       
   502             String name = list.get(list.size() - 1);
       
   503             String returnType = list.get(list.size() - 2);
       
   504 
       
   505             MethodBuilder mb = new MethodBuilder(name);
       
   506             mb.modifiers.setModifiers(list.subList(0, list.size() - 2));
       
   507             mb.setReturn(returnType);
       
   508 
       
   509             pa = params.split(",");
       
   510             Stream.of(pa).forEach(p -> {
       
   511                 p = p.trim();
       
   512                 if (!p.isEmpty())
       
   513                     mb.addParameter(p);
       
   514             });
       
   515             if (!suffix.isEmpty() || suffix.length() > 1) {
       
   516                 mb.setBody(suffix);
       
   517             }
       
   518             return mb;
       
   519         }
       
   520 
       
   521         /**
       
   522          * Sets the modifiers for this builder.
       
   523          *
       
   524          * @param modifiers
       
   525          * @return this builder
       
   526          */
       
   527         public MethodBuilder setModifiers(String modifiers) {
       
   528             this.modifiers.setModifiers(modifiers);
       
   529             return this;
       
   530         }
       
   531 
       
   532         @Override
       
   533         public MethodBuilder setComments(String... comments) {
       
   534             super.setComments(comments);
       
   535             return this;
       
   536         }
       
   537 
       
   538         @Override
       
   539         public MethodBuilder setComments(Comment.Kind kind) {
       
   540             super.setComments(kind);
       
   541             return this;
       
   542         }
       
   543 
       
   544         /**
       
   545          * Sets a return type for a method.
       
   546          * @param returnType the return type.
       
   547          * @return this method builder.
       
   548          */
       
   549         public MethodBuilder setReturn(String returnType) {
       
   550             this.returnType = returnType;
       
   551             return this;
       
   552         }
       
   553 
       
   554         /**
       
   555          * Adds a parameter(s) to the method method builder.
       
   556          * @param params a pair consisting of type and parameter name.
       
   557          * @return this method builder.
       
   558          */
       
   559         public MethodBuilder addParameters(Pair... params) {
       
   560             this.params.addAll(List.of(params));
       
   561             return this;
       
   562         }
       
   563 
       
   564         /**
       
   565          * Adds a parameter to the method method builder.
       
   566          * @param type the type of parameter.
       
   567          * @param name the parameter name.
       
   568          * @return this method builder.
       
   569          */
       
   570         public MethodBuilder addParameter(String type, String name) {
       
   571             this.params.add(new Pair(type, name));
       
   572             return this;
       
   573         }
       
   574 
       
   575         /**
       
   576          * Adds a parameter to the method builder, by parsing the string.
       
   577          * @param s the parameter description such as "Double voltage"
       
   578          * @return this method builder.
       
   579          */
       
   580         public MethodBuilder addParameter(String s) {
       
   581             String[] p = s.trim().split(" ");
       
   582             return addParameter(p[0], p[p.length - 1]);
       
   583         }
       
   584 
       
   585         /**
       
   586          * Sets the body of the method, described by the string.
       
   587          * Such as "{", "double i = v/r;", "return i;", "}"
       
   588          * @param body of the methods
       
   589          * @return
       
   590          */
       
   591         public MethodBuilder setBody(String... body) {
       
   592             if (body == null) {
       
   593                 this.body = null;
       
   594             } else {
       
   595                 this.body = new ArrayList<>();
       
   596                 this.body.addAll(List.of(body));
       
   597             }
       
   598             return this;
       
   599         }
       
   600 
       
   601         @Override
       
   602         public String toString() {
       
   603             OutputWriter ow = new OutputWriter();
       
   604             String indent = "    ";
       
   605             switch (comments.kind) {
       
   606                 case AUTO:
       
   607                     ow.println(indent + "/** Method " + super.name + " in " + super.cname);
       
   608                     if (!params.isEmpty())
       
   609                         params.forEach(p -> ow.println(indent + " * @param " + p.second + " a param"));
       
   610                     if (returnType != null && !returnType.isEmpty() && !returnType.contains("void"))
       
   611                         ow.println(indent + " * @return returns something");
       
   612                     ow.println(indent + " */");
       
   613                     break;
       
   614                 case INHERIT_DOC: case USER:
       
   615                     ow.println(indent + "/** " + comments.toString() + " */");
       
   616                     break;
       
   617                 case NO_API_COMMENT:
       
   618                     ow.println(indent + "// NO_API_COMMENT");
       
   619                     break;
       
   620             }
       
   621 
       
   622             ow.print(indent + super.modifiers.toString() + " ");
       
   623             ow.print(returnType + " ");
       
   624             ow.print(super.name + "(");
       
   625             if (!params.isEmpty()) {
       
   626                 ListIterator<Pair> iter = params.listIterator();
       
   627                 while (iter.hasNext()) {
       
   628                     Pair p = iter.next();
       
   629                     ow.print(p.first + " " + p.second);
       
   630                     if (iter.hasNext())
       
   631                         ow.print(", ");
       
   632                 }
       
   633             }
       
   634             ow.print(")");
       
   635             if (body == null) {
       
   636                 ow.println(";");
       
   637             } else {
       
   638                 body.forEach(ow::println);
       
   639             }
       
   640             return ow.toString();
       
   641         }
       
   642     }
       
   643 
       
   644 //A sample, to test with an IDE.
       
   645 //    public static void main(String... args) throws IOException {
       
   646 //        ClassBuilder cb = new ClassBuilder(new ToolBox(), "foo.bar.Test<C extends A>");
       
   647 //        cb.addModifiers("public", "abstract", "static")
       
   648 //                .addImports("java.io").addImports("java.nio")
       
   649 //                .setComments("A comment")
       
   650 //                .addImplements("Serialization", "Something")
       
   651 //                .setExtends("java.lang.Object")
       
   652 //                .addMembers(
       
   653 //                        FieldBuilder.parse("public int xxx;"),
       
   654 //                        FieldBuilder.parse("public int yyy = 10;"),
       
   655 //                        MethodBuilder.parse("public static void main(A a, B b, C c);")
       
   656 //                            .setComments("CC"),
       
   657 //                        MethodBuilder.parse("void foo(){//do something}")
       
   658 //
       
   659 //                );
       
   660 //        ClassBuilder ic = new ClassBuilder(new ToolBox(), "IC");
       
   661 //        cb.addModifiers( "interface");
       
   662 //        cb.addNestedClasses(ic);
       
   663 //        System.out.println(cb.toString());
       
   664 //        cb.write(Paths.get("src-out"));
       
   665 //    }
       
   666 }