make/langtools/tools/propertiesparser/gen/ClassGenerator.java
changeset 47216 71c04702a3d5
parent 28801 2f1c998c3fcc
child 49158 f62d1d1c2d9c
equal deleted inserted replaced
47215:4ebc2e2fb97c 47216:71c04702a3d5
       
     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 propertiesparser.gen;
       
    25 
       
    26 import propertiesparser.parser.Message;
       
    27 import propertiesparser.parser.MessageFile;
       
    28 import propertiesparser.parser.MessageInfo;
       
    29 import propertiesparser.parser.MessageLine;
       
    30 import propertiesparser.parser.MessageType;
       
    31 import propertiesparser.parser.MessageType.CompoundType;
       
    32 import propertiesparser.parser.MessageType.CustomType;
       
    33 import propertiesparser.parser.MessageType.SimpleType;
       
    34 import propertiesparser.parser.MessageType.UnionType;
       
    35 import propertiesparser.parser.MessageType.Visitor;
       
    36 
       
    37 import java.io.File;
       
    38 import java.io.FileWriter;
       
    39 import java.io.IOException;
       
    40 import java.io.InputStream;
       
    41 import java.text.MessageFormat;
       
    42 import java.util.ArrayList;
       
    43 import java.util.Arrays;
       
    44 import java.util.Collections;
       
    45 import java.util.TreeSet;
       
    46 import java.util.List;
       
    47 import java.util.Map;
       
    48 import java.util.Set;
       
    49 import java.util.Properties;
       
    50 import java.util.stream.Collectors;
       
    51 import java.util.stream.Stream;
       
    52 
       
    53 public class ClassGenerator {
       
    54 
       
    55     /** Empty string - used to generate indentation padding. */
       
    56     private final static String INDENT_STRING = "                                                                   ";
       
    57 
       
    58     /** Default indentation step. */
       
    59     private final static int INDENT_WIDTH = 4;
       
    60 
       
    61     /** File-backed property file containing basic code stubs. */
       
    62     static Properties stubs;
       
    63 
       
    64     static {
       
    65         //init properties from file
       
    66         stubs = new Properties();
       
    67         String resourcePath = "/propertiesparser/resources/templates.properties";
       
    68         try (InputStream in = ClassGenerator.class.getResourceAsStream(resourcePath)) {
       
    69             stubs.load(in);
       
    70         } catch (IOException ex) {
       
    71             throw new AssertionError(ex);
       
    72         }
       
    73     }
       
    74 
       
    75     /**
       
    76      * Supported stubs in the property file.
       
    77      */
       
    78     enum StubKind {
       
    79         TOPLEVEL("toplevel.decl"),
       
    80         FACTORY_CLASS("nested.decl"),
       
    81         IMPORT("import.decl"),
       
    82         FACTORY_METHOD_DECL("factory.decl.method"),
       
    83         FACTORY_METHOD_ARG("factory.decl.method.arg"),
       
    84         FACTORY_METHOD_BODY("factory.decl.method.body"),
       
    85         FACTORY_FIELD("factory.decl.field"),
       
    86         WILDCARDS_EXTENDS("wildcards.extends"),
       
    87         SUPPRESS_WARNINGS("suppress.warnings");
       
    88 
       
    89         /** stub key (as it appears in the property file) */
       
    90         String key;
       
    91 
       
    92         StubKind(String key) {
       
    93             this.key = key;
       
    94         }
       
    95 
       
    96         /**
       
    97          * Subst a list of arguments into a given stub.
       
    98          */
       
    99         String format(Object... args) {
       
   100             return MessageFormat.format((String)stubs.get(key), args);
       
   101         }
       
   102     }
       
   103 
       
   104     /**
       
   105      * Nested factory class kind. There are multiple sub-factories, one for each kind of commonly used
       
   106      * diagnostics (i.e. error, warnings, note, fragment). An additional category is defined for
       
   107      * those resource keys whose prefix doesn't match any predefined category.
       
   108      */
       
   109     enum FactoryKind {
       
   110         ERR("err", "Error", "Errors"),
       
   111         WARN("warn", "Warning", "Warnings"),
       
   112         NOTE("note", "Note", "Notes"),
       
   113         MISC("misc", "Fragment", "Fragments"),
       
   114         OTHER(null, null, null);
       
   115 
       
   116         /** The prefix for this factory kind (i.e. 'err'). */
       
   117         String prefix;
       
   118 
       
   119         /** The type of the factory method/fields in this class. */
       
   120         String keyClazz;
       
   121 
       
   122         /** The class name to be used for this factory. */
       
   123         String factoryClazz;
       
   124 
       
   125         FactoryKind(String prefix, String keyClazz, String factoryClazz) {
       
   126             this.prefix = prefix;
       
   127             this.keyClazz = keyClazz;
       
   128             this.factoryClazz = factoryClazz;
       
   129         }
       
   130 
       
   131         /**
       
   132          * Utility method for parsing a factory kind from a resource key prefix.
       
   133          */
       
   134         static FactoryKind parseFrom(String prefix) {
       
   135             for (FactoryKind k : FactoryKind.values()) {
       
   136                 if (k.prefix == null || k.prefix.equals(prefix)) {
       
   137                     return k;
       
   138                 }
       
   139             }
       
   140             return null;
       
   141         }
       
   142     }
       
   143 
       
   144     /**
       
   145      * Main entry-point: generate a Java enum-like set of nested factory classes into given output
       
   146      * folder. The factories are populated as mandated by the comments in the input resource file.
       
   147      */
       
   148     public void generateFactory(MessageFile messageFile, File outDir) {
       
   149         Map<FactoryKind, List<Map.Entry<String, Message>>> groupedEntries =
       
   150                 messageFile.messages.entrySet().stream()
       
   151                         .collect(Collectors.groupingBy(e -> FactoryKind.parseFrom(e.getKey().split("\\.")[1])));
       
   152         //generate nested classes
       
   153         List<String> nestedDecls = new ArrayList<>();
       
   154         Set<String> importedTypes = new TreeSet<>();
       
   155         for (Map.Entry<FactoryKind, List<Map.Entry<String, Message>>> entry : groupedEntries.entrySet()) {
       
   156             if (entry.getKey() == FactoryKind.OTHER) continue;
       
   157             //emit members
       
   158             String members = entry.getValue().stream()
       
   159                     .flatMap(e -> generateFactoryMethodsAndFields(e.getKey(), e.getValue()).stream())
       
   160                     .collect(Collectors.joining("\n\n"));
       
   161             //emit nested class
       
   162             String factoryDecl =
       
   163                     StubKind.FACTORY_CLASS.format(entry.getKey().factoryClazz, indent(members, 1));
       
   164             nestedDecls.add(indent(factoryDecl, 1));
       
   165             //add imports
       
   166             entry.getValue().stream().forEach(e ->
       
   167                     importedTypes.addAll(importedTypes(e.getValue().getMessageInfo().getTypes())));
       
   168         }
       
   169         String clazz = StubKind.TOPLEVEL.format(
       
   170                 packageName(messageFile.file),
       
   171                 String.join("\n", generateImports(importedTypes)),
       
   172                 toplevelName(messageFile.file),
       
   173                 String.join("\n", nestedDecls));
       
   174         try (FileWriter fw = new FileWriter(new File(outDir, toplevelName(messageFile.file) + ".java"))) {
       
   175             fw.append(clazz);
       
   176         } catch (Throwable ex) {
       
   177             throw new AssertionError(ex);
       
   178         }
       
   179     }
       
   180 
       
   181     /**
       
   182      * Indent a string to a given level.
       
   183      */
       
   184     String indent(String s, int level) {
       
   185         return Stream.of(s.split("\n"))
       
   186                 .map(sub -> INDENT_STRING.substring(0, level * INDENT_WIDTH) + sub)
       
   187                 .collect(Collectors.joining("\n"));
       
   188     }
       
   189 
       
   190     /**
       
   191      * Retrieve package part of given file object.
       
   192      */
       
   193     String packageName(File file) {
       
   194         String path = file.getAbsolutePath();
       
   195         int begin = path.lastIndexOf(File.separatorChar + "com" + File.separatorChar);
       
   196         String packagePath = path.substring(begin + 1, path.lastIndexOf(File.separatorChar));
       
   197         String packageName =  packagePath.replace(File.separatorChar, '.');
       
   198         return packageName;
       
   199     }
       
   200 
       
   201     /**
       
   202      * Form the name of the toplevel factory class.
       
   203      */
       
   204     public static String toplevelName(File file) {
       
   205         return Stream.of(file.getName().split("\\."))
       
   206                 .map(s -> Character.toUpperCase(s.charAt(0)) + s.substring(1))
       
   207                 .collect(Collectors.joining(""));
       
   208     }
       
   209 
       
   210     /**
       
   211      * Generate a list of import declarations given a set of imported types.
       
   212      */
       
   213     List<String> generateImports(Set<String> importedTypes) {
       
   214         List<String> importDecls = new ArrayList<>();
       
   215         for (String it : importedTypes) {
       
   216             importDecls.add(StubKind.IMPORT.format(it));
       
   217         }
       
   218         return importDecls;
       
   219     }
       
   220 
       
   221     /**
       
   222      * Generate a list of factory methods/fields to be added to a given factory nested class.
       
   223      */
       
   224     List<String> generateFactoryMethodsAndFields(String key, Message msg) {
       
   225         MessageInfo msgInfo = msg.getMessageInfo();
       
   226         List<MessageLine> lines = msg.getLines(false);
       
   227         String javadoc = lines.stream()
       
   228                 .filter(ml -> !ml.isInfo() && !ml.isEmptyOrComment())
       
   229                 .map(ml -> ml.text)
       
   230                 .collect(Collectors.joining("\n *"));
       
   231         String[] keyParts = key.split("\\.");
       
   232         FactoryKind k = FactoryKind.parseFrom(keyParts[1]);
       
   233         String factoryName = factoryName(key);
       
   234         if (msgInfo.getTypes().isEmpty()) {
       
   235             //generate field
       
   236             String factoryField = StubKind.FACTORY_FIELD.format(k.keyClazz, factoryName,
       
   237                     "\"" + keyParts[0] + "\"",
       
   238                     "\"" + Stream.of(keyParts).skip(2).collect(Collectors.joining(".")) + "\"",
       
   239                     javadoc);
       
   240             return Collections.singletonList(factoryField);
       
   241         } else {
       
   242             //generate method
       
   243             List<String> factoryMethods = new ArrayList<>();
       
   244             for (List<MessageType> msgTypes : normalizeTypes(0, msgInfo.getTypes())) {
       
   245                 List<String> types = generateTypes(msgTypes);
       
   246                 List<String> argNames = argNames(types.size());
       
   247                 String suppressionString = needsSuppressWarnings(msgTypes) ?
       
   248                         StubKind.SUPPRESS_WARNINGS.format() : "";
       
   249                 String factoryMethod = StubKind.FACTORY_METHOD_DECL.format(suppressionString, k.keyClazz,
       
   250                         factoryName, argDecls(types, argNames).stream().collect(Collectors.joining(", ")),
       
   251                         indent(StubKind.FACTORY_METHOD_BODY.format(k.keyClazz,
       
   252                                 "\"" + keyParts[0] + "\"",
       
   253                                 "\"" + Stream.of(keyParts).skip(2).collect(Collectors.joining(".")) + "\"",
       
   254                                 argNames.stream().collect(Collectors.joining(", "))), 1),
       
   255                         javadoc);
       
   256                 factoryMethods.add(factoryMethod);
       
   257             }
       
   258             return factoryMethods;
       
   259         }
       
   260     }
       
   261 
       
   262     /**
       
   263      * Form the name of a factory method/field given a resource key.
       
   264      */
       
   265     String factoryName(String key) {
       
   266         return Stream.of(key.split("[\\.-]"))
       
   267                 .skip(2)
       
   268                 .map(s -> Character.toUpperCase(s.charAt(0)) + s.substring(1))
       
   269                 .collect(Collectors.joining(""));
       
   270     }
       
   271 
       
   272     /**
       
   273      * Generate a formal parameter list given a list of types and names.
       
   274      */
       
   275     List<String> argDecls(List<String> types, List<String> args) {
       
   276         List<String> argNames = new ArrayList<>();
       
   277         for (int i = 0 ; i < types.size() ; i++) {
       
   278             argNames.add(types.get(i) + " " + args.get(i));
       
   279         }
       
   280         return argNames;
       
   281     }
       
   282 
       
   283     /**
       
   284      * Generate a list of formal parameter names given a size.
       
   285      */
       
   286     List<String> argNames(int size) {
       
   287         List<String> argNames = new ArrayList<>();
       
   288         for (int i = 0 ; i < size ; i++) {
       
   289             argNames.add(StubKind.FACTORY_METHOD_ARG.format(i));
       
   290         }
       
   291         return argNames;
       
   292     }
       
   293 
       
   294     /**
       
   295      * Convert a (normalized) parsed type into a string-based representation of some Java type.
       
   296      */
       
   297     List<String> generateTypes(List<MessageType> msgTypes) {
       
   298         return msgTypes.stream().map(t -> t.accept(stringVisitor, null)).collect(Collectors.toList());
       
   299     }
       
   300     //where
       
   301         Visitor<String, Void> stringVisitor = new Visitor<String, Void>() {
       
   302             @Override
       
   303             public String visitCustomType(CustomType t, Void aVoid) {
       
   304                 String customType = t.typeString;
       
   305                 return customType.substring(customType.lastIndexOf('.') + 1);
       
   306             }
       
   307 
       
   308             @Override
       
   309             public String visitSimpleType(SimpleType t, Void aVoid) {
       
   310                 return t.clazz;
       
   311             }
       
   312 
       
   313             @Override
       
   314             public String visitCompoundType(CompoundType t, Void aVoid) {
       
   315                 return StubKind.WILDCARDS_EXTENDS.format(t.kind.clazz.clazz,
       
   316                         t.elemtype.accept(this, null));
       
   317             }
       
   318 
       
   319             @Override
       
   320             public String visitUnionType(UnionType t, Void aVoid) {
       
   321                 throw new AssertionError("Union types should have been denormalized!");
       
   322             }
       
   323         };
       
   324 
       
   325     /**
       
   326      * See if any of the parsed types in the given list needs warning suppression.
       
   327      */
       
   328     boolean needsSuppressWarnings(List<MessageType> msgTypes) {
       
   329         return msgTypes.stream().anyMatch(t -> t.accept(suppressWarningsVisitor, null));
       
   330     }
       
   331     //where
       
   332     Visitor<Boolean, Void> suppressWarningsVisitor = new Visitor<Boolean, Void>() {
       
   333         @Override
       
   334         public Boolean visitCustomType(CustomType t, Void aVoid) {
       
   335             //play safe
       
   336             return true;
       
   337         }
       
   338         @Override
       
   339         public Boolean visitSimpleType(SimpleType t, Void aVoid) {
       
   340             switch (t) {
       
   341                 case LIST:
       
   342                 case SET:
       
   343                     return true;
       
   344                 default:
       
   345                     return false;
       
   346             }
       
   347         }
       
   348 
       
   349         @Override
       
   350         public Boolean visitCompoundType(CompoundType t, Void aVoid) {
       
   351             return t.elemtype.accept(this, null);
       
   352         }
       
   353 
       
   354         @Override
       
   355         public Boolean visitUnionType(UnionType t, Void aVoid) {
       
   356             return needsSuppressWarnings(Arrays.asList(t.choices));
       
   357         }
       
   358     };
       
   359 
       
   360     /**
       
   361      * Retrieve a list of types that need to be imported, so that the factory body can refer
       
   362      * to the types in the given list using simple names.
       
   363      */
       
   364     Set<String> importedTypes(List<MessageType> msgTypes) {
       
   365         Set<String> imports = new TreeSet<>();
       
   366         msgTypes.forEach(t -> t.accept(importVisitor, imports));
       
   367         return imports;
       
   368     }
       
   369     //where
       
   370     Visitor<Void, Set<String>> importVisitor = new Visitor<Void, Set<String>>() {
       
   371         @Override
       
   372         public Void visitCustomType(CustomType t, Set<String> imports) {
       
   373             imports.add(t.typeString);
       
   374             return null;
       
   375         }
       
   376 
       
   377         @Override
       
   378         public Void visitSimpleType(SimpleType t, Set<String> imports) {
       
   379             if (t.qualifier != null) {
       
   380                 imports.add(t.qualifier + "." + t.clazz);
       
   381             }
       
   382             return null;
       
   383         }
       
   384 
       
   385         @Override
       
   386         public Void visitCompoundType(CompoundType t, Set<String> imports) {
       
   387             visitSimpleType(t.kind.clazz, imports);
       
   388             t.elemtype.accept(this, imports);
       
   389             return null;
       
   390         }
       
   391 
       
   392         @Override
       
   393         public Void visitUnionType(UnionType t, Set<String> imports) {
       
   394             Stream.of(t.choices).forEach(c -> c.accept(this, imports));
       
   395             return null;
       
   396         }
       
   397     };
       
   398 
       
   399     /**
       
   400      * Normalize parsed types in a comment line. If one or more types in the line contains alternatives,
       
   401      * this routine generate a list of 'overloaded' normalized signatures.
       
   402      */
       
   403     List<List<MessageType>> normalizeTypes(int idx, List<MessageType> msgTypes) {
       
   404         if (msgTypes.size() == idx) return Collections.singletonList(Collections.emptyList());
       
   405         MessageType head = msgTypes.get(idx);
       
   406         List<List<MessageType>> buf = new ArrayList<>();
       
   407         for (MessageType alternative : head.accept(normalizeVisitor, null)) {
       
   408             for (List<MessageType> rest : normalizeTypes(idx + 1, msgTypes)) {
       
   409                 List<MessageType> temp = new ArrayList<>(rest);
       
   410                 temp.add(0, alternative);
       
   411                 buf.add(temp);
       
   412             }
       
   413         }
       
   414         return buf;
       
   415     }
       
   416     //where
       
   417     Visitor<List<MessageType>, Void> normalizeVisitor = new Visitor<List<MessageType>, Void>() {
       
   418         @Override
       
   419         public List<MessageType> visitCustomType(CustomType t, Void aVoid) {
       
   420             return Collections.singletonList(t);
       
   421         }
       
   422 
       
   423         @Override
       
   424         public List<MessageType> visitSimpleType(SimpleType t, Void aVoid) {
       
   425             return Collections.singletonList(t);
       
   426         }
       
   427 
       
   428         @Override
       
   429         public List<MessageType> visitCompoundType(CompoundType t, Void aVoid) {
       
   430             return t.elemtype.accept(this, null).stream()
       
   431                     .map(nt -> new CompoundType(t.kind, nt))
       
   432                     .collect(Collectors.toList());
       
   433         }
       
   434 
       
   435         @Override
       
   436         public List<MessageType> visitUnionType(UnionType t, Void aVoid) {
       
   437             return Stream.of(t.choices)
       
   438                     .flatMap(t2 -> t2.accept(this, null).stream())
       
   439                     .collect(Collectors.toList());
       
   440         }
       
   441     };
       
   442 }