# HG changeset patch # User mcimadamore # Date 1420728185 0 # Node ID 1633de6070ae920527799c28425351f31a68013e # Parent 8eeea699174cf71fe38d83b34d649ffa8c9c2cfc 8058542: Devise scheme for better diagnostic creation Summary: Add support for generating (at build-time) an enum-like class containing all javac diagnostics, which allows for safe diagnostic creation. Reviewed-by: jlahoda, jjg, vromero, erikj, jfranck diff -r 8eeea699174c -r 1633de6070ae langtools/make/Tools.gmk --- a/langtools/make/Tools.gmk Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/make/Tools.gmk Thu Jan 08 14:43:05 2015 +0000 @@ -39,7 +39,7 @@ DISABLE_SJAVAC := true, \ ADD_JAVAC_FLAGS := -Xprefer:source, \ SRC := $(LANGTOOLS_TOPDIR)/make/tools, \ - INCLUDES := compileproperties, \ + INCLUDES := compileproperties propertiesparser, \ BIN := $(BUILDTOOLS_OUTPUTDIR)/langtools_tools_classes)) all: $(BUILD_TOOLS_LANGTOOLS) diff -r 8eeea699174c -r 1633de6070ae langtools/make/build.properties --- a/langtools/make/build.properties Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/make/build.properties Thu Jan 08 14:43:05 2015 +0000 @@ -54,6 +54,9 @@ jdk.javadoc.dependencies=java.base:java.compiler:jdk.compiler jdk.dev.dependencies=java.base:java.compiler:jdk.compiler +javac.resource.includes = \ + com/sun/tools/javac/resources/compiler.properties + #test configuration: jtreg.tests= boot.javac.tests = tools/javac diff -r 8eeea699174c -r 1633de6070ae langtools/make/build.xml --- a/langtools/make/build.xml Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/make/build.xml Thu Jan 08 14:43:05 2015 +0000 @@ -527,7 +527,8 @@ + compilation.kind="@{compilation.kind}" + resource.includes="${javac.resource.includes}" /> - + + @@ -583,6 +585,12 @@ + + + + + @@ -644,6 +652,28 @@ + + + + + + + + + + + diff -r 8eeea699174c -r 1633de6070ae langtools/make/gensrc/Gensrc-jdk.compiler.gmk --- a/langtools/make/gensrc/Gensrc-jdk.compiler.gmk Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/make/gensrc/Gensrc-jdk.compiler.gmk Thu Jan 08 14:43:05 2015 +0000 @@ -37,4 +37,7 @@ $(eval $(call SetupCompileProperties,COMPILE_PROPERTIES,\ $(JAVAC_VERSION) $(JAVAH_VERSION) $(JAVAP_VERSION))) -all: $(COMPILE_PROPERTIES) +$(eval $(call SetupParseProperties,PARSE_PROPERTIES,\ + com/sun/tools/javac/resources/compiler.properties)) + +all: $(COMPILE_PROPERTIES) $(PARSE_PROPERTIES) diff -r 8eeea699174c -r 1633de6070ae langtools/make/gensrc/GensrcCommon.gmk --- a/langtools/make/gensrc/GensrcCommon.gmk Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/make/gensrc/GensrcCommon.gmk Thu Jan 08 14:43:05 2015 +0000 @@ -35,6 +35,11 @@ TOOL_COMPILEPROPS_CMD := $(JAVA) -cp $(BUILDTOOLS_OUTPUTDIR)/langtools_tools_classes \ compileproperties.CompileProperties -quiet +################################################################################ +# The compileprops tools compiles a properties file into an enum-like class. +TOOL_PARSEPROPS_CMD := $(JAVA) -cp $(BUILDTOOLS_OUTPUTDIR)/langtools_tools_classes \ + propertiesparser.PropertiesParser + ################################################################################ # Sets up a rule that creates a version.properties file in the gensrc output @@ -93,3 +98,32 @@ endef ################################################################################ +# Parse property files in given location and generate a Java-like enum in the gensrc folder. +# Param 1 - Variable to add targets to +# Param 2 - Extra properties files to process +define SetupParseProperties + #property file to generate + PARSEPROPSOURCES := $$(foreach var,$2,$$(addsuffix $$(var),$(LANGTOOLS_TOPDIR)/src/$(MODULE)/share/classes/)) + + PARSEPROPALLDIRS := $$(patsubst $(LANGTOOLS_TOPDIR)/src/%, \ + $(SUPPORT_OUTPUTDIR)/gensrc/%, \ + $$(dir $$(PARSEPROPSOURCES))) + + PARSEPROPDIRS := $$(sort $$(PARSEPROPALLDIRS)) + + PARSEPROPCMDLINE := $$(subst _SPACE_, $$(SPACE), \ + $$(join $$(foreach var,$$(PARSEPROPSOURCES),$$(addprefix -compile_SPACE_,$$(var))), \ + $$(addprefix _SPACE_, $$(PARSEPROPALLDIRS)))) + + # Now setup the rule for the generation of the resource bundles. + $(SUPPORT_OUTPUTDIR)/gensrc/$(MODULE)/_the_parsed_props: $(PARSEPROPSOURCES) + $(CP) -r $(LANGTOOLS_TOPDIR)/make/tools/propertiesparser/resources $(BUILDTOOLS_OUTPUTDIR)/langtools_tools_classes/propertiesparser/resources + $(MKDIR) -p $$(@D) $$(PARSEPROPDIRS) + $(ECHO) Parsing $$(words $$(PARSEPROPSOURCES)) properties into enum-like class for $(MODULE) + $(TOOL_PARSEPROPS_CMD) $$(PARSEPROPCMDLINE) + $(TOUCH) $$@ + + $$(strip $1) += $(SUPPORT_OUTPUTDIR)/gensrc/$(MODULE)/_the_parsed_props +endef + +################################################################################ diff -r 8eeea699174c -r 1633de6070ae langtools/make/intellij/build.xml --- a/langtools/make/intellij/build.xml Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/make/intellij/build.xml Thu Jan 08 14:43:05 2015 +0000 @@ -28,7 +28,7 @@ - + diff -r 8eeea699174c -r 1633de6070ae langtools/make/intellij/compiler.xml --- a/langtools/make/intellij/compiler.xml Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/make/intellij/compiler.xml Thu Jan 08 14:43:05 2015 +0000 @@ -5,6 +5,7 @@ + diff -r 8eeea699174c -r 1633de6070ae langtools/make/intellij/langtools.iml --- a/langtools/make/intellij/langtools.iml Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/make/intellij/langtools.iml Thu Jan 08 14:43:05 2015 +0000 @@ -1,20 +1,22 @@ - + - - + + + + + - diff -r 8eeea699174c -r 1633de6070ae langtools/make/intellij/misc.xml --- a/langtools/make/intellij/misc.xml Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/make/intellij/misc.xml Thu Jan 08 14:43:05 2015 +0000 @@ -3,8 +3,8 @@ - - + + diff -r 8eeea699174c -r 1633de6070ae langtools/make/netbeans/langtools/nbproject/project.xml --- a/langtools/make/netbeans/langtools/nbproject/project.xml Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/make/netbeans/langtools/nbproject/project.xml Thu Jan 08 14:43:05 2015 +0000 @@ -478,29 +478,34 @@ ${root}/src/java.base/share/classes + ${root}/build/bootstrap/java.base/gensrc ${root}/build/java.base/classes 1.8 ${root}/src/java.compiler/share/classes + ${root}/build/bootstrap/java.compiler/gensrc ${root}/build/java.base/classes ${root}/build/java.compiler/classes 1.8 ${root}/src/jdk.compiler/share/classes + ${root}/build/bootstrap/jdk.compiler/gensrc ${root}/build/java.base/classes:${root}/build/java.compiler/classes ${root}/build/jdk.compiler/classes 1.8 ${root}/src/jdk.dev/share/classes + ${root}/build/bootstrap/jdk.dev/gensrc ${root}/build/java.base/classes:${root}/build/java.compiler/classes:${root}/build/jdk.compiler/classes ${root}/build/jdk.dev/classes 1.8 ${root}/src/jdk.javadoc/share/classes + ${root}/build/bootstrap/jdk.javadoc/gensrc ${root}/build/java.base/classes:${root}/build/java.compiler/classes:${root}/build/jdk.compiler/classes ${root}/build/jdk.javadoc/classes 1.8 diff -r 8eeea699174c -r 1633de6070ae langtools/make/tools/anttasks/PropertiesParserTask.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/make/tools/anttasks/PropertiesParserTask.java Thu Jan 08 14:43:05 2015 +0000 @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package anttasks; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import propertiesparser.PropertiesParser; +import propertiesparser.gen.ClassGenerator; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Resource; + +public class PropertiesParserTask extends MatchingTask { + public void addSrc(Path src) { + if (srcDirs == null) + srcDirs = new Path(getProject()); + srcDirs.add(src); + } + + public void setDestDir(File destDir) { + this.destDir = destDir; + } + + @Override + public void execute() { + List mainOpts = new ArrayList(); + int count = 0; + for (String dir : srcDirs.list()) { + File baseDir = getProject().resolveFile(dir); + DirectoryScanner s = getDirectoryScanner(baseDir); + for (String path : s.getIncludedFiles()) { + if (path.endsWith(".properties")) { + File srcFile = new File(baseDir, path); + String destPath = + path.substring(0, path.lastIndexOf(File.separator) + 1) + + ClassGenerator.toplevelName(srcFile) + ".java"; + File destFile = new File(this.destDir, destPath); + File destDir = destFile.getParentFile(); + // Arguably, the comparison in the next line should be ">", not ">=" + // but that assumes the resolution of the last modified time is fine + // grained enough; in practice, it is better to use ">=". + if (destFile.exists() && destFile.lastModified() >= srcFile.lastModified()) + continue; + destDir.mkdirs(); + mainOpts.add("-compile"); + mainOpts.add(srcFile.getPath()); + mainOpts.add(destDir.getPath()); + count++; + } + } + } + if (mainOpts.size() > 0) { + log("Generating " + count + " resource files to " + destDir, Project.MSG_INFO); + PropertiesParser pp = new PropertiesParser(msg -> log(msg, Project.MSG_INFO)); + boolean ok = pp.run(mainOpts.toArray(new String[mainOpts.size()])); + if (!ok) + throw new BuildException("PropertiesParser failed."); + } + } + + private Path srcDirs; + private File destDir; +} diff -r 8eeea699174c -r 1633de6070ae langtools/make/tools/propertiesparser/PropertiesParser.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/make/tools/propertiesparser/PropertiesParser.java Thu Jan 08 14:43:05 2015 +0000 @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package propertiesparser; + +import propertiesparser.parser.MessageFile; +import propertiesparser.gen.ClassGenerator; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.lang.RuntimeException; +import java.lang.Throwable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** Translates a .properties file into a .java file containing an enum-like Java class + * which defines static factory methods for all resource keys in a given resource file.

