8058542: Devise scheme for better diagnostic creation

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
This commit is contained in:
Maurizio Cimadamore 2015-01-08 14:43:05 +00:00
parent 5cb7065612
commit 56a33e4541
26 changed files with 1825 additions and 132 deletions

@ -39,7 +39,7 @@ $(eval $(call SetupJavaCompilation,BUILD_TOOLS_LANGTOOLS, \
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)

@ -54,6 +54,9 @@ jdk.compiler.dependencies=java.base:java.compiler
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

@ -527,7 +527,8 @@
<build-module-classes module.name="java.compiler"
compilation.kind="@{compilation.kind}" />
<build-module-classes module.name="jdk.compiler"
compilation.kind="@{compilation.kind}" />
compilation.kind="@{compilation.kind}"
resource.includes="${javac.resource.includes}" />
<build-module-classes module.name="jdk.javadoc"
compilation.kind="@{compilation.kind}" />
<build-module-classes module.name="jdk.dev"
@ -536,10 +537,11 @@
</macrodef>
</target>
<target name="-def-build-module-classes" depends="-def-pcompile">
<target name="-def-build-module-classes" depends="-def-pcompile,-def-pparse">
<macrodef name="build-module-classes">
<attribute name="module.name"/>
<attribute name="compilation.kind" default=""/>
<attribute name="resource.includes" default="nonExistent" />
<attribute name="dependencies" default="${@{module.name}.dependencies}"/>
<attribute name="includes" default="${@{compilation.kind}javac.includes}"/>
<attribute name="javac.lint.opts" default="${@{compilation.kind}javac.lint.opts}"/>
@ -583,6 +585,12 @@
<path location="${src.dir}"/>
</src>
</pcompile>
<pparse destdir="@{gensrc.dir}"
includes="@{resource.includes}">
<src>
<path location="${src.dir}"/>
</src>
</pparse>
<copy todir="@{gensrc.dir}">
<fileset dir="${src.dir}" includes="@{includes}" />
<globmapper from="*.properties-template" to="*.properties"/>
@ -644,6 +652,28 @@
</macrodef>
</target>
<target name="-def-pparse">
<mkdir dir="${build.toolclasses.dir}"/>
<copy todir="${build.toolclasses.dir}/propertiesparser" >
<fileset dir="make/tools/propertiesparser" includes="**/resources/**"/>
</copy>
<javac fork="true"
source="${boot.javac.source}"
target="${boot.javac.target}"
executable="${boot.java.home}/bin/javac"
srcdir="${make.tools.dir}"
includes="propertiesparser/* anttasks/PropertiesParser* anttasks/PathFileSet*"
destdir="${build.toolclasses.dir}/"
classpath="${ant.core.lib}"
bootclasspath="${boot.java.home}/jre/lib/rt.jar"
includeantruntime="false">
<compilerarg line="${javac.lint.opts}"/>
</javac>
<taskdef name="pparse"
classname="anttasks.PropertiesParserTask"
classpath="${build.toolclasses.dir}/"/>
</target>
<target name="-do-depend" if="do.depend">
<depend srcdir="${src.dir}:${gensrc.dir}" destdir="${classes.dir}" classpath="${classpath}"
cache="${depcache.dir}"/>

@ -37,4 +37,7 @@ $(eval $(call SetupVersionProperties,JAVAP_VERSION,\
$(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)

@ -35,6 +35,11 @@ include JavaCompilation.gmk
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 @@ define SetupCompileProperties
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
################################################################################

@ -28,7 +28,7 @@
</sequential>
</macrodef>
<target name="post-make" depends="build-all-tools"/>
<target name="post-make" depends="clean, build-all-tools"/>
<target name="jtreg-debug" depends="build-all-tools,-def-jtreg">
<exec-target target="jtreg-debug-internal"/>

@ -5,6 +5,7 @@
<excludeFromCompile>
<directory url="file://$PROJECT_DIR$/src" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/test" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/build" includeSubdirectories="true" />
</excludeFromCompile>
<resourceExtensions />
<wildcardResourcePatterns />

@ -1,20 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="false">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<output url="file://$MODULE_DIR$/build" />
<output-test url="file://$MODULE_DIR$/build" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/bootstrap/gensrc" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/genstubs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/java.base/share/classes" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/java.compiler/share/classes" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/jdk.compiler/share/classes" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/jdk.dev/share/classes" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/jdk.javadoc/share/classes" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/build/bootstrap/java.base/gensrc" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/bootstrap/java.compiler/gensrc" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/bootstrap/jdk.compiler/gensrc" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/bootstrap/jdk.dev/gensrc" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/bootstrap/jdk.javadoc/gensrc" isTestSource="false" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="inheritedJdk" />
</component>
</module>

@ -3,8 +3,8 @@
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" assert-keyword="true" jdk-15="true">
<output url="file://$PROJECT_DIR$/build" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/.idea/out" />
</component>
</project>

@ -478,29 +478,34 @@
<java-data xmlns="http://www.netbeans.org/ns/freeform-project-java/4">
<compilation-unit>
<package-root>${root}/src/java.base/share/classes</package-root>
<package-root>${root}/build/bootstrap/java.base/gensrc</package-root>
<built-to>${root}/build/java.base/classes</built-to>
<source-level>1.8</source-level>
</compilation-unit>
<compilation-unit>
<package-root>${root}/src/java.compiler/share/classes</package-root>
<package-root>${root}/build/bootstrap/java.compiler/gensrc</package-root>
<classpath mode="compile">${root}/build/java.base/classes</classpath>
<built-to>${root}/build/java.compiler/classes</built-to>
<source-level>1.8</source-level>
</compilation-unit>
<compilation-unit>
<package-root>${root}/src/jdk.compiler/share/classes</package-root>
<package-root>${root}/build/bootstrap/jdk.compiler/gensrc</package-root>
<classpath mode="compile">${root}/build/java.base/classes:${root}/build/java.compiler/classes</classpath>
<built-to>${root}/build/jdk.compiler/classes</built-to>
<source-level>1.8</source-level>
</compilation-unit>
<compilation-unit>
<package-root>${root}/src/jdk.dev/share/classes</package-root>
<package-root>${root}/build/bootstrap/jdk.dev/gensrc</package-root>
<classpath mode="compile">${root}/build/java.base/classes:${root}/build/java.compiler/classes:${root}/build/jdk.compiler/classes</classpath>
<built-to>${root}/build/jdk.dev/classes</built-to>
<source-level>1.8</source-level>
</compilation-unit>
<compilation-unit>
<package-root>${root}/src/jdk.javadoc/share/classes</package-root>
<package-root>${root}/build/bootstrap/jdk.javadoc/gensrc</package-root>
<classpath mode="compile">${root}/build/java.base/classes:${root}/build/java.compiler/classes:${root}/build/jdk.compiler/classes</classpath>
<built-to>${root}/build/jdk.javadoc/classes</built-to>
<source-level>1.8</source-level>

@ -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<String> mainOpts = new ArrayList<String>();
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;
}

@ -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. <P>
*
* 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<String, String> 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<String, String> parseOptions(String args[]) {
Map<String, String> 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");
}
}

@ -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<FactoryKind, List<Map.Entry<String, Message>>> groupedEntries =
messageFile.messages.entrySet().stream()
.collect(Collectors.groupingBy(e -> FactoryKind.parseFrom(e.getKey().split("\\.")[1])));
//generate nested classes
List<String> nestedDecls = new ArrayList<>();
Set<String> importedTypes = new TreeSet<>();
for (Map.Entry<FactoryKind, List<Map.Entry<String, Message>>> 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<String> generateImports(Set<String> importedTypes) {
List<String> 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<String> generateFactoryMethodsAndFields(String key, Message msg) {
MessageInfo msgInfo = msg.getMessageInfo();
List<MessageLine> 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<String> factoryMethods = new ArrayList<>();
for (List<MessageType> msgTypes : normalizeTypes(0, msgInfo.getTypes())) {
List<String> types = generateTypes(msgTypes);
List<String> 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<String> argDecls(List<String> types, List<String> args) {
List<String> 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<String> argNames(int size) {
List<String> 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<String> generateTypes(List<MessageType> msgTypes) {
return msgTypes.stream().map(t -> t.accept(stringVisitor, null)).collect(Collectors.toList());
}
//where
Visitor<String, Void> stringVisitor = new Visitor<String, Void>() {
@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<MessageType> msgTypes) {
return msgTypes.stream().anyMatch(t -> t.accept(suppressWarningsVisitor, null));
}
//where
Visitor<Boolean, Void> suppressWarningsVisitor = new Visitor<Boolean, Void>() {
@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<String> importedTypes(List<MessageType> msgTypes) {
Set<String> imports = new TreeSet<>();
msgTypes.forEach(t -> t.accept(importVisitor, imports));
return imports;
}
//where
Visitor<Void, Set<String>> importVisitor = new Visitor<Void, Set<String>>() {
@Override
public Void visitCustomType(CustomType t, Set<String> imports) {
imports.add(t.typeString);
return null;
}
@Override
public Void visitSimpleType(SimpleType t, Set<String> imports) {
if (t.qualifier != null) {
imports.add(t.qualifier + "." + t.clazz);
}
return null;
}
@Override
public Void visitCompoundType(CompoundType t, Set<String> imports) {
visitSimpleType(t.kind.clazz, imports);
t.elemtype.accept(this, imports);
return null;
}
@Override
public Void visitUnionType(UnionType t, Set<String> 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<List<MessageType>> normalizeTypes(int idx, List<MessageType> msgTypes) {
if (msgTypes.size() == idx) return Collections.singletonList(Collections.emptyList());
MessageType head = msgTypes.get(idx);
List<List<MessageType>> buf = new ArrayList<>();
for (MessageType alternative : head.accept(normalizeVisitor, null)) {
for (List<MessageType> rest : normalizeTypes(idx + 1, msgTypes)) {
List<MessageType> temp = new ArrayList<>(rest);
temp.add(0, alternative);
buf.add(temp);
}
}
return buf;
}
//where
Visitor<List<MessageType>, Void> normalizeVisitor = new Visitor<List<MessageType>, Void>() {
@Override
public List<MessageType> visitCustomType(CustomType t, Void aVoid) {
return Collections.singletonList(t);
}
@Override
public List<MessageType> visitSimpleType(SimpleType t, Void aVoid) {
return Collections.singletonList(t);
}
@Override
public List<MessageType> 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<MessageType> visitUnionType(UnionType t, Void aVoid) {
return Stream.of(t.choices)
.flatMap(t2 -> t2.accept(this, null).stream())
.collect(Collectors.toList());
}
};
}

@ -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<MessageLine> getLines(boolean includeAllPrecedingComments) {
List<MessageLine> 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;
}
}

@ -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<String, Message> 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));
}
}
}
}

@ -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<MessageType> 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<MessageType> 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);
}

@ -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;
}
}

@ -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, A> R accept(Visitor<R, A> 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, A> R accept(Visitor<R, A> 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("<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, A> R accept(Visitor<R, A> 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, A> R accept(Visitor<R, A> 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, A> R accept(Visitor<R, A> 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<R, A> {
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);
}
}

@ -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}<? extends {1}>
suppress.warnings=\
@SuppressWarnings("rawtypes")\n

@ -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<T>
# 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:
#
# These comments are verified by the jtreg test test/tools/javac/diags/MessageInfo,
# list of type or message segment
#
# The following type aliases are supported:
#
# message segment --> diagnostic or fragment
# file name --> file or file object
#
# 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.array.req.but.found=\
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.multiple.values=\
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 @@ compiler.err.warnings.and.werror=\
# 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.type.var.more.than.once.in.result=\
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 @@ compiler.err.cant.access=\
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.wrong.class=\
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.not.applicable.method.found=\
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 @@ compiler.note.verbose.resolve.multi=\
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\

@ -329,7 +329,7 @@ public abstract class DCTree implements DocTree {
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)

@ -31,6 +31,9 @@ import javax.tools.JavaFileObject;
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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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 @@ public abstract class AbstractLog {
* @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);

@ -28,6 +28,7 @@ package com.sun.tools.javac.util;
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 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
}
/**
* 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 class JCDiagnostic implements Diagnostic<JavaFileObject> {
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 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
* @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 warning message.
* @param args Fields of the warning message.
* @param warningKey The key for the localized 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);
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 key The key for the localized message.
* @param args Fields of the message.
* @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 mandatoryNote(DiagnosticSource source, String key, Object... args) {
return create(NOTE, null, EnumSet.of(DiagnosticFlag.MANDATORY), source, null, key, args);
return mandatoryNote(source, noteKey(key, args));
}
/**
* Create a note diagnostic that will not be hidden by the -nowarn or -Xlint:none options.
* @param noteKey The key for the localized note message.
* @see MandatoryWarningHandler
*/
public JCDiagnostic mandatoryNote(DiagnosticSource source, Note noteKey) {
return create(null, EnumSet.of(DiagnosticFlag.MANDATORY), source, null, noteKey);
}
/**
@ -184,20 +200,20 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
* @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 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
* @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 class JCDiagnostic implements Diagnostic<JavaFileObject> {
*/
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 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
* @param key The key for the localized message.
* @param args Fields of the message.
*/
public JCDiagnostic create(
DiagnosticType kind, LintCategory lc, Set<DiagnosticFlag> 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<DiagnosticFlag> 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<DiagnosticFlag> 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 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
@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 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
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<DiagnosticFlag> flags;
private final LintCategory lintCategory;
@ -390,35 +471,121 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
}
}
/**
* 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<JCDiagnostic> formatter,
DiagnosticType dt,
DiagnosticInfo diagnosticInfo,
LintCategory lc,
Set<DiagnosticFlag> 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 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
* @return the type of this diagnostic
*/
public DiagnosticType getType() {
return type;
return diagnosticInfo.type;
}
/**
@ -543,7 +710,7 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
* @return the arguments to be included in the text of the diagnostic
*/
public Object[] getArgs() {
return args;
return diagnosticInfo.args;
}
/**
@ -551,7 +718,7 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
* @return the prefix string associated with this type of diagnostic
*/
public String getPrefix() {
return getPrefix(type);
return getPrefix(diagnosticInfo.type);
}
/**
@ -567,7 +734,7 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
*/
@Override
public String toString() {
return defaultFormatter.format(this,Locale.getDefault());
return defaultFormatter.format(this, Locale.getDefault());
}
private DiagnosticFormatter<JCDiagnostic> defaultFormatter;
@ -578,7 +745,7 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
@DefinedBy(Api.COMPILER)
public Diagnostic.Kind getKind() {
switch (type) {
switch (diagnosticInfo.type) {
case NOTE:
return Diagnostic.Kind.NOTE;
case WARNING:
@ -594,7 +761,7 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
@DefinedBy(Api.COMPILER)
public String getCode() {
return key;
return diagnosticInfo.key();
}
@DefinedBy(Api.COMPILER)
@ -605,7 +772,7 @@ public class JCDiagnostic implements Diagnostic<JavaFileObject> {
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 class JCDiagnostic implements Diagnostic<JavaFileObject> {
public MultilineDiagnostic(JCDiagnostic other, List<JCDiagnostic> 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;
}

@ -535,7 +535,7 @@ public class Log extends AbstractLog {
* @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++;
}

@ -387,7 +387,7 @@ public class T6769027
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) {

@ -31,7 +31,9 @@ import java.util.regex.Pattern;
*/
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.