+ * + * Usage: java PropertiesParser -compile [path to .properties file] [output folder where .java file will be written] + * + * @author mcimadamore + */ + +public class PropertiesParser { + + public Logger logger; + + public PropertiesParser(Logger logger) { + this.logger = logger; + } + + public static void main(String[] args) { + PropertiesParser pp = new PropertiesParser(msg -> System.out.println(msg)); + boolean ok = pp.run(args); + if ( !ok ) { + System.exit(1); + } + } + + public static interface Logger { + void info(String msg); + } + + public void info(String msg) { + logger.info(msg); + } + + public boolean run(String[] args) { + Map optionsMap = parseOptions(args); + if (optionsMap.isEmpty()) { + usage(); + return false; + } + try { + optionsMap.forEach((propfile, outfile) -> compilePropertyFile(propfile, outfile)); + return true; + } catch (RuntimeException ex) { + ex.printStackTrace(); + return false; + } + } + + private void compilePropertyFile(String propertyPath, String outPath) { + try { + File propertyFile = new File(propertyPath); + String prefix = propertyFile.getName().split("\\.")[0]; + MessageFile messageFile = new MessageFile(propertyFile, prefix); + new ClassGenerator().generateFactory(messageFile, new File(outPath)); + } catch (Throwable ex) { + throw new RuntimeException(ex); + } + } + + private Map parseOptions(String args[]) { + Map optionsMap = new HashMap<>(args.length); + for ( int i = 0; i < args.length ; i++ ) { + if ( "-compile".equals(args[i]) && i+2 < args.length ) { + optionsMap.put(args[++i], args[++i]); + } else { + return new HashMap<>(); + } + } + return optionsMap; + } + + private void usage() { + info("usage:"); + info(" java PropertiesParser {-compile path_to_properties_file path_to_java_output_dir}"); + info(""); + info("Example:"); + info(" java PropertiesParser -compile resources/test.properties resources"); + } +} diff -r 8eeea699174c -r 1633de6070ae langtools/make/tools/propertiesparser/gen/ClassGenerator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/make/tools/propertiesparser/gen/ClassGenerator.java Thu Jan 08 14:43:05 2015 +0000 @@ -0,0 +1,419 @@ +package propertiesparser.gen; + +import propertiesparser.parser.Message; +import propertiesparser.parser.MessageFile; +import propertiesparser.parser.MessageInfo; +import propertiesparser.parser.MessageLine; +import propertiesparser.parser.MessageType; +import propertiesparser.parser.MessageType.CompoundType; +import propertiesparser.parser.MessageType.CustomType; +import propertiesparser.parser.MessageType.SimpleType; +import propertiesparser.parser.MessageType.UnionType; +import propertiesparser.parser.MessageType.Visitor; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.TreeSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Properties; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ClassGenerator { + + /** Empty string - used to generate indentation padding. */ + private final static String INDENT_STRING = " "; + + /** Default indentation step. */ + private final static int INDENT_WIDTH = 4; + + /** File-backed property file containing basic code stubs. */ + static Properties stubs; + + static { + //init properties from file + stubs = new Properties(); + String resourcePath = "/propertiesparser/resources/templates.properties"; + try (InputStream in = ClassGenerator.class.getResourceAsStream(resourcePath)) { + stubs.load(in); + } catch (IOException ex) { + throw new AssertionError(ex); + } + } + + /** + * Supported stubs in the property file. + */ + enum StubKind { + TOPLEVEL("toplevel.decl"), + FACTORY_CLASS("nested.decl"), + IMPORT("import.decl"), + FACTORY_METHOD_DECL("factory.decl.method"), + FACTORY_METHOD_ARG("factory.decl.method.arg"), + FACTORY_METHOD_BODY("factory.decl.method.body"), + FACTORY_FIELD("factory.decl.field"), + WILDCARDS_EXTENDS("wildcards.extends"), + SUPPRESS_WARNINGS("suppress.warnings"); + + /** stub key (as it appears in the property file) */ + String key; + + StubKind(String key) { + this.key = key; + } + + /** + * Subst a list of arguments into a given stub. + */ + String format(Object... args) { + return MessageFormat.format((String)stubs.get(key), args); + } + } + + /** + * Nested factory class kind. There are multiple sub-factories, one for each kind of commonly used + * diagnostics (i.e. error, warnings, note, fragment). An additional category is defined for + * those resource keys whose prefix doesn't match any predefined category. + */ + enum FactoryKind { + ERR("err", "Error", "Errors"), + WARN("warn", "Warning", "Warnings"), + NOTE("note", "Note", "Notes"), + MISC("misc", "Fragment", "Fragments"), + OTHER(null, null, null); + + /** The prefix for this factory kind (i.e. 'err'). */ + String prefix; + + /** The type of the factory method/fields in this class. */ + String keyClazz; + + /** The class name to be used for this factory. */ + String factoryClazz; + + FactoryKind(String prefix, String keyClazz, String factoryClazz) { + this.prefix = prefix; + this.keyClazz = keyClazz; + this.factoryClazz = factoryClazz; + } + + /** + * Utility method for parsing a factory kind from a resource key prefix. + */ + static FactoryKind parseFrom(String prefix) { + for (FactoryKind k : FactoryKind.values()) { + if (k.prefix == null || k.prefix.equals(prefix)) { + return k; + } + } + return null; + } + } + + /** + * Main entry-point: generate a Java enum-like set of nested factory classes into given output + * folder. The factories are populated as mandated by the comments in the input resource file. + */ + public void generateFactory(MessageFile messageFile, File outDir) { + Map>> groupedEntries = + messageFile.messages.entrySet().stream() + .collect(Collectors.groupingBy(e -> FactoryKind.parseFrom(e.getKey().split("\\.")[1]))); + //generate nested classes + List nestedDecls = new ArrayList<>(); + Set importedTypes = new TreeSet<>(); + for (Map.Entry>> entry : groupedEntries.entrySet()) { + if (entry.getKey() == FactoryKind.OTHER) continue; + //emit members + String members = entry.getValue().stream() + .flatMap(e -> generateFactoryMethodsAndFields(e.getKey(), e.getValue()).stream()) + .collect(Collectors.joining("\n\n")); + //emit nested class + String factoryDecl = + StubKind.FACTORY_CLASS.format(entry.getKey().factoryClazz, indent(members, 1)); + nestedDecls.add(indent(factoryDecl, 1)); + //add imports + entry.getValue().stream().forEach(e -> + importedTypes.addAll(importedTypes(e.getValue().getMessageInfo().getTypes()))); + } + String clazz = StubKind.TOPLEVEL.format( + packageName(messageFile.file), + String.join("\n", generateImports(importedTypes)), + toplevelName(messageFile.file), + String.join("\n", nestedDecls)); + try (FileWriter fw = new FileWriter(new File(outDir, toplevelName(messageFile.file) + ".java"))) { + fw.append(clazz); + } catch (Throwable ex) { + throw new AssertionError(ex); + } + } + + /** + * Indent a string to a given level. + */ + String indent(String s, int level) { + return Stream.of(s.split("\n")) + .map(sub -> INDENT_STRING.substring(0, level * INDENT_WIDTH) + sub) + .collect(Collectors.joining("\n")); + } + + /** + * Retrieve package part of given file object. + */ + String packageName(File file) { + String path = file.getAbsolutePath(); + int begin = path.indexOf("com" + File.separatorChar); + String packagePath = path.substring(begin, path.lastIndexOf(File.separatorChar)); + String packageName = packagePath.replace(File.separatorChar, '.'); + return packageName; + } + + /** + * Form the name of the toplevel factory class. + */ + public static String toplevelName(File file) { + return Stream.of(file.getName().split("\\.")) + .map(s -> Character.toUpperCase(s.charAt(0)) + s.substring(1)) + .collect(Collectors.joining("")); + } + + /** + * Generate a list of import declarations given a set of imported types. + */ + List generateImports(Set importedTypes) { + List importDecls = new ArrayList<>(); + for (String it : importedTypes) { + importDecls.add(StubKind.IMPORT.format(it)); + } + return importDecls; + } + + /** + * Generate a list of factory methods/fields to be added to a given factory nested class. + */ + List generateFactoryMethodsAndFields(String key, Message msg) { + MessageInfo msgInfo = msg.getMessageInfo(); + List lines = msg.getLines(false); + String javadoc = lines.stream() + .filter(ml -> !ml.isInfo() && !ml.isEmptyOrComment()) + .map(ml -> ml.text) + .collect(Collectors.joining("\n *")); + String[] keyParts = key.split("\\."); + FactoryKind k = FactoryKind.parseFrom(keyParts[1]); + String factoryName = factoryName(key); + if (msgInfo.getTypes().isEmpty()) { + //generate field + String factoryField = StubKind.FACTORY_FIELD.format(k.keyClazz, factoryName, + "\"" + keyParts[0] + "\"", + "\"" + Stream.of(keyParts).skip(2).collect(Collectors.joining(".")) + "\"", + javadoc); + return Collections.singletonList(factoryField); + } else { + //generate method + List factoryMethods = new ArrayList<>(); + for (List msgTypes : normalizeTypes(0, msgInfo.getTypes())) { + List types = generateTypes(msgTypes); + List argNames = argNames(types.size()); + String suppressionString = needsSuppressWarnings(msgTypes) ? + StubKind.SUPPRESS_WARNINGS.format() : ""; + String factoryMethod = StubKind.FACTORY_METHOD_DECL.format(suppressionString, k.keyClazz, + factoryName, argDecls(types, argNames).stream().collect(Collectors.joining(", ")), + indent(StubKind.FACTORY_METHOD_BODY.format(k.keyClazz, + "\"" + keyParts[0] + "\"", + "\"" + Stream.of(keyParts).skip(2).collect(Collectors.joining(".")) + "\"", + argNames.stream().collect(Collectors.joining(", "))), 1), + javadoc); + factoryMethods.add(factoryMethod); + } + return factoryMethods; + } + } + + /** + * Form the name of a factory method/field given a resource key. + */ + String factoryName(String key) { + return Stream.of(key.split("[\\.-]")) + .skip(2) + .map(s -> Character.toUpperCase(s.charAt(0)) + s.substring(1)) + .collect(Collectors.joining("")); + } + + /** + * Generate a formal parameter list given a list of types and names. + */ + List argDecls(List types, List args) { + List argNames = new ArrayList<>(); + for (int i = 0 ; i < types.size() ; i++) { + argNames.add(types.get(i) + " " + args.get(i)); + } + return argNames; + } + + /** + * Generate a list of formal parameter names given a size. + */ + List argNames(int size) { + List argNames = new ArrayList<>(); + for (int i = 0 ; i < size ; i++) { + argNames.add(StubKind.FACTORY_METHOD_ARG.format(i)); + } + return argNames; + } + + /** + * Convert a (normalized) parsed type into a string-based representation of some Java type. + */ + List generateTypes(List msgTypes) { + return msgTypes.stream().map(t -> t.accept(stringVisitor, null)).collect(Collectors.toList()); + } + //where + Visitor stringVisitor = new Visitor() { + @Override + public String visitCustomType(CustomType t, Void aVoid) { + String customType = t.typeString; + return customType.substring(customType.lastIndexOf('.') + 1); + } + + @Override + public String visitSimpleType(SimpleType t, Void aVoid) { + return t.clazz; + } + + @Override + public String visitCompoundType(CompoundType t, Void aVoid) { + return StubKind.WILDCARDS_EXTENDS.format(t.kind.clazz.clazz, + t.elemtype.accept(this, null)); + } + + @Override + public String visitUnionType(UnionType t, Void aVoid) { + throw new AssertionError("Union types should have been denormalized!"); + } + }; + + /** + * See if any of the parsed types in the given list needs warning suppression. + */ + boolean needsSuppressWarnings(List msgTypes) { + return msgTypes.stream().anyMatch(t -> t.accept(suppressWarningsVisitor, null)); + } + //where + Visitor suppressWarningsVisitor = new Visitor() { + @Override + public Boolean visitCustomType(CustomType t, Void aVoid) { + //play safe + return true; + } + @Override + public Boolean visitSimpleType(SimpleType t, Void aVoid) { + switch (t) { + case LIST: + case SET: + return true; + default: + return false; + } + } + + @Override + public Boolean visitCompoundType(CompoundType t, Void aVoid) { + return t.elemtype.accept(this, null); + } + + @Override + public Boolean visitUnionType(UnionType t, Void aVoid) { + return needsSuppressWarnings(Arrays.asList(t.choices)); + } + }; + + /** + * Retrieve a list of types that need to be imported, so that the factory body can refer + * to the types in the given list using simple names. + */ + Set importedTypes(List msgTypes) { + Set imports = new TreeSet<>(); + msgTypes.forEach(t -> t.accept(importVisitor, imports)); + return imports; + } + //where + Visitor> importVisitor = new Visitor>() { + @Override + public Void visitCustomType(CustomType t, Set imports) { + imports.add(t.typeString); + return null; + } + + @Override + public Void visitSimpleType(SimpleType t, Set imports) { + if (t.qualifier != null) { + imports.add(t.qualifier + "." + t.clazz); + } + return null; + } + + @Override + public Void visitCompoundType(CompoundType t, Set imports) { + visitSimpleType(t.kind.clazz, imports); + t.elemtype.accept(this, imports); + return null; + } + + @Override + public Void visitUnionType(UnionType t, Set imports) { + Stream.of(t.choices).forEach(c -> c.accept(this, imports)); + return null; + } + }; + + /** + * Normalize parsed types in a comment line. If one or more types in the line contains alternatives, + * this routine generate a list of 'overloaded' normalized signatures. + */ + List> normalizeTypes(int idx, List msgTypes) { + if (msgTypes.size() == idx) return Collections.singletonList(Collections.emptyList()); + MessageType head = msgTypes.get(idx); + List> buf = new ArrayList<>(); + for (MessageType alternative : head.accept(normalizeVisitor, null)) { + for (List rest : normalizeTypes(idx + 1, msgTypes)) { + List temp = new ArrayList<>(rest); + temp.add(0, alternative); + buf.add(temp); + } + } + return buf; + } + //where + Visitor, Void> normalizeVisitor = new Visitor, Void>() { + @Override + public List visitCustomType(CustomType t, Void aVoid) { + return Collections.singletonList(t); + } + + @Override + public List visitSimpleType(SimpleType t, Void aVoid) { + return Collections.singletonList(t); + } + + @Override + public List visitCompoundType(CompoundType t, Void aVoid) { + return t.elemtype.accept(this, null).stream() + .map(nt -> new CompoundType(t.kind, nt)) + .collect(Collectors.toList()); + } + + @Override + public List visitUnionType(UnionType t, Void aVoid) { + return Stream.of(t.choices) + .flatMap(t2 -> t2.accept(this, null).stream()) + .collect(Collectors.toList()); + } + }; +} diff -r 8eeea699174c -r 1633de6070ae langtools/make/tools/propertiesparser/parser/Message.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/make/tools/propertiesparser/parser/Message.java Thu Jan 08 14:43:05 2015 +0000 @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package propertiesparser.parser; + +import java.util.ArrayList; +import java.util.List; + +/** + * A message within the message file. + * A message is a series of lines containing a "name=value" property, + * optionally preceded by a comment describing the use of placeholders + * such as {0}, {1}, etc within the property value. + */ +public final class Message { + final MessageLine firstLine; + private MessageInfo messageInfo; + + Message(MessageLine l) { + firstLine = l; + } + + /** + * Get the Info object for this message. It may be empty if there + * if no comment preceding the property specification. + */ + public MessageInfo getMessageInfo() { + if (messageInfo == null) { + MessageLine l = firstLine.prev; + if (l != null && l.isInfo()) + messageInfo = new MessageInfo(l.text); + else + messageInfo = MessageInfo.dummyInfo; + } + return messageInfo; + } + + /** + * Get all the lines pertaining to this message. + */ + public List getLines(boolean includeAllPrecedingComments) { + List lines = new ArrayList<>(); + MessageLine l = firstLine; + if (includeAllPrecedingComments) { + // scan back to find end of prev message + while (l.prev != null && l.prev.isEmptyOrComment()) + l = l.prev; + // skip leading blank lines + while (l.text.isEmpty()) + l = l.next; + } else { + if (l.prev != null && l.prev.isInfo()) + l = l.prev; + } + + // include any preceding lines + for ( ; l != firstLine; l = l.next) + lines.add(l); + + // include message lines + for (l = firstLine; l != null && l.hasContinuation(); l = l.next) + lines.add(l); + lines.add(l); + + // include trailing blank line if present + l = l.next; + if (l != null && l.text.isEmpty()) + lines.add(l); + + return lines; + } +} diff -r 8eeea699174c -r 1633de6070ae langtools/make/tools/propertiesparser/parser/MessageFile.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/make/tools/propertiesparser/parser/MessageFile.java Thu Jan 08 14:43:05 2015 +0000 @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package propertiesparser.parser; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; + +/** + * Class to facilitate manipulating compiler.properties. + */ +public class MessageFile { + + MessageLine firstLine; + public Map messages = new TreeMap<>(); + public File file; + public String keyPrefix; + + public MessageFile(File file, String keyPrefix) throws IOException { + this.file = file; + this.keyPrefix = keyPrefix; + read(file); + } + + final void read(File in) throws IOException { + MessageLine currLine = null; + for (String line : Files.readAllLines(in.toPath())) { + if (currLine == null) + firstLine = currLine = new MessageLine(line); + else + currLine = currLine.append(line); + if (line.startsWith(keyPrefix + ".")) { + int eq = line.indexOf("="); + if (eq > 0) + messages.put(line.substring(0, eq), new Message(currLine)); + } + } + } +} diff -r 8eeea699174c -r 1633de6070ae langtools/make/tools/propertiesparser/parser/MessageInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/make/tools/propertiesparser/parser/MessageInfo.java Thu Jan 08 14:43:05 2015 +0000 @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package propertiesparser.parser; + +import propertiesparser.parser.MessageType.CompoundType; +import propertiesparser.parser.MessageType.OrType; +import propertiesparser.parser.MessageType.SimpleType; +import propertiesparser.parser.MessageType.UnionType; + +import java.util.ArrayList; +import java.util.List; + +/** + * An object to represent the comment that may precede the property + * specification in a Message. + * The comment is modelled as a list of fields, where the fields correspond + * to the placeholder values (e.g. {0}, {1}, etc) within the message value. + */ +public final class MessageInfo { + + /** The fields of the Info object. */ + List types = new ArrayList<>(); + + MessageInfo(String text) throws IllegalArgumentException { + if (text != null) { + if (!text.startsWith("# ")) + throw new IllegalArgumentException(); + String[] segs = text.substring(2).split(", "); + types = new ArrayList<>(); + for (String seg : segs) { + types.add(parseType(seg)); + } + } + } + + public List getTypes() { + return types; + } + + boolean isEmpty() { + return types.isEmpty(); + } + + @Override + public String toString() { + return types.toString(); + } + + /** + * Split the type comment into multiple alternatives (separated by 'or') - then parse each of them + * individually and form an 'or' type. + */ + MessageType parseType(String text) { + int commentStart = text.indexOf("("); + if (commentStart != -1) { + //remove optional comment + text = text.substring(0, commentStart); + } + text = text.substring(text.indexOf(": ") + 2); + String[] alternatives = text.split(" " + OrType.OR_NAME + " "); + MessageType[] types = new MessageType[alternatives.length]; + for (int i = 0 ; i < alternatives.length ; i++) { + types[i] = parseAlternative(alternatives[i].trim()); + } + return types.length > 1 ? + new OrType(types) : types[0]; + } + + /** + * Parse a subset of the type comment; valid matches are simple types, compound types, + * union types and custom types. + */ + MessageType parseAlternative(String text) { + //try with custom types + if (text.charAt(0) == '\'') { + int end = text.indexOf('\'', 1); + return new MessageType.CustomType(text.substring(1, end)); + } + //try with simple types + for (SimpleType st : SimpleType.values()) { + if (text.equals(st.kindName())) { + return st; + } + } + //try with compound types + for (CompoundType.Kind ck : CompoundType.Kind.values()) { + if (text.startsWith(ck.kindName)) { + MessageType elemtype = parseAlternative(text.substring(ck.kindName.length() + 1).trim()); + return new CompoundType(ck, elemtype); + } + } + //try with union types + for (UnionType.Kind uk : UnionType.Kind.values()) { + if (text.startsWith(uk.kindName)) { + return new UnionType(uk); + } + } + //no match - report a warning + System.err.println("WARNING - unrecognized type: " + text); + return SimpleType.UNKNOWN; + } + + /** Dummy message info to be used when no resource key comment is available. */ + static final MessageInfo dummyInfo = new MessageInfo(null); +} diff -r 8eeea699174c -r 1633de6070ae langtools/make/tools/propertiesparser/parser/MessageLine.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/make/tools/propertiesparser/parser/MessageLine.java Thu Jan 08 14:43:05 2015 +0000 @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package propertiesparser.parser; + +import java.util.regex.Pattern; + +/** + * A line of text within the message file. + * The lines form a doubly linked list for simple navigation. + */ +public class MessageLine { + + static final Pattern emptyOrCommentPattern = Pattern.compile("( *#.*)?"); + static final Pattern typePattern = Pattern.compile("[-\\\\'A-Z\\.a-z ]+( \\([A-Za-z 0-9]+\\))?"); + static final Pattern infoPattern = Pattern.compile(String.format("# ([0-9]+: %s, )*[0-9]+: %s", + typePattern.pattern(), typePattern.pattern())); + + public String text; + MessageLine prev; + MessageLine next; + + MessageLine(String text) { + this.text = text; + } + + public boolean isEmptyOrComment() { + return emptyOrCommentPattern.matcher(text).matches(); + } + + public boolean isInfo() { + return infoPattern.matcher(text).matches(); + } + + boolean hasContinuation() { + return (next != null) && text.endsWith("\\"); + } + + MessageLine append(String text) { + MessageLine l = new MessageLine(text); + append(l); + return l; + } + + void append(MessageLine l) { + assert l.prev == null && l.next == null; + l.prev = this; + l.next = next; + if (next != null) { + next.prev = l; + } + next = l; + } +} diff -r 8eeea699174c -r 1633de6070ae langtools/make/tools/propertiesparser/parser/MessageType.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/make/tools/propertiesparser/parser/MessageType.java Thu Jan 08 14:43:05 2015 +0000 @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package propertiesparser.parser; + +/** + * Common interface to all kinds of diagnostic argument types. + */ +public interface MessageType { + + /** + * Visitor method. + */ + R accept(Visitor v, A a); + + /** + * The type as mentioned in the resource file. + */ + String kindName(); + + /** + * A custom type is a type for which a predefined alternative does not exist. As such, it is an + * handy option when prototyping - but usages of custom types should be avoided in product-quality + * resource file comments. + * + * Example: 'com.sun.tools.javac.code.Flags.Flag' + */ + public static class CustomType implements MessageType { + + /** The string-based representation of this type. */ + public String typeString; + + public CustomType(String typeString) { + this.typeString = typeString; + } + + @Override + public String kindName() { + return typeString; + } + + @Override + public R accept(Visitor v, A a) { + return v.visitCustomType(this, a); + } + } + + /** + * A predefined type. All common types mentioned in the resource file comments are meant to + * be included here. + */ + public enum SimpleType implements MessageType { + + BOOLEAN("boolean", "boolean", null), + FRAGMENT("fragment", "Fragment", null), + DIAGNOSTIC("diagnostic", "JCDiagnostic", "com.sun.tools.javac.util"), + MODIFIER("modifier", "Modifier", "javax.lang.model.element"), + FILE("file", "File", "java.io"), + FILE_OBJECT("file object", "JavaFileObject", "javax.tools"), + NAME("name", "Name", "com.sun.tools.javac.util"), + NUMBER("number", "int", null), + OPTION_NAME("option name", "Option", "com.sun.tools.javac.main"), + SOURCE_VERSION("source version", "Source", "com.sun.tools.javac.code"), + STRING("string", "String", null), + SYMBOL("symbol", "Symbol", "com.sun.tools.javac.code"), + SYMBOL_KIND("symbol kind", "Kind", "com.sun.tools.javac.code.Kinds"), + KIND_NAME("kind name", "KindName", "com.sun.tools.javac.code.Kinds"), + TOKEN("token", "TokenKind", "com.sun.tools.javac.parser.Tokens"), + TYPE("type", "Type", "com.sun.tools.javac.code"), + SET("set", "Set", "java.util"), + LIST("list", "List", "java.util"), + OBJECT("object", "Object", null), + UNUSED("unused", "Void", null), + UNKNOWN("", "UnknownType", null); + + /** name of the predefined type as mentioned in the resource file. */ + public final String kindName; + + /** string-based representation of the type */ + public final String clazz; + + /** type qualifier (might be null) */ + public final String qualifier; + + SimpleType(String kindName, String clazz, String qualifier) { + this.kindName = kindName; + this.clazz = clazz; + this.qualifier = qualifier; + } + + @Override + public String kindName() { + return kindName; + } + + @Override + public R accept(Visitor v, A a) { + return v.visitSimpleType(this, a); + } + } + + /** + * A compound type is a collection of some element type. + * + * Example: list of string + */ + public static class CompoundType implements MessageType { + + /** + * Compound type kind. + */ + public enum Kind { + LIST("list of", SimpleType.LIST), + SET("set of", SimpleType.SET); + + public final String kindName; + public final SimpleType clazz; + + Kind(String kindName, SimpleType clazz) { + this.kindName = kindName; + this.clazz = clazz; + } + } + + /** The compound type kind. */ + public final Kind kind; + + /** The element type. */ + public final MessageType elemtype; + + public CompoundType(Kind kind, MessageType elemtype) { + this.kind = kind; + this.elemtype = elemtype; + } + + @Override + public String kindName() { + return kind.kindName; + } + + @Override + public R accept(Visitor v, A a) { + return v.visitCompoundType(this, a); + } + } + + /** + * A union type represents an alternative between two (or more) types. It can be useful to + * define the type of an argument which can assume multiple (unrelated) values; union types + * are only meant to be used in cases where the alternative comes up frequently enough in the + * resource file comments - in order to avoid cluttered comments. + * + * Example: message segment + */ + public static class UnionType implements MessageType { + + /** + * Union type kind. + */ + public enum Kind { + MESSAGE_SEGMENT("message segment", SimpleType.DIAGNOSTIC, SimpleType.FRAGMENT), + FILE_NAME("file name", SimpleType.FILE, SimpleType.FILE_OBJECT); + + final String kindName; + final SimpleType[] choices; + + Kind(String kindName, SimpleType... choices) { + this.kindName = kindName; + this.choices = choices; + } + } + + /** The union type kind. */ + public final Kind kind; + + /** The union type alternatives. */ + public final MessageType[] choices; + + UnionType(Kind kind) { + this(kind, kind.choices); + } + + protected UnionType(Kind kind, MessageType[] choices) { + this.choices = choices; + this.kind = kind; + } + + @Override + public String kindName() { + return kind.kindName; + } + + @Override + public R accept(Visitor v, A a) { + return v.visitUnionType(this, a); + } + } + + /** + * A subclass of union type representing 'explicit' alternatives in the resource file comments. + * Note: as the token 'or' is parsed with lowest priority, it is not possible, for instance, + * to form a compound type out of an 'or' type. In such cases a plain union type should be used + * instead. + * + * Examples: symbol or type + */ + public static class OrType extends UnionType { + + public static final String OR_NAME = "or"; + + @Override + public String kindName() { + return OR_NAME; + } + + public OrType(MessageType... choices) { + super(null, choices); + } + } + + /** + * Visitor class. + */ + public static abstract class Visitor { + public abstract R visitCustomType(CustomType t, A a); + public abstract R visitSimpleType(SimpleType t, A a); + public abstract R visitCompoundType(CompoundType t, A a); + public abstract R visitUnionType(UnionType t, A a); + } +} diff -r 8eeea699174c -r 1633de6070ae langtools/make/tools/propertiesparser/resources/templates.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/make/tools/propertiesparser/resources/templates.properties Thu Jan 08 14:43:05 2015 +0000 @@ -0,0 +1,48 @@ +toplevel.decl=\ + package {0};\n\ + \n\ + {1}\n\ + import com.sun.tools.javac.util.JCDiagnostic.Error;\n\ + import com.sun.tools.javac.util.JCDiagnostic.Warning;\n\ + import com.sun.tools.javac.util.JCDiagnostic.Note;\n\ + import com.sun.tools.javac.util.JCDiagnostic.Fragment;\n\ + \n\ + public class {2} '{'\n\ + {3}\n\ + '}'\n + +import.decl=\ + import {0}; + +nested.decl =\ + public static class {0} '{'\n\ + {1}\n\ + '}' + +factory.decl.method=\ + /**\n\ + ' '* {5}\n\ + ' '*/\n\ + {0}public static {1} {2}({3}) '{'\n\ + {4}\n\ + '}' + +factory.decl.method.arg=\ + arg{0} + +factory.decl.method.body=\ + return new {0}({1}, {2}, {3}); + +factory.decl.field=\ + /**\n\ + ' '* {4}\n\ + ' '*/\n\ + public static final {0} {1} = new {0}({2}, {3}); + +wildcards.extends=\ + {0} + +suppress.warnings=\ + @SuppressWarnings("rawtypes")\n + + diff -r 8eeea699174c -r 1633de6070ae langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties Thu Jan 08 14:43:05 2015 +0000 @@ -1,5 +1,6 @@ + # -# Copyright (c) 1999, 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -26,30 +27,50 @@ # Messages in this file which use "placeholders" for values (e.g. {0}, {1}) # are preceded by a stylized comment describing the type of the corresponding # values. -# The types currently in use are +# The simple types currently in use are: # # boolean true or false -# file name the name of an input file; e.g. MyFile.java -# message segment a sub-message; see compiler.misc.* +# diagnostic a sub-message; see compiler.misc.* +# fragment similar to 'message segment', but with more specific type # modifier a Java modifier; e.g. public, private, protected +# file a file URL +# file object a file URL - similar to 'file' but typically used for source/class files, hence more specific # name a name, typically a Java identifier # number an integer # option name the name of a command line option # source version a source version number, such as 1.5, 1.6, 1.7 # string a general string # symbol the name of a declared type -# symbol kind a description of the kind of a declaration; see compiler.misc.kindname.* +# symbol kind the kind of a symbol (i.e. method, variable) +# kind name an informative description of the kind of a declaration; see compiler.misc.kindname.* # token the name of a non-terminal in source code; see compiler.misc.token.* # type a Java type; e.g. int, X, X +# object a Java object (unspecified) # unused the value is not used in this message # +# The following compound types are also used: +# # list of X a comma-separated list of items; e.g. list of type -# X or Y alternation; e.g. message segment or type # set of X a comma-separated collection of items; e.g. set of modifier # -# These may be composed: e.g. list of type or message segment +# These may be composed: +# +# list of type or message segment +# +# The following type aliases are supported: +# +# message segment --> diagnostic or fragment +# file name --> file or file object # -# These comments are verified by the jtreg test test/tools/javac/diags/MessageInfo, +# Custom comments are supported in parenthesis i.e. +# +# number (classfile major version) +# +# These comments are used internally in order to generate an enum-like class declaration containing +# a method/field for each of the diagnostic keys listed here. Those methods/fields can then be used +# by javac code to build diagnostics in a type-safe fashion. +# +# In addition, these comments are verified by the jtreg test test/tools/javac/diags/MessageInfo, # using info derived from the collected set of examples in test/tools/javac/diags/examples. # MessageInfo can also be run as a standalone utility providing more facilities # for manipulating this file. For more details, see MessageInfo.java. @@ -140,7 +161,7 @@ compiler.err.attribute.value.must.be.constant=\ element value must be a constant expression -# 0: statement type +# 0: string (statement type) compiler.err.bad.initializer=\ bad initializer for {0} @@ -360,7 +381,7 @@ compiler.err.invalid.repeatable.annotation.invalid.value=\ {0} is not a valid @Repeatable: invalid value element -# 0: symbol type, 1: unused, 2: type +# 0: symbol or type, 1: unused, 2: type compiler.err.invalid.repeatable.annotation.value.return=\ containing annotation type ({0}) must declare an element named ''value'' of type {2} @@ -832,7 +853,7 @@ # Errors related to annotation processing -# 0: symbol, 1: string, 2: stack-trace +# 0: symbol, 1: string, 2: string (stack-trace) compiler.err.proc.cant.access=\ cannot access {0}\n\ {1}\n\ @@ -984,15 +1005,15 @@ compiler.err.types.incompatible.diff.ret=\ types {0} and {1} are incompatible; both define {2}, but with unrelated return types -# 0: kind, 1: type, 2: name, 3: list of type, 4: symbol, 5: symbol +# 0: kind name, 1: type, 2: name, 3: list of type, 4: symbol, 5: symbol compiler.err.types.incompatible.unrelated.defaults=\ {0} {1} inherits unrelated defaults for {2}({3}) from types {4} and {5} -# 0: kind, 1: type, 2: name, 3: list of type, 4: symbol, 5: symbol +# 0: kind name, 1: type, 2: name, 3: list of type, 4: symbol, 5: symbol compiler.err.types.incompatible.abstract.default=\ {0} {1} inherits abstract and default for {2}({3}) from types {4} and {5} -# 0: name, 1: kind, 2: symbol +# 0: name, 1: kind name, 2: symbol compiler.err.default.overrides.object.member=\ default method {0} in {1} {2} overrides a member of java.lang.Object @@ -1747,11 +1768,11 @@ cannot access {0}\n\ {1} -# 0: class name +# 0: name compiler.misc.bad.class.file=\ class file is invalid for class {0} -# 0: file name, 1: expected CP entry type, 2: constant pool index +# 0: file name, 1: string (expected constant pool entry type), 2: number (constant pool index) compiler.misc.bad.const.pool.entry=\ bad constant pool entry in {0}\n\ expected {1} at index {2} @@ -1802,11 +1823,11 @@ compiler.misc.class.file.not.found=\ class file for {0} not found -# 0: classfile major version, 1: classfile minor version +# 0: string (classfile major version), 1: string (classfile minor version) compiler.misc.invalid.default.interface=\ default method found in version {0}.{1} classfile -# 0: classfile major version, 1: classfile minor version +# 0: string (classfile major version), 1: string (classfile minor version) compiler.misc.invalid.static.interface=\ static method found in version {0}.{1} classfile @@ -2422,7 +2443,7 @@ compiler.misc.partial.inst.sig=\ partially instantiated to: {0} -# 0: name, 1: symbol, 2: number, 3: MethodResolutionPhase, 4: list of type or message segment, 5: list of type or message segment +# 0: name, 1: symbol, 2: number, 3: string (method resolution phase), 4: list of type or message segment, 5: list of type or message segment compiler.note.verbose.resolve.multi=\ resolving method {0} in type {1} to candidate {2}\n\ phase: {3}\n\ @@ -2430,7 +2451,7 @@ with type-args: {5}\n\ candidates: -# 0: name, 1: symbol, 2: unused, 3: MethodResolutionPhase, 4: list of type or message segment, 5: list of type or message segment +# 0: name, 1: symbol, 2: unused, 3: string (method resolution phase), 4: list of type or message segment, 5: list of type or message segment compiler.note.verbose.resolve.multi.1=\ erroneous resolution for method {0} in type {1}\n\ phase: {3}\n\ diff -r 8eeea699174c -r 1633de6070ae langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java Thu Jan 08 14:43:05 2015 +0000 @@ -329,7 +329,7 @@ DCErroneous(String body, JCDiagnostic.Factory diags, DiagnosticSource diagSource, String code, Object... args) { this.body = body; - this.diag = diags.error(diagSource, this, code, args); + this.diag = diags.error(null, diagSource, this, code, args); } @Override @DefinedBy(Api.COMPILER_TREE) diff -r 8eeea699174c -r 1633de6070ae langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/AbstractLog.java --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/AbstractLog.java Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/AbstractLog.java Thu Jan 08 14:43:05 2015 +0000 @@ -31,6 +31,9 @@ import com.sun.tools.javac.code.Lint.LintCategory; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag; +import com.sun.tools.javac.util.JCDiagnostic.Error; +import com.sun.tools.javac.util.JCDiagnostic.Note; +import com.sun.tools.javac.util.JCDiagnostic.Warning; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.JCDiagnostic.SimpleDiagnosticPosition; @@ -93,7 +96,15 @@ * @param args Fields of the error message. */ public void error(String key, Object ... args) { - report(diags.error(source, null, key, args)); + error(diags.errorKey(key, args)); + } + + /** Report an error, unless another error was already reported at same + * source position. + * @param errorKey The key for the localized error message. + */ + public void error(Error errorKey) { + report(diags.error(null, source, null, errorKey)); } /** Report an error, unless another error was already reported at same @@ -102,8 +113,17 @@ * @param key The key for the localized error message. * @param args Fields of the error message. */ - public void error(DiagnosticPosition pos, String key, Object ... args) { - report(diags.error(source, pos, key, args)); + public void error(DiagnosticPosition pos, String key, Object... args) { + error(pos, diags.errorKey(key, args)); + } + + /** Report an error, unless another error was already reported at same + * source position. + * @param pos The source position at which to report the error. + * @param errorKey The key for the localized error message. + */ + public void error(DiagnosticPosition pos, Error errorKey) { + report(diags.error(null, source, pos, errorKey)); } /** Report an error, unless another error was already reported at same @@ -114,9 +134,17 @@ * @param args Fields of the error message. */ public void error(DiagnosticFlag flag, DiagnosticPosition pos, String key, Object ... args) { - JCDiagnostic d = diags.error(source, pos, key, args); - d.setFlag(flag); - report(d); + error(flag, pos, diags.errorKey(key, args)); + } + + /** Report an error, unless another error was already reported at same + * source position. + * @param flag A flag to set on the diagnostic + * @param pos The source position at which to report the error. + * @param errorKey The key for the localized error message. + */ + public void error(DiagnosticFlag flag, DiagnosticPosition pos, Error errorKey) { + report(diags.error(flag, source, pos, errorKey)); } /** Report an error, unless another error was already reported at same @@ -126,7 +154,16 @@ * @param args Fields of the error message. */ public void error(int pos, String key, Object ... args) { - report(diags.error(source, wrap(pos), key, args)); + error(pos, diags.errorKey(key, args)); + } + + /** Report an error, unless another error was already reported at same + * source position. + * @param pos The source position at which to report the error. + * @param errorKey The key for the localized error message. + */ + public void error(int pos, Error errorKey) { + report(diags.error(null, source, wrap(pos), errorKey)); } /** Report an error, unless another error was already reported at same @@ -137,9 +174,17 @@ * @param args Fields of the error message. */ public void error(DiagnosticFlag flag, int pos, String key, Object ... args) { - JCDiagnostic d = diags.error(source, wrap(pos), key, args); - d.setFlag(flag); - report(d); + error(flag, pos, diags.errorKey(key, args)); + } + + /** Report an error, unless another error was already reported at same + * source position. + * @param flag A flag to set on the diagnostic + * @param pos The source position at which to report the error. + * @param errorKey The key for the localized error message. + */ + public void error(DiagnosticFlag flag, int pos, Error errorKey) { + report(diags.error(flag, source, wrap(pos), errorKey)); } /** Report a warning, unless suppressed by the -nowarn option or the @@ -148,7 +193,15 @@ * @param args Fields of the warning message. */ public void warning(String key, Object ... args) { - report(diags.warning(source, null, key, args)); + warning(diags.warningKey(key, args)); + } + + /** Report a warning, unless suppressed by the -nowarn option or the + * maximum number of warnings has been reached. + * @param warningKey The key for the localized warning message. + */ + public void warning(Warning warningKey) { + report(diags.warning(null, source, null, warningKey)); } /** Report a lint warning, unless suppressed by the -nowarn option or the @@ -158,7 +211,16 @@ * @param args Fields of the warning message. */ public void warning(LintCategory lc, String key, Object ... args) { - report(diags.warning(lc, key, args)); + warning(lc, diags.warningKey(key, args)); + } + + /** Report a lint warning, unless suppressed by the -nowarn option or the + * maximum number of warnings has been reached. + * @param lc The lint category for the diagnostic + * @param warningKey The key for the localized warning message. + */ + public void warning(LintCategory lc, Warning warningKey) { + report(diags.warning(lc, null, null, warningKey)); } /** Report a warning, unless suppressed by the -nowarn option or the @@ -168,7 +230,16 @@ * @param args Fields of the warning message. */ public void warning(DiagnosticPosition pos, String key, Object ... args) { - report(diags.warning(source, pos, key, args)); + warning(pos, diags.warningKey(key, args)); + } + + /** Report a warning, unless suppressed by the -nowarn option or the + * maximum number of warnings has been reached. + * @param pos The source position at which to report the warning. + * @param warningKey The key for the localized warning message. + */ + public void warning(DiagnosticPosition pos, Warning warningKey) { + report(diags.warning(null, source, pos, warningKey)); } /** Report a lint warning, unless suppressed by the -nowarn option or the @@ -179,7 +250,17 @@ * @param args Fields of the warning message. */ public void warning(LintCategory lc, DiagnosticPosition pos, String key, Object ... args) { - report(diags.warning(lc, source, pos, key, args)); + warning(lc, pos, diags.warningKey(key, args)); + } + + /** Report a lint warning, unless suppressed by the -nowarn option or the + * maximum number of warnings has been reached. + * @param lc The lint category for the diagnostic + * @param pos The source position at which to report the warning. + * @param warningKey The key for the localized warning message. + */ + public void warning(LintCategory lc, DiagnosticPosition pos, Warning warningKey) { + report(diags.warning(lc, source, pos, warningKey)); } /** Report a warning, unless suppressed by the -nowarn option or the @@ -189,7 +270,16 @@ * @param args Fields of the warning message. */ public void warning(int pos, String key, Object ... args) { - report(diags.warning(source, wrap(pos), key, args)); + warning(pos, diags.warningKey(key, args)); + } + + /** Report a warning, unless suppressed by the -nowarn option or the + * maximum number of warnings has been reached. + * @param pos The source position at which to report the warning. + * @param warningKey The key for the localized warning message. + */ + public void warning(int pos, Warning warningKey) { + report(diags.warning(null, source, wrap(pos), warningKey)); } /** Report a warning. @@ -198,7 +288,15 @@ * @param args Fields of the warning message. */ public void mandatoryWarning(DiagnosticPosition pos, String key, Object ... args) { - report(diags.mandatoryWarning(source, pos, key, args)); + mandatoryWarning(pos, diags.warningKey(key, args)); + } + + /** Report a warning. + * @param pos The source position at which to report the warning. + * @param warningKey The key for the localized warning message. + */ + public void mandatoryWarning(DiagnosticPosition pos, Warning warningKey) { + report(diags.mandatoryWarning(null, source, pos, warningKey)); } /** Report a warning. @@ -208,7 +306,16 @@ * @param args Fields of the warning message. */ public void mandatoryWarning(LintCategory lc, DiagnosticPosition pos, String key, Object ... args) { - report(diags.mandatoryWarning(lc, source, pos, key, args)); + mandatoryWarning(lc, pos, diags.warningKey(key, args)); + } + + /** Report a warning. + * @param lc The lint category for the diagnostic + * @param pos The source position at which to report the warning. + * @param warningKey The key for the localized warning message. + */ + public void mandatoryWarning(LintCategory lc, DiagnosticPosition pos, Warning warningKey) { + report(diags.mandatoryWarning(lc, source, pos, warningKey)); } /** Provide a non-fatal notification, unless suppressed by the -nowarn option. @@ -216,7 +323,14 @@ * @param args Fields of the notint an error or warning message: */ public void note(String key, Object ... args) { - report(diags.note(source, null, key, args)); + note(diags.noteKey(key, args)); + } + + /** Provide a non-fatal notification, unless suppressed by the -nowarn option. + * @param noteKey The key for the localized notification message. + */ + public void note(Note noteKey) { + report(diags.note(source, null, noteKey)); } /** Provide a non-fatal notification, unless suppressed by the -nowarn option. @@ -224,7 +338,14 @@ * @param args Fields of the notification message. */ public void note(DiagnosticPosition pos, String key, Object ... args) { - report(diags.note(source, pos, key, args)); + note(pos, diags.noteKey(key, args)); + } + + /** Provide a non-fatal notification, unless suppressed by the -nowarn option. + * @param noteKey The key for the localized notification message. + */ + public void note(DiagnosticPosition pos, Note noteKey) { + report(diags.note(source, pos, noteKey)); } /** Provide a non-fatal notification, unless suppressed by the -nowarn option. @@ -232,7 +353,14 @@ * @param args Fields of the notification message. */ public void note(int pos, String key, Object ... args) { - report(diags.note(source, wrap(pos), key, args)); + note(pos, diags.noteKey(key, args)); + } + + /** Provide a non-fatal notification, unless suppressed by the -nowarn option. + * @param noteKey The key for the localized notification message. + */ + public void note(int pos, Note noteKey) { + report(diags.note(source, wrap(pos), noteKey)); } /** Provide a non-fatal notification, unless suppressed by the -nowarn option. @@ -240,7 +368,14 @@ * @param args Fields of the notification message. */ public void note(JavaFileObject file, String key, Object ... args) { - report(diags.note(getSource(file), null, key, args)); + note(file, diags.noteKey(key, args)); + } + + /** Provide a non-fatal notification, unless suppressed by the -nowarn option. + * @param noteKey The key for the localized notification message. + */ + public void note(JavaFileObject file, Note noteKey) { + report(diags.note(getSource(file), null, noteKey)); } /** Provide a non-fatal notification, unless suppressed by the -nowarn option. @@ -248,7 +383,14 @@ * @param args Fields of the notification message. */ public void mandatoryNote(final JavaFileObject file, String key, Object ... args) { - report(diags.mandatoryNote(getSource(file), key, args)); + mandatoryNote(file, diags.noteKey(key, args)); + } + + /** Provide a non-fatal notification, unless suppressed by the -nowarn option. + * @param noteKey The key for the localized notification message. + */ + public void mandatoryNote(final JavaFileObject file, Note noteKey) { + report(diags.mandatoryNote(getSource(file), noteKey)); } protected abstract void report(JCDiagnostic diagnostic); diff -r 8eeea699174c -r 1633de6070ae langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/JCDiagnostic.java --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/JCDiagnostic.java Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/JCDiagnostic.java Thu Jan 08 14:43:05 2015 +0000 @@ -28,6 +28,7 @@ import java.util.EnumSet; import java.util.Locale; import java.util.Set; +import java.util.stream.Stream; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; @@ -92,28 +93,30 @@ } /** - * Create an error diagnostic. + * Create an error diagnostic * @param source The source of the compilation unit, if any, in which to report the error. * @param pos The source position at which to report the error. * @param key The key for the localized error message. * @param args Fields of the error message. */ public JCDiagnostic error( - DiagnosticSource source, DiagnosticPosition pos, String key, Object... args) { - return create(ERROR, null, defaultErrorFlags, source, pos, key, args); + DiagnosticFlag flag, DiagnosticSource source, DiagnosticPosition pos, String key, Object... args) { + return error(flag, source, pos, errorKey(key, args)); } /** - * Create a warning diagnostic that will not be hidden by the -nowarn or -Xlint:none options. - * @param source The source of the compilation unit, if any, in which to report the warning. - * @param pos The source position at which to report the warning. - * @param key The key for the localized warning message. - * @param args Fields of the warning message. - * @see MandatoryWarningHandler + * Create an error diagnostic + * @param source The source of the compilation unit, if any, in which to report the error. + * @param pos The source position at which to report the error. + * @param errorKey The key for the localized error message. */ - public JCDiagnostic mandatoryWarning( - DiagnosticSource source, DiagnosticPosition pos, String key, Object... args) { - return create(WARNING, null, EnumSet.of(DiagnosticFlag.MANDATORY), source, pos, key, args); + public JCDiagnostic error( + DiagnosticFlag flag, DiagnosticSource source, DiagnosticPosition pos, Error errorKey) { + JCDiagnostic diag = create(null, defaultErrorFlags, source, pos, errorKey); + if (flag != null) { + diag.setFlag(flag); + } + return diag; } /** @@ -128,31 +131,35 @@ public JCDiagnostic mandatoryWarning( LintCategory lc, DiagnosticSource source, DiagnosticPosition pos, String key, Object... args) { - return create(WARNING, lc, EnumSet.of(DiagnosticFlag.MANDATORY), source, pos, key, args); + return mandatoryWarning(lc, source, pos, warningKey(key, args)); + } + + /** + * Create a warning diagnostic that will not be hidden by the -nowarn or -Xlint:none options. + * @param lc The lint category for the diagnostic + * @param source The source of the compilation unit, if any, in which to report the warning. + * @param pos The source position at which to report the warning. + * @param warningKey The key for the localized warning message. + * @see MandatoryWarningHandler + */ + public JCDiagnostic mandatoryWarning( + LintCategory lc, + DiagnosticSource source, DiagnosticPosition pos, Warning warningKey) { + return create(lc, EnumSet.of(DiagnosticFlag.MANDATORY), source, pos, warningKey); } /** * Create a warning diagnostic. * @param lc The lint category for the diagnostic + * @param source The source of the compilation unit, if any, in which to report the warning. + * @param pos The source position at which to report the warning. * @param key The key for the localized error message. * @param args Fields of the warning message. * @see MandatoryWarningHandler */ public JCDiagnostic warning( - LintCategory lc, String key, Object... args) { - return create(WARNING, lc, EnumSet.noneOf(DiagnosticFlag.class), null, null, key, args); - } - - /** - * Create a warning diagnostic. - * @param source The source of the compilation unit, if any, in which to report the warning. - * @param pos The source position at which to report the warning. - * @param key The key for the localized warning message. - * @param args Fields of the warning message. - */ - public JCDiagnostic warning( - DiagnosticSource source, DiagnosticPosition pos, String key, Object... args) { - return create(WARNING, null, EnumSet.noneOf(DiagnosticFlag.class), source, pos, key, args); + LintCategory lc, DiagnosticSource source, DiagnosticPosition pos, String key, Object... args) { + return warning(lc, source, pos, warningKey(key, args)); } /** @@ -160,23 +167,32 @@ * @param lc The lint category for the diagnostic * @param source The source of the compilation unit, if any, in which to report the warning. * @param pos The source position at which to report the warning. + * @param warningKey The key for the localized warning message. + * @see MandatoryWarningHandler + */ + public JCDiagnostic warning( + LintCategory lc, DiagnosticSource source, DiagnosticPosition pos, Warning warningKey) { + return create(lc, EnumSet.noneOf(DiagnosticFlag.class), source, pos, warningKey); + } + + /** + * Create a note diagnostic that will not be hidden by the -nowarn or -Xlint:none options. + * @param source The source of the compilation unit, if any, in which to report the warning. * @param key The key for the localized warning message. * @param args Fields of the warning message. * @see MandatoryWarningHandler */ - public JCDiagnostic warning( - LintCategory lc, DiagnosticSource source, DiagnosticPosition pos, String key, Object... args) { - return create(WARNING, lc, EnumSet.noneOf(DiagnosticFlag.class), source, pos, key, args); + public JCDiagnostic mandatoryNote(DiagnosticSource source, String key, Object... args) { + return mandatoryNote(source, noteKey(key, args)); } /** * Create a note diagnostic that will not be hidden by the -nowarn or -Xlint:none options. - * @param key The key for the localized message. - * @param args Fields of the message. + * @param noteKey The key for the localized note message. * @see MandatoryWarningHandler */ - public JCDiagnostic mandatoryNote(DiagnosticSource source, String key, Object... args) { - return create(NOTE, null, EnumSet.of(DiagnosticFlag.MANDATORY), source, null, key, args); + public JCDiagnostic mandatoryNote(DiagnosticSource source, Note noteKey) { + return create(null, EnumSet.of(DiagnosticFlag.MANDATORY), source, null, noteKey); } /** @@ -184,20 +200,20 @@ * @param key The key for the localized error message. * @param args Fields of the message. */ - public JCDiagnostic note(String key, Object... args) { - return create(NOTE, null, EnumSet.noneOf(DiagnosticFlag.class), null, null, key, args); + public JCDiagnostic note( + DiagnosticSource source, DiagnosticPosition pos, String key, Object... args) { + return note(source, pos, noteKey(key, args)); } /** * Create a note diagnostic. * @param source The source of the compilation unit, if any, in which to report the note. * @param pos The source position at which to report the note. - * @param key The key for the localized message. - * @param args Fields of the message. + * @param noteKey The key for the localized note message. */ public JCDiagnostic note( - DiagnosticSource source, DiagnosticPosition pos, String key, Object... args) { - return create(NOTE, null, EnumSet.noneOf(DiagnosticFlag.class), source, pos, key, args); + DiagnosticSource source, DiagnosticPosition pos, Note noteKey) { + return create(null, EnumSet.noneOf(DiagnosticFlag.class), source, pos, noteKey); } /** @@ -206,7 +222,15 @@ * @param args Fields of the message. */ public JCDiagnostic fragment(String key, Object... args) { - return create(FRAGMENT, null, EnumSet.noneOf(DiagnosticFlag.class), null, null, key, args); + return fragment(fragmentKey(key, args)); + } + + /** + * Create a fragment diagnostic, for use as an argument in other diagnostics + * @param fragmentKey The key for the localized subdiagnostic message. + */ + public JCDiagnostic fragment(Fragment fragmentKey) { + return create(null, EnumSet.noneOf(DiagnosticFlag.class), null, null, fragmentKey); } /** @@ -220,7 +244,19 @@ */ public JCDiagnostic create( DiagnosticType kind, DiagnosticSource source, DiagnosticPosition pos, String key, Object... args) { - return create(kind, null, EnumSet.noneOf(DiagnosticFlag.class), source, pos, key, args); + return create(null, EnumSet.noneOf(DiagnosticFlag.class), source, pos, DiagnosticInfo.of(kind, prefix, key, args)); + } + + /** + * Create a new diagnostic of the given kind, which is not mandatory and which has + * no lint category. + * @param source The source of the compilation unit, if any, in which to report the message. + * @param pos The source position at which to report the message. + * @param diagnosticInfo The key for the localized message. + */ + public JCDiagnostic create( + DiagnosticSource source, DiagnosticPosition pos, DiagnosticInfo diagnosticInfo) { + return create(null, EnumSet.noneOf(DiagnosticFlag.class), source, pos, diagnosticInfo); } /** @@ -233,13 +269,59 @@ * @param key The key for the localized message. * @param args Fields of the message. */ - public JCDiagnostic create( - DiagnosticType kind, LintCategory lc, Set flags, DiagnosticSource source, DiagnosticPosition pos, String key, Object... args) { - return new JCDiagnostic(formatter, kind, lc, flags, source, pos, qualify(kind, key), args); + public JCDiagnostic create(DiagnosticType kind, + LintCategory lc, Set flags, DiagnosticSource source, DiagnosticPosition pos, String key, Object... args) { + return create(lc, flags, source, pos, DiagnosticInfo.of(kind, prefix, key, args)); } - protected String qualify(DiagnosticType t, String key) { - return prefix + "." + t.key + "." + key; + /** + * Create a new diagnostic with given key. + * @param lc The lint category, if applicable, or null + * @param flags The set of flags for the diagnostic + * @param source The source of the compilation unit, if any, in which to report the message. + * @param pos The source position at which to report the message. + * @param diagnosticInfo The key for the localized message. + */ + public JCDiagnostic create( + LintCategory lc, Set flags, DiagnosticSource source, DiagnosticPosition pos, DiagnosticInfo diagnosticInfo) { + return new JCDiagnostic(formatter, normalize(diagnosticInfo), lc, flags, source, pos); + } + //where + DiagnosticInfo normalize(DiagnosticInfo diagnosticInfo) { + //replace all nested FragmentKey with full-blown JCDiagnostic objects + return DiagnosticInfo.of(diagnosticInfo.type, diagnosticInfo.prefix, diagnosticInfo.code, + Stream.of(diagnosticInfo.args).map(o -> { + return (o instanceof Fragment) ? + fragment((Fragment)o) : o; + }).toArray()); + } + + /** + * Create a new error key. + */ + Error errorKey(String code, Object... args) { + return (Error)DiagnosticInfo.of(ERROR, prefix, code, args); + } + + /** + * Create a new warning key. + */ + Warning warningKey(String code, Object... args) { + return (Warning)DiagnosticInfo.of(WARNING, prefix, code, args); + } + + /** + * Create a new note key. + */ + Note noteKey(String code, Object... args) { + return (Note)DiagnosticInfo.of(NOTE, prefix, code, args); + } + + /** + * Create a new fragment key. + */ + Fragment fragmentKey(String code, Object... args) { + return (Fragment)DiagnosticInfo.of(FRAGMENT, prefix, code, args); } } @@ -254,13 +336,14 @@ @Deprecated public static JCDiagnostic fragment(String key, Object... args) { return new JCDiagnostic(getFragmentFormatter(), - FRAGMENT, + DiagnosticInfo.of(FRAGMENT, + "compiler", + key, + args), null, EnumSet.noneOf(DiagnosticFlag.class), null, - null, - "compiler." + FRAGMENT.key + "." + key, - args); + null); } //where @Deprecated @@ -352,11 +435,9 @@ COMPRESSED } - private final DiagnosticType type; private final DiagnosticSource source; private final DiagnosticPosition position; - private final String key; - protected final Object[] args; + private final DiagnosticInfo diagnosticInfo; private final Set flags; private final LintCategory lintCategory; @@ -391,34 +472,120 @@ } /** + * A diagnostic key object encapsulates basic properties of a diagnostic, such as the resource key, + * the arguments and the kind associated with the diagnostic object. Diagnostic keys can be either + * created programmatically (by using the supplied factory method) or obtained through build-time + * generated factory methods. + */ + public static abstract class DiagnosticInfo { + + /** The diagnostic kind (i.e. error). */ + DiagnosticType type; + + /** The diagnostic prefix (i.e. 'javac'); used to compute full resource key. */ + String prefix; + + /** The diagnostic code (i.e. 'cannot.resolve.sym'); together with {@code prefix} it forms + * the full resource key. */ + String code; + + /** The diagnostic arguments. */ + Object[] args; + + private DiagnosticInfo(DiagnosticType type, String prefix, String code, Object... args) { + this.type = type; + this.prefix = prefix; + this.code = code; + this.args = args; + } + + /** + * Compute the resource key. + */ + public String key() { + return prefix + "." + type.key + "." + code; + } + + /** + * Static factory method; build a custom diagnostic key using given kind, prefix, code and args. + */ + public static DiagnosticInfo of(DiagnosticType type, String prefix, String code, Object... args) { + switch (type) { + case ERROR: + return new Error(prefix, code, args); + case WARNING: + return new Warning(prefix, code, args); + case NOTE: + return new Note(prefix, code, args); + case FRAGMENT: + return new Fragment(prefix, code, args); + default: + Assert.error("Wrong diagnostic type: " + type); + return null; + } + } + + } + + /** + * Class representing error diagnostic keys. + */ + public static final class Error extends DiagnosticInfo { + public Error(String prefix, String key, Object... args) { + super(DiagnosticType.ERROR, prefix, key, args); + } + } + + /** + * Class representing warning diagnostic keys. + */ + public static final class Warning extends DiagnosticInfo { + public Warning(String prefix, String key, Object... args) { + super(DiagnosticType.WARNING, prefix, key, args); + } + } + + /** + * Class representing note diagnostic keys. + */ + public static final class Note extends DiagnosticInfo { + public Note(String prefix, String key, Object... args) { + super(DiagnosticType.NOTE, prefix, key, args); + } + } + + /** + * Class representing fragment diagnostic keys. + */ + public static final class Fragment extends DiagnosticInfo { + public Fragment(String prefix, String key, Object... args) { + super(DiagnosticType.FRAGMENT, prefix, key, args); + } + } + + /** * Create a diagnostic object. * @param formatter the formatter to use for the diagnostic - * @param dt the type of diagnostic + * @param diagnosticInfo the diagnostic key * @param lc the lint category for the diagnostic * @param source the name of the source file, or null if none. * @param pos the character offset within the source file, if given. - * @param key a resource key to identify the text of the diagnostic - * @param args arguments to be included in the text of the diagnostic */ protected JCDiagnostic(DiagnosticFormatter formatter, - DiagnosticType dt, + DiagnosticInfo diagnosticInfo, LintCategory lc, Set flags, DiagnosticSource source, - DiagnosticPosition pos, - String key, - Object... args) { + DiagnosticPosition pos) { if (source == null && pos != null && pos.getPreferredPosition() != Position.NOPOS) throw new IllegalArgumentException(); this.defaultFormatter = formatter; - this.type = dt; + this.diagnosticInfo = diagnosticInfo; this.lintCategory = lc; this.flags = flags; this.source = source; this.position = pos; - this.key = key; - this.args = args; } /** @@ -426,7 +593,7 @@ * @return the type of this diagnostic */ public DiagnosticType getType() { - return type; + return diagnosticInfo.type; } /** @@ -543,7 +710,7 @@ * @return the arguments to be included in the text of the diagnostic */ public Object[] getArgs() { - return args; + return diagnosticInfo.args; } /** @@ -551,7 +718,7 @@ * @return the prefix string associated with this type of diagnostic */ public String getPrefix() { - return getPrefix(type); + return getPrefix(diagnosticInfo.type); } /** @@ -567,7 +734,7 @@ */ @Override public String toString() { - return defaultFormatter.format(this,Locale.getDefault()); + return defaultFormatter.format(this, Locale.getDefault()); } private DiagnosticFormatter defaultFormatter; @@ -578,7 +745,7 @@ @DefinedBy(Api.COMPILER) public Diagnostic.Kind getKind() { - switch (type) { + switch (diagnosticInfo.type) { case NOTE: return Diagnostic.Kind.NOTE; case WARNING: @@ -594,7 +761,7 @@ @DefinedBy(Api.COMPILER) public String getCode() { - return key; + return diagnosticInfo.key(); } @DefinedBy(Api.COMPILER) @@ -605,7 +772,7 @@ public void setFlag(DiagnosticFlag flag) { flags.add(flag); - if (type == DiagnosticType.ERROR) { + if (diagnosticInfo.type == DiagnosticType.ERROR) { switch (flag) { case SYNTAX: flags.remove(DiagnosticFlag.RECOVERABLE); @@ -627,13 +794,11 @@ public MultilineDiagnostic(JCDiagnostic other, List subdiagnostics) { super(other.defaultFormatter, - other.getType(), + other.diagnosticInfo, other.getLintCategory(), other.flags, other.getDiagnosticSource(), - other.position, - other.getCode(), - other.getArgs()); + other.position); this.subdiagnostics = subdiagnostics; } diff -r 8eeea699174c -r 1633de6070ae langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Log.java --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Log.java Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Log.java Thu Jan 08 14:43:05 2015 +0000 @@ -535,7 +535,7 @@ * @param args Fields of the warning message. */ public void strictWarning(DiagnosticPosition pos, String key, Object ... args) { - writeDiagnostic(diags.warning(source, pos, key, args)); + writeDiagnostic(diags.warning(null, source, pos, key, args)); nwarnings++; } diff -r 8eeea699174c -r 1633de6070ae langtools/test/tools/javac/Diagnostics/6769027/T6769027.java --- a/langtools/test/tools/javac/Diagnostics/6769027/T6769027.java Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/test/tools/javac/Diagnostics/6769027/T6769027.java Thu Jan 08 14:43:05 2015 +0000 @@ -387,7 +387,7 @@ messages.add("tester"); JCDiagnostic.Factory diags = JCDiagnostic.Factory.instance(ctx); log.useSource(new MyFileObject("This is a source line")); - JCDiagnostic d = diags.error(log.currentSource(), + JCDiagnostic d = diags.error(null, log.currentSource(), posKind.pos(), errorKind.key(), "Hello!"); if (multiKind != MultilineKind.NONE) { diff -r 8eeea699174c -r 1633de6070ae langtools/test/tools/javac/diags/MessageFile.java --- a/langtools/test/tools/javac/diags/MessageFile.java Wed Jan 07 17:06:47 2015 -0800 +++ b/langtools/test/tools/javac/diags/MessageFile.java Thu Jan 08 14:43:05 2015 +0000 @@ -31,7 +31,9 @@ */ class MessageFile { static final Pattern emptyOrCommentPattern = Pattern.compile("( *#.*)?"); - static final Pattern infoPattern = Pattern.compile("# ([0-9]+: [-A-Za-z ]+, )*[0-9]+: [-A-Za-z ]+"); + static final Pattern typePattern = Pattern.compile("[-\\\\'A-Z\\.a-z ]+( \\([A-Za-z 0-9]+\\))?"); + static final Pattern infoPattern = Pattern.compile(String.format("# ([0-9]+: %s, )*[0-9]+: %s", + typePattern.pattern(), typePattern.pattern())); /** * A line of text within the message file.