8072988: Update javax.annotation.processing for modules
Summary: Support for generating files in multi-module mode.
Reviewed-by: darcy, jjg
Contributed-by: joe.darcy@oracle.com, jan.lahoda@oracle.com
--- a/langtools/src/java.compiler/share/classes/javax/annotation/processing/Filer.java Tue Dec 13 10:48:18 2016 +0100
+++ b/langtools/src/java.compiler/share/classes/javax/annotation/processing/Filer.java Tue Dec 13 10:49:28 2016 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2016, 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
@@ -66,7 +66,8 @@
* allow the <em>originating elements</em> to be provided as hints to
* the tool infrastructure to better manage dependencies. The
* originating elements are the types or packages (representing {@code
- * package-info} files) which caused an annotation processor to
+ * package-info} files) or modules (representing {@code
+ * module-info} files) which caused an annotation processor to
* attempt to create a new file. For example, if an annotation
* processor tries to create a source file, {@code
* GeneratedFromUserSource}, in response to processing
@@ -111,10 +112,10 @@
* to overwrite existing files that were not generated.
*
* <p> Processors can indicate a source or class file is generated by
- * including an {@link javax.annotation.Generated @Generated}
- * annotation.
+ * including a {@code javax.annotation.Generated} annotation if the
+ * environment is configured so that that type is accessible.
*
- * <p> Note that some of the effect of overwriting a file can be
+ * @apiNote Some of the effect of overwriting a file can be
* achieved by using a <i>decorator</i>-style pattern. Instead of
* modifying a class directly, the class is designed so that either
* its superclass is generated by annotation processing or subclasses
@@ -131,18 +132,35 @@
public interface Filer {
/**
* Creates a new source file and returns an object to allow
- * writing to it. The file's name and path (relative to the
- * {@linkplain StandardLocation#SOURCE_OUTPUT root output location
- * for source files}) are based on the type to be declared in that
- * file. If more than one type is being declared, the name of the
- * principal top-level type (the public one, for example) should
- * be used. A source file can also be created to hold information
- * about a package, including package annotations. To create a
- * source file for a named package, have {@code name} be the
+ * writing to it. A source file for a type, or a package can
+ * be created.
+ *
+ * The file's name and path (relative to the {@linkplain
+ * StandardLocation#SOURCE_OUTPUT root output location for source
+ * files}) are based on the name of the item to be declared in
+ * that file as well as the specified module for the item (if
+ * any).
+ *
+ * If more than one type is being declared in a single file (that
+ * is, a single compilation unit), the name of the file should
+ * correspond to the name of the principal top-level type (the
+ * public one, for example).
+ *
+ * <p>A source file can also be created to hold information about
+ * a package, including package annotations. To create a source
+ * file for a named package, have the {@code name} argument be the
* package's name followed by {@code ".package-info"}; to create a
* source file for an unnamed package, use {@code "package-info"}.
*
- * <p> Note that to use a particular {@linkplain
+ * <p>The optional module name is prefixed to the type name or
+ * package name and separated using a "{@code /}" character. For
+ * example, to create a source file for type {@code a.B} in module
+ * {@code foo}, use a {@code name} argument of {@code "foo/a.B"}.
+ *
+ * <p>Creating a source file in or for an unnamed package in a named
+ * module is <em>not</em> supported.
+ *
+ * @apiNote To use a particular {@linkplain
* java.nio.charset.Charset charset} to encode the contents of the
* file, an {@code OutputStreamWriter} with the chosen charset can
* be created from the {@code OutputStream} from the returned
@@ -161,37 +179,51 @@
* @param name canonical (fully qualified) name of the principal type
* being declared in this file or a package name followed by
* {@code ".package-info"} for a package information file
- * @param originatingElements type or package elements causally
+ * @param originatingElements type or package or module elements causally
* associated with the creation of this file, may be elided or
* {@code null}
* @return a {@code JavaFileObject} to write the new source file
* @throws FilerException if the same pathname has already been
* created, the same type has already been created, or the name is
- * not valid for a type
+ * otherwise not valid for the entity requested to being created
* @throws IOException if the file cannot be created
+ * @jls 7.3 Compilation Units
*/
JavaFileObject createSourceFile(CharSequence name,
Element... originatingElements) throws IOException;
/**
* Creates a new class file, and returns an object to allow
- * writing to it. The file's name and path (relative to the
- * {@linkplain StandardLocation#CLASS_OUTPUT root output location
- * for class files}) are based on the name of the type being
- * written. A class file can also be created to hold information
- * about a package, including package annotations. To create a
- * class file for a named package, have {@code name} be the
+ * writing to it. A class file for a type, or a package can
+ * be created.
+ *
+ * The file's name and path (relative to the {@linkplain
+ * StandardLocation#CLASS_OUTPUT root output location for class
+ * files}) are based on the name of the item to be declared as
+ * well as the specified module for the item (if any).
+ *
+ * <p>A class file can also be created to hold information about a
+ * package, including package annotations. To create a class file
+ * for a named package, have the {@code name} argument be the
* package's name followed by {@code ".package-info"}; creating a
* class file for an unnamed package is not supported.
*
- * <p>To avoid subsequent errors, the contents of the class file
- * should be compatible with the {@linkplain
- * ProcessingEnvironment#getSourceVersion source version} being used
- * for this run.
+ * <p>The optional module name is prefixed to the type name or
+ * package name and separated using a "{@code /}" character. For
+ * example, to create a class file for type {@code a.B} in module
+ * {@code foo}, use a {@code name} argument of {@code "foo/a.B"}.
+ *
+ * <p>Creating a class file in or for an unnamed package in a named
+ * module is <em>not</em> supported.
+ *
+ * @apiNote To avoid subsequent errors, the contents of the class
+ * file should be compatible with the {@linkplain
+ * ProcessingEnvironment#getSourceVersion source version} being
+ * used for this run.
*
* @param name binary name of the type being written or a package name followed by
* {@code ".package-info"} for a package information file
- * @param originatingElements type or package elements causally
+ * @param originatingElements type or package or module elements causally
* associated with the creation of this file, may be elided or
* {@code null}
* @return a {@code JavaFileObject} to write the new class file
@@ -210,20 +242,27 @@
* other supported location. The locations {@link
* StandardLocation#CLASS_OUTPUT CLASS_OUTPUT} and {@link
* StandardLocation#SOURCE_OUTPUT SOURCE_OUTPUT} must be
- * supported. The resource may be named relative to some package
- * (as are source and class files), and from there by a relative
- * pathname. In a loose sense, the full pathname of the new file
- * will be the concatenation of {@code location}, {@code pkg}, and
- * {@code relativeName}.
+ * supported. The resource may be named relative to some module
+ * and/or package (as are source and class files), and from there
+ * by a relative pathname. In a loose sense, the full pathname of
+ * the new file will be the concatenation of {@code location},
+ * {@code moduleAndPkg}, and {@code relativeName}.
*
- * <p>Files created via this method are not registered for
+ * If {@code moduleAndPkg} contains a "{@code /}" character, the
+ * prefix before the "{@code /}" character is the module name and
+ * the suffix after the "{@code /}" character is the package
+ * name. The package suffix may be empty. If {@code moduleAndPkg}
+ * does not contain a "{@code /}" character, the entire argument
+ * is interpreted as a package name.
+ *
+ * <p>Files created via this method are <em>not</em> registered for
* annotation processing, even if the full pathname of the file
* would correspond to the full pathname of a new source file
* or new class file.
*
* @param location location of the new file
- * @param pkg package relative to which the file should be named,
- * or the empty string if none
+ * @param moduleAndPkg module and/or package relative to which the file
+ * should be named, or the empty string if none
* @param relativeName final pathname components of the file
* @param originatingElements type or package elements causally
* associated with the creation of this file, may be elided or
@@ -233,10 +272,11 @@
* @throws FilerException if the same pathname has already been
* created
* @throws IllegalArgumentException for an unsupported location
+ * @throws IllegalArgumentException if {@code moduleAndPkg} is ill-formed
* @throws IllegalArgumentException if {@code relativeName} is not relative
*/
FileObject createResource(JavaFileManager.Location location,
- CharSequence pkg,
+ CharSequence moduleAndPkg,
CharSequence relativeName,
Element... originatingElements) throws IOException;
@@ -246,18 +286,27 @@
* and {@link StandardLocation#SOURCE_OUTPUT SOURCE_OUTPUT} must
* be supported.
*
+ * <p>If {@code moduleAndPkg} contains a "{@code /}" character, the
+ * prefix before the "{@code /}" character is the module name and
+ * the suffix after the "{@code /}" character is the package
+ * name. The package suffix may be empty; however, if a module
+ * name is present, it must be nonempty. If {@code moduleAndPkg}
+ * does not contain a "{@code /}" character, the entire argument
+ * is interpreted as a package name.
+ *
* @param location location of the file
- * @param pkg package relative to which the file should be searched,
- * or the empty string if none
+ * @param moduleAndPkg module and/or package relative to which the file
+ * should be searched for, or the empty string if none
* @param relativeName final pathname components of the file
* @return an object to read the file
* @throws FilerException if the same pathname has already been
* opened for writing
* @throws IOException if the file cannot be opened
* @throws IllegalArgumentException for an unsupported location
+ * @throws IllegalArgumentException if {@code moduleAndPkg} is ill-formed
* @throws IllegalArgumentException if {@code relativeName} is not relative
*/
FileObject getResource(JavaFileManager.Location location,
- CharSequence pkg,
+ CharSequence moduleAndPkg,
CharSequence relativeName) throws IOException;
}
--- a/langtools/src/java.compiler/share/classes/javax/annotation/processing/Processor.java Tue Dec 13 10:48:18 2016 +0100
+++ b/langtools/src/java.compiler/share/classes/javax/annotation/processing/Processor.java Tue Dec 13 10:49:28 2016 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2016, 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
@@ -115,7 +115,17 @@
* the root elements of a round. For this purpose, a type parameter is
* considered to be enclosed by its {@linkplain
* TypeParameterElement#getGenericElement generic
- * element}. Annotations on {@linkplain
+ * element}.
+
+ * For this purpose, a package element is <em>not</em> considered to
+ * enclose the top-level types within that package. (A root element
+ * representing a package is created when a {@code package-info} file
+ * is processed.) Likewise, for this purpose, a module element is
+ * <em>not</em> considered to enclose the packages within that
+ * module. (A root element representing a module is created when a
+ * {@code module-info} file is processed.)
+ *
+ * Annotations on {@linkplain
* java.lang.annotation.ElementType#TYPE_USE type uses}, as opposed to
* annotations on elements, are ignored when computing whether or not
* an annotation type is present.
@@ -235,12 +245,20 @@
* (fully qualified) name of a supported annotation type.
* Alternately it may be of the form "<tt><i>name</i>.*</tt>"
* representing the set of all annotation types with canonical
- * names beginning with "<tt><i>name.</i></tt>". Finally, {@code
- * "*"} by itself represents the set of all annotation types,
- * including the empty set. Note that a processor should not
- * claim {@code "*"} unless it is actually processing all files;
- * claiming unnecessary annotations may cause a performance
- * slowdown in some environments.
+ * names beginning with "<tt><i>name.</i></tt>".
+ *
+ * In either of those cases, the name of the annotation type can
+ * be optionally preceded by a module name followed by a {@code
+ * "/"} character. For example, if a processor supports {@code
+ * "a.B"}, this can include multiple annotation types named {@code
+ * a.B} which reside in different modules. To only support {@code
+ * a.B} in the {@code Foo} module, instead use {@code "Foo/a.B"}.
+ *
+ * Finally, {@code "*"} by itself represents the set of all
+ * annotation types, including the empty set. Note that a
+ * processor should not claim {@code "*"} unless it is actually
+ * processing all files; claiming unnecessary annotations may
+ * cause a performance slowdown in some environments.
*
* <p>Each string returned in the set must be accepted by the
* following grammar:
@@ -248,9 +266,12 @@
* <blockquote>
* <dl>
* <dt><i>SupportedAnnotationTypeString:</i>
- * <dd><i>TypeName</i> <i>DotStar</i><sub><i>opt</i></sub>
+ * <dd><i>ModulePrefix</i><sub><i>opt</i></sub> <i>TypeName</i> <i>DotStar</i><sub><i>opt</i></sub>
* <dd><tt>*</tt>
*
+ * <dt><i>ModulePrefix:</i>
+ * <dd><i>TypeName</i> <tt>/</tt>
+ *
* <dt><i>DotStar:</i>
* <dd><tt>.</tt> <tt>*</tt>
* </dl>
--- a/langtools/src/java.compiler/share/classes/javax/annotation/processing/RoundEnvironment.java Tue Dec 13 10:48:18 2016 +0100
+++ b/langtools/src/java.compiler/share/classes/javax/annotation/processing/RoundEnvironment.java Tue Dec 13 10:49:28 2016 +0100
@@ -76,14 +76,17 @@
/**
* Returns the elements annotated with the given annotation type.
* The annotation may appear directly or be inherited. Only
- * package elements and type elements <i>included</i> in this
+ * package elements, module elements, and type elements <i>included</i> in this
* round of annotation processing, or declarations of members,
* constructors, parameters, or type parameters declared within
* those, are returned. Included type elements are {@linkplain
* #getRootElements root types} and any member types nested within
- * them. Elements in a package are not considered included simply
+ * them. Elements of a package are not considered included simply
* because a {@code package-info} file for that package was
* created.
+ * Likewise, elements of a module are not considered included
+ * simply because a {@code module-info} file for that module was
+ * created
*
* @param a annotation type being requested
* @return the elements annotated with the given annotation type,
@@ -128,7 +131,7 @@
/**
* Returns the elements annotated with the given annotation type.
* The annotation may appear directly or be inherited. Only
- * package elements and type elements <i>included</i> in this
+ * package elements, module elements, and type elements <i>included</i> in this
* round of annotation processing, or declarations of members,
* constructors, parameters, or type parameters declared within
* those, are returned. Included type elements are {@linkplain
@@ -136,6 +139,9 @@
* them. Elements in a package are not considered included simply
* because a {@code package-info} file for that package was
* created.
+ * Likewise, elements of a module are not considered included
+ * simply because a {@code module-info} file for that module was
+ * created
*
* @param a annotation type being requested
* @return the elements annotated with the given annotation type,
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java Tue Dec 13 10:48:18 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java Tue Dec 13 10:49:28 2016 +0100
@@ -476,8 +476,18 @@
private Location getModuleLocation(JavaFileObject fo, Name pkgName) throws IOException {
// For now, just check module source path.
// We may want to check source path as well.
- return fileManager.getLocationForModule(StandardLocation.MODULE_SOURCE_PATH,
- fo, (pkgName == null) ? null : pkgName.toString());
+ Location loc =
+ fileManager.getLocationForModule(StandardLocation.MODULE_SOURCE_PATH,
+ fo, (pkgName == null) ? null : pkgName.toString());
+ if (loc == null) {
+ Location sourceOutput = fileManager.hasLocation(StandardLocation.SOURCE_OUTPUT) ?
+ StandardLocation.SOURCE_OUTPUT : StandardLocation.CLASS_OUTPUT;
+ loc =
+ fileManager.getLocationForModule(sourceOutput,
+ fo, (pkgName == null) ? null : pkgName.toString());
+ }
+
+ return loc;
}
private void checkSpecifiedModule(List<JCCompilationUnit> trees, JCDiagnostic.Error error) {
@@ -614,6 +624,11 @@
};
}
+ public boolean isRootModule(ModuleSymbol module) {
+ Assert.checkNonNull(rootModules);
+ return rootModules.contains(module);
+ }
+
class ModuleVisitor extends JCTree.Visitor {
private ModuleSymbol sym;
private final Set<ModuleSymbol> allRequires = new HashSet<>();
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java Tue Dec 13 10:48:18 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java Tue Dec 13 10:49:28 2016 +0100
@@ -952,6 +952,8 @@
public Location getLocationForModule(Location location, String moduleName) throws IOException {
checkModuleOrientedOrOutputLocation(location);
nullCheck(moduleName);
+ if (location == SOURCE_OUTPUT && getSourceOutDir() == null)
+ location = CLASS_OUTPUT;
return locations.getLocationForModule(location, moduleName);
}
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacFiler.java Tue Dec 13 10:48:18 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacFiler.java Tue Dec 13 10:49:28 2016 +0100
@@ -51,6 +51,9 @@
import static javax.tools.StandardLocation.CLASS_OUTPUT;
import com.sun.tools.javac.code.Lint;
+import com.sun.tools.javac.code.Symbol.ModuleSymbol;
+import com.sun.tools.javac.code.Symtab;
+import com.sun.tools.javac.comp.Modules;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.DefinedBy.Api;
@@ -114,10 +117,12 @@
*/
private class FilerOutputFileObject extends ForwardingFileObject<FileObject> {
private boolean opened = false;
+ private ModuleSymbol mod;
private String name;
- FilerOutputFileObject(String name, FileObject fileObject) {
+ FilerOutputFileObject(ModuleSymbol mod, String name, FileObject fileObject) {
super(fileObject);
+ this.mod = mod;
this.name = name;
}
@@ -126,7 +131,7 @@
if (opened)
throw new IOException(ALREADY_OPENED);
opened = true;
- return new FilerOutputStream(name, fileObject);
+ return new FilerOutputStream(mod, name, fileObject);
}
@Override @DefinedBy(Api.COMPILER)
@@ -134,7 +139,7 @@
if (opened)
throw new IOException(ALREADY_OPENED);
opened = true;
- return new FilerWriter(name, fileObject);
+ return new FilerWriter(mod, name, fileObject);
}
// Three anti-literacy methods
@@ -161,8 +166,8 @@
private class FilerOutputJavaFileObject extends FilerOutputFileObject implements JavaFileObject {
private final JavaFileObject javaFileObject;
- FilerOutputJavaFileObject(String name, JavaFileObject javaFileObject) {
- super(name, javaFileObject);
+ FilerOutputJavaFileObject(ModuleSymbol mod, String name, JavaFileObject javaFileObject) {
+ super(mod, name, javaFileObject);
this.javaFileObject = javaFileObject;
}
@@ -248,6 +253,7 @@
* when they are closed.
*/
private class FilerOutputStream extends FilterOutputStream {
+ ModuleSymbol mod;
String typeName;
FileObject fileObject;
boolean closed = false;
@@ -256,8 +262,9 @@
* @param typeName name of class or {@code null} if just a
* binary file
*/
- FilerOutputStream(String typeName, FileObject fileObject) throws IOException {
+ FilerOutputStream(ModuleSymbol mod, String typeName, FileObject fileObject) throws IOException {
super(fileObject.openOutputStream());
+ this.mod = mod;
this.typeName = typeName;
this.fileObject = fileObject;
}
@@ -270,7 +277,7 @@
* stream, still try to process the file.
*/
- closeFileObject(typeName, fileObject);
+ closeFileObject(mod, typeName, fileObject);
out.close();
}
}
@@ -282,6 +289,7 @@
* closed.
*/
private class FilerWriter extends FilterWriter {
+ ModuleSymbol mod;
String typeName;
FileObject fileObject;
boolean closed = false;
@@ -291,8 +299,9 @@
* @param typeName name of source file or {@code null} if just a
* text file
*/
- FilerWriter(String typeName, FileObject fileObject) throws IOException {
+ FilerWriter(ModuleSymbol mod, String typeName, FileObject fileObject) throws IOException {
super(fileObject.openWriter());
+ this.mod = mod;
this.typeName = typeName;
this.fileObject = fileObject;
}
@@ -305,7 +314,7 @@
* Writer, still try to process the file.
*/
- closeFileObject(typeName, fileObject);
+ closeFileObject(mod, typeName, fileObject);
out.close();
}
}
@@ -313,6 +322,9 @@
JavaFileManager fileManager;
Log log;
+ Modules modules;
+ Names names;
+ Symtab syms;
Context context;
boolean lastRound;
@@ -340,7 +352,7 @@
* This set must be synchronized. Its iterators should preserve
* insertion order.
*/
- private final Map<String, JavaFileObject> generatedClasses;
+ private final Map<ModuleSymbol, Map<String, JavaFileObject>> generatedClasses;
/**
* JavaFileObjects for source files closed in this round. This
@@ -353,13 +365,13 @@
* Names of all created source files. Its iterators should
* preserve insertion order.
*/
- private final Set<String> aggregateGeneratedSourceNames;
+ private final Set<Pair<ModuleSymbol, String>> aggregateGeneratedSourceNames;
/**
* Names of all created class files. Its iterators should
* preserve insertion order.
*/
- private final Set<String> aggregateGeneratedClassNames;
+ private final Set<Pair<ModuleSymbol, String>> aggregateGeneratedClassNames;
JavacFiler(Context context) {
@@ -367,12 +379,15 @@
fileManager = context.get(JavaFileManager.class);
log = Log.instance(context);
+ modules = Modules.instance(context);
+ names = Names.instance(context);
+ syms = Symtab.instance(context);
fileObjectHistory = synchronizedSet(new LinkedHashSet<FileObject>());
generatedSourceNames = synchronizedSet(new LinkedHashSet<String>());
generatedSourceFileObjects = synchronizedSet(new LinkedHashSet<JavaFileObject>());
- generatedClasses = synchronizedMap(new LinkedHashMap<String, JavaFileObject>());
+ generatedClasses = synchronizedMap(new LinkedHashMap<>());
openTypeNames = synchronizedSet(new LinkedHashSet<String>());
@@ -382,19 +397,51 @@
lint = (Lint.instance(context)).isEnabled(PROCESSING);
}
- @DefinedBy(Api.ANNOTATION_PROCESSING)
- public JavaFileObject createSourceFile(CharSequence name,
+ @Override @DefinedBy(Api.ANNOTATION_PROCESSING)
+ public JavaFileObject createSourceFile(CharSequence nameAndModule,
Element... originatingElements) throws IOException {
- return createSourceOrClassFile(true, name.toString());
+ Pair<ModuleSymbol, String> moduleAndClass = checkOrInferModule(nameAndModule);
+ return createSourceOrClassFile(moduleAndClass.fst, true, moduleAndClass.snd);
+ }
+
+ @Override @DefinedBy(Api.ANNOTATION_PROCESSING)
+ public JavaFileObject createClassFile(CharSequence nameAndModule,
+ Element... originatingElements) throws IOException {
+ Pair<ModuleSymbol, String> moduleAndClass = checkOrInferModule(nameAndModule);
+ return createSourceOrClassFile(moduleAndClass.fst, false, moduleAndClass.snd);
}
- @DefinedBy(Api.ANNOTATION_PROCESSING)
- public JavaFileObject createClassFile(CharSequence name,
- Element... originatingElements) throws IOException {
- return createSourceOrClassFile(false, name.toString());
+ private Pair<ModuleSymbol, String> checkOrInferModule(CharSequence moduleAndPkg) throws FilerException {
+ String moduleAndPkgString = moduleAndPkg.toString();
+ int slash = moduleAndPkgString.indexOf('/');
+
+ if (slash != (-1)) {
+ //module name specified:
+ String module = moduleAndPkgString.substring(0, slash);
+
+ ModuleSymbol explicitModule = syms.getModule(names.fromString(module));
+
+ if (explicitModule == null) {
+ throw new FilerException("Module: " + module + " does not exist.");
+ }
+
+ if (!modules.isRootModule(explicitModule)) {
+ throw new FilerException("Cannot write to the given module!");
+ }
+
+ return Pair.of(explicitModule, moduleAndPkgString.substring(slash + 1));
+ } else {
+ if (modules.multiModuleMode) {
+ throw new FilerException("No module to write to specified!");
+ }
+
+ return Pair.of(modules.getDefaultModule(), moduleAndPkgString);
+ }
}
- private JavaFileObject createSourceOrClassFile(boolean isSourceFile, String name) throws IOException {
+ private JavaFileObject createSourceOrClassFile(ModuleSymbol mod, boolean isSourceFile, String name) throws IOException {
+ Assert.checkNonNull(mod);
+
if (lint) {
int periodIndex = name.lastIndexOf(".");
if (periodIndex != -1) {
@@ -404,8 +451,12 @@
log.warning("proc.suspicious.class.name", name, extn);
}
}
- checkNameAndExistence(name, isSourceFile);
+ checkNameAndExistence(mod, name, isSourceFile);
Location loc = (isSourceFile ? SOURCE_OUTPUT : CLASS_OUTPUT);
+
+ if (modules.multiModuleMode) {
+ loc = this.fileManager.getLocationForModule(loc, mod.name.toString());
+ }
JavaFileObject.Kind kind = (isSourceFile ?
JavaFileObject.Kind.SOURCE :
JavaFileObject.Kind.CLASS);
@@ -418,21 +469,30 @@
log.warning("proc.file.create.last.round", name);
if (isSourceFile)
- aggregateGeneratedSourceNames.add(name);
+ aggregateGeneratedSourceNames.add(Pair.of(mod, name));
else
- aggregateGeneratedClassNames.add(name);
+ aggregateGeneratedClassNames.add(Pair.of(mod, name));
openTypeNames.add(name);
- return new FilerOutputJavaFileObject(name, fileObject);
+ return new FilerOutputJavaFileObject(mod, name, fileObject);
}
- @DefinedBy(Api.ANNOTATION_PROCESSING)
+ @Override @DefinedBy(Api.ANNOTATION_PROCESSING)
public FileObject createResource(JavaFileManager.Location location,
- CharSequence pkg,
+ CharSequence moduleAndPkg,
CharSequence relativeName,
Element... originatingElements) throws IOException {
+ Pair<ModuleSymbol, String> moduleAndPackage = checkOrInferModule(moduleAndPkg);
+ ModuleSymbol msym = moduleAndPackage.fst;
+ String pkg = moduleAndPackage.snd;
+
locationCheck(location);
+ if (modules.multiModuleMode) {
+ Assert.checkNonNull(msym);
+ location = this.fileManager.getLocationForModule(location, msym.name.toString());
+ }
+
String strPkg = pkg.toString();
if (strPkg.length() > 0)
checkName(strPkg);
@@ -443,9 +503,9 @@
checkFileReopening(fileObject, true);
if (fileObject instanceof JavaFileObject)
- return new FilerOutputJavaFileObject(null, (JavaFileObject)fileObject);
+ return new FilerOutputJavaFileObject(msym, null, (JavaFileObject)fileObject);
else
- return new FilerOutputFileObject(null, fileObject);
+ return new FilerOutputFileObject(msym, null, fileObject);
}
private void locationCheck(JavaFileManager.Location location) {
@@ -457,13 +517,21 @@
}
}
- @DefinedBy(Api.ANNOTATION_PROCESSING)
+ @Override @DefinedBy(Api.ANNOTATION_PROCESSING)
public FileObject getResource(JavaFileManager.Location location,
- CharSequence pkg,
+ CharSequence moduleAndPkg,
CharSequence relativeName) throws IOException {
- String strPkg = pkg.toString();
- if (strPkg.length() > 0)
- checkName(strPkg);
+ Pair<ModuleSymbol, String> moduleAndPackage = checkOrInferModule(moduleAndPkg);
+ ModuleSymbol msym = moduleAndPackage.fst;
+ String pkg = moduleAndPackage.snd;
+
+ if (modules.multiModuleMode) {
+ Assert.checkNonNull(msym);
+ location = this.fileManager.getLocationForModule(location, msym.name.toString());
+ }
+
+ if (pkg.length() > 0)
+ checkName(pkg);
// TODO: Only support reading resources in selected output
// locations? Only allow reading of non-source, non-class
@@ -478,12 +546,12 @@
FileObject fileObject;
if (location.isOutputLocation()) {
fileObject = fileManager.getFileForOutput(location,
- pkg.toString(),
+ pkg,
relativeName.toString(),
null);
} else {
fileObject = fileManager.getFileForInput(location,
- pkg.toString(),
+ pkg,
relativeName.toString());
}
if (fileObject == null) {
@@ -524,16 +592,19 @@
}
}
- private void checkNameAndExistence(String typename, boolean allowUnnamedPackageInfo) throws FilerException {
+ private void checkNameAndExistence(ModuleSymbol mod, String typename, boolean allowUnnamedPackageInfo) throws FilerException {
// TODO: Check if type already exists on source or class path?
// If so, use warning message key proc.type.already.exists
checkName(typename, allowUnnamedPackageInfo);
- if (aggregateGeneratedSourceNames.contains(typename) ||
- aggregateGeneratedClassNames.contains(typename)) {
+ if (aggregateGeneratedSourceNames.contains(Pair.of(mod, typename)) ||
+ aggregateGeneratedClassNames.contains(Pair.of(mod, typename))) {
if (lint)
log.warning("proc.type.recreate", typename);
throw new FilerException("Attempt to recreate a file for type " + typename);
}
+ if (!mod.isUnnamed() && !typename.contains(".")) {
+ throw new FilerException("Attempt to create a type in unnamed package of a named module: " + typename);
+ }
}
/**
@@ -565,7 +636,7 @@
return generatedSourceFileObjects;
}
- public Map<String, JavaFileObject> getGeneratedClasses() {
+ public Map<ModuleSymbol, Map<String, JavaFileObject>> getGeneratedClasses() {
return generatedClasses;
}
@@ -621,7 +692,7 @@
* Upon close, register files opened by create{Source, Class}File
* for annotation processing.
*/
- private void closeFileObject(String typeName, FileObject fileObject) {
+ private void closeFileObject(ModuleSymbol mod, String typeName, FileObject fileObject) {
/*
* If typeName is non-null, the file object was opened as a
* source or class file by the user. If a file was opened as
@@ -640,7 +711,7 @@
break;
case CLASS:
- generatedClasses.put(typeName, javaFileObject);
+ generatedClasses.computeIfAbsent(mod, m -> Collections.synchronizedMap(new LinkedHashMap<>())).put(typeName, javaFileObject);
openTypeNames.remove(typeName);
break;
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java Tue Dec 13 10:48:18 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java Tue Dec 13 10:49:28 2016 +0100
@@ -34,6 +34,7 @@
import java.net.URL;
import java.nio.file.Path;
import java.util.*;
+import java.util.Map.Entry;
import java.util.regex.*;
import java.util.stream.Collectors;
@@ -827,12 +828,14 @@
private void discoverAndRunProcs(Set<TypeElement> annotationsPresent,
List<ClassSymbol> topLevelClasses,
- List<PackageSymbol> packageInfoFiles) {
+ List<PackageSymbol> packageInfoFiles,
+ List<ModuleSymbol> moduleInfoFiles) {
Map<String, TypeElement> unmatchedAnnotations = new HashMap<>(annotationsPresent.size());
for(TypeElement a : annotationsPresent) {
- unmatchedAnnotations.put(a.getQualifiedName().toString(),
- a);
+ ModuleElement mod = elementUtils.getModuleOf(a);
+ unmatchedAnnotations.put((mod != null ? mod.getSimpleName() + "/" : "") + a.getQualifiedName().toString(),
+ a);
}
// Give "*" processors a chance to match
@@ -849,6 +852,7 @@
Set<Element> rootElements = new LinkedHashSet<>();
rootElements.addAll(topLevelClasses);
rootElements.addAll(packageInfoFiles);
+ rootElements.addAll(moduleInfoFiles);
rootElements = Collections.unmodifiableSet(rootElements);
RoundEnvironment renv = new JavacRoundEnvironment(false,
@@ -986,7 +990,7 @@
/** The trees that need to be cleaned - includes roots and implicitly parsed trees. */
Set<JCCompilationUnit> treesToClean;
/** The classes to be compiler that have were generated. */
- Map<String, JavaFileObject> genClassFiles;
+ Map<ModuleSymbol, Map<String, JavaFileObject>> genClassFiles;
/** The set of annotations to be processed this round. */
Set<TypeElement> annotationsPresent;
@@ -994,6 +998,8 @@
List<ClassSymbol> topLevelClasses;
/** The set of package-info files to be processed this round. */
List<PackageSymbol> packageInfoFiles;
+ /** The set of module-info files to be processed this round. */
+ List<ModuleSymbol> moduleInfoFiles;
/** Create a round (common code). */
private Round(int number, Set<JCCompilationUnit> treesToClean,
@@ -1011,6 +1017,7 @@
// the following will be populated as needed
topLevelClasses = List.nil();
packageInfoFiles = List.nil();
+ moduleInfoFiles = List.nil();
this.treesToClean = treesToClean;
}
@@ -1031,12 +1038,14 @@
packageInfoFiles = getPackageInfoFiles(roots);
+ moduleInfoFiles = getModuleInfoFiles(roots);
+
findAnnotationsPresent();
}
/** Create a new round. */
private Round(Round prev,
- Set<JavaFileObject> newSourceFiles, Map<String,JavaFileObject> newClassFiles) {
+ Set<JavaFileObject> newSourceFiles, Map<ModuleSymbol, Map<String,JavaFileObject>> newClassFiles) {
this(prev.number+1, prev.treesToClean, null);
prev.newRound();
this.genClassFiles = prev.genClassFiles;
@@ -1048,9 +1057,13 @@
if (unrecoverableError())
return;
+ roots = compiler.initModules(roots);
+
enterClassFiles(genClassFiles);
List<ClassSymbol> newClasses = enterClassFiles(newClassFiles);
- genClassFiles.putAll(newClassFiles);
+ for (Entry<ModuleSymbol, Map<String, JavaFileObject>> moduleAndClassFiles : newClassFiles.entrySet()) {
+ genClassFiles.computeIfAbsent(moduleAndClassFiles.getKey(), m -> new LinkedHashMap<>()).putAll(moduleAndClassFiles.getValue());
+ }
enterTrees(roots);
if (unrecoverableError())
@@ -1064,11 +1077,13 @@
getPackageInfoFiles(parsedFiles),
getPackageInfoFilesFromClasses(newClasses));
+ moduleInfoFiles = List.nil(); //module-info cannot be generated
+
findAnnotationsPresent();
}
/** Create the next round to be used. */
- Round next(Set<JavaFileObject> newSourceFiles, Map<String, JavaFileObject> newClassFiles) {
+ Round next(Set<JavaFileObject> newSourceFiles, Map<ModuleSymbol, Map<String, JavaFileObject>> newClassFiles) {
return new Round(this, newSourceFiles, newClassFiles);
}
@@ -1121,45 +1136,47 @@
annotationComputer.scan(classSym, annotationsPresent);
for (PackageSymbol pkgSym : packageInfoFiles)
annotationComputer.scan(pkgSym, annotationsPresent);
+ for (ModuleSymbol mdlSym : moduleInfoFiles)
+ annotationComputer.scan(mdlSym, annotationsPresent);
}
/** Enter a set of generated class files. */
- private List<ClassSymbol> enterClassFiles(Map<String, JavaFileObject> classFiles) {
+ private List<ClassSymbol> enterClassFiles(Map<ModuleSymbol, Map<String, JavaFileObject>> modulesAndClassFiles) {
List<ClassSymbol> list = List.nil();
- for (Map.Entry<String,JavaFileObject> entry : classFiles.entrySet()) {
- Name name = names.fromString(entry.getKey());
- JavaFileObject file = entry.getValue();
- if (file.getKind() != JavaFileObject.Kind.CLASS)
- throw new AssertionError(file);
- ClassSymbol cs;
- // TODO: for now, we assume that generated code is in a default module;
- // in time, we need a way to be able to specify the module for generated code
- if (isPkgInfo(file, JavaFileObject.Kind.CLASS)) {
- Name packageName = Convert.packagePart(name);
- PackageSymbol p = symtab.enterPackage(defaultModule, packageName);
- if (p.package_info == null)
- p.package_info = symtab.enterClass(defaultModule, Convert.shortName(name), p);
- cs = p.package_info;
- cs.reset();
- if (cs.classfile == null)
+ for (Entry<ModuleSymbol, Map<String, JavaFileObject>> moduleAndClassFiles : modulesAndClassFiles.entrySet()) {
+ for (Map.Entry<String,JavaFileObject> entry : moduleAndClassFiles.getValue().entrySet()) {
+ Name name = names.fromString(entry.getKey());
+ JavaFileObject file = entry.getValue();
+ if (file.getKind() != JavaFileObject.Kind.CLASS)
+ throw new AssertionError(file);
+ ClassSymbol cs;
+ if (isPkgInfo(file, JavaFileObject.Kind.CLASS)) {
+ Name packageName = Convert.packagePart(name);
+ PackageSymbol p = symtab.enterPackage(moduleAndClassFiles.getKey(), packageName);
+ if (p.package_info == null)
+ p.package_info = symtab.enterClass(moduleAndClassFiles.getKey(), Convert.shortName(name), p);
+ cs = p.package_info;
+ cs.reset();
+ if (cs.classfile == null)
+ cs.classfile = file;
+ cs.completer = initialCompleter;
+ } else {
+ cs = symtab.enterClass(moduleAndClassFiles.getKey(), name);
+ cs.reset();
cs.classfile = file;
- cs.completer = initialCompleter;
- } else {
- cs = symtab.enterClass(defaultModule, name);
- cs.reset();
- cs.classfile = file;
- cs.completer = initialCompleter;
- cs.owner.members().enter(cs); //XXX - OverwriteBetweenCompilations; syms.getClass is not sufficient anymore
+ cs.completer = initialCompleter;
+ cs.owner.members().enter(cs); //XXX - OverwriteBetweenCompilations; syms.getClass is not sufficient anymore
+ }
+ list = list.prepend(cs);
}
- list = list.prepend(cs);
}
return list.reverse();
}
/** Enter a set of syntax trees. */
private void enterTrees(List<JCCompilationUnit> roots) {
- compiler.enterTrees(compiler.initModules(roots));
+ compiler.enterTrees(roots);
}
/** Run a processing round. */
@@ -1179,7 +1196,7 @@
JavacProcessingEnvironment.this);
discoveredProcs.iterator().runContributingProcs(renv);
} else {
- discoverAndRunProcs(annotationsPresent, topLevelClasses, packageInfoFiles);
+ discoverAndRunProcs(annotationsPresent, topLevelClasses, packageInfoFiles, moduleInfoFiles);
}
} catch (Throwable t) {
// we're specifically expecting Abort here, but if any Throwable
@@ -1418,6 +1435,18 @@
return packages.reverse();
}
+ private List<ModuleSymbol> getModuleInfoFiles(List<? extends JCCompilationUnit> units) {
+ List<ModuleSymbol> modules = List.nil();
+ for (JCCompilationUnit unit : units) {
+ if (isModuleInfo(unit.sourcefile, JavaFileObject.Kind.SOURCE) &&
+ unit.defs.nonEmpty() &&
+ unit.defs.head.hasTag(Tag.MODULEDEF)) {
+ modules = modules.prepend(unit.modle);
+ }
+ }
+ return modules.reverse();
+ }
+
// avoid unchecked warning from use of varargs
private static <T> List<T> join(List<T> list1, List<T> list2) {
return list1.appendList(list2);
@@ -1431,6 +1460,10 @@
return isPkgInfo(sym.classfile, JavaFileObject.Kind.CLASS) && (sym.packge().package_info == sym);
}
+ private boolean isModuleInfo(JavaFileObject fo, JavaFileObject.Kind kind) {
+ return fo.isNameCompatible("module-info", kind);
+ }
+
/*
* Called retroactively to determine if a class loader was required,
* after we have failed to create one.
@@ -1625,8 +1658,21 @@
* import-style string, return a regex that won't match anything.
*/
private static Pattern importStringToPattern(String s, Processor p, Log log) {
- if (MatchingUtils.isValidImportString(s)) {
- return MatchingUtils.validImportStringToPattern(s);
+ String module;
+ String pkg;
+ int slash = s.indexOf('/');
+ if (slash == (-1)) {
+ if (s.equals("*")) {
+ return MatchingUtils.validImportStringToPattern(s);
+ }
+ module = ".*/";
+ pkg = s;
+ } else {
+ module = Pattern.quote(s.substring(0, slash + 1));
+ pkg = s.substring(slash + 1);
+ }
+ if (MatchingUtils.isValidImportString(pkg)) {
+ return Pattern.compile(module + MatchingUtils.validImportStringToPatternString(pkg));
} else {
log.warning("proc.malformed.supported.string", s, p.getClass().getName());
return noMatches; // won't match any valid identifier
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/MatchingUtils.java Tue Dec 13 10:48:18 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/MatchingUtils.java Tue Dec 13 10:49:28 2016 +0100
@@ -36,7 +36,9 @@
* deletion without notice.</b>
*/
public class MatchingUtils {
- private static final Pattern allMatches = Pattern.compile(".*");
+
+ private static final String allMatchesString = ".*";
+ private static final Pattern allMatches = Pattern.compile(allMatchesString);
/**
* Return true if the argument string is a valid import-style
@@ -72,9 +74,9 @@
return valid;
}
- public static Pattern validImportStringToPattern(String s) {
+ public static String validImportStringToPatternString(String s) {
if (s.equals("*")) {
- return allMatches;
+ return allMatchesString;
} else {
String s_prime = s.replace(".", "\\.");
@@ -82,7 +84,17 @@
s_prime = s_prime.substring(0, s_prime.length() - 1) + ".+";
}
- return Pattern.compile(s_prime);
+ return s_prime;
+ }
+ }
+
+ public static Pattern validImportStringToPattern(String s) {
+ String pattern = validImportStringToPatternString(s);
+
+ if (pattern == allMatchesString) {
+ return allMatches;
+ } else {
+ return Pattern.compile(pattern);
}
}
--- a/langtools/test/tools/javac/modules/AnnotationProcessing.java Tue Dec 13 10:48:18 2016 +0100
+++ b/langtools/test/tools/javac/modules/AnnotationProcessing.java Tue Dec 13 10:49:28 2016 +0100
@@ -33,23 +33,35 @@
* @run main AnnotationProcessing
*/
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.FilerException;
import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.ModuleElement.ProvidesDirective;
import javax.lang.model.element.ModuleElement.UsesDirective;
@@ -60,10 +72,20 @@
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.ElementScanner9;
import javax.tools.Diagnostic.Kind;
+import javax.tools.FileObject;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileManager.Location;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
import toolbox.JavacTask;
import toolbox.Task;
import toolbox.Task.Mode;
+import toolbox.Task.OutputKind;
public class AnnotationProcessing extends ModuleTestBase {
@@ -135,6 +157,7 @@
public static final class AP extends AbstractProcessor {
private Map<String, List<String>> module2ExpectedEnclosedElements;
+ private Set<String> seenModules = new HashSet<>();
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
@@ -181,12 +204,16 @@
.map(p -> p.getQualifiedName().toString())
.collect(Collectors.toList());
- assertEquals(module2ExpectedEnclosedElements.remove(module.getQualifiedName().toString()),
+ String moduleName = module.getQualifiedName().toString();
+
+ assertEquals(module2ExpectedEnclosedElements.get(moduleName),
actualElements);
+
+ seenModules.add(moduleName);
}
if (roundEnv.processingOver()) {
- assertEquals(true, module2ExpectedEnclosedElements.isEmpty());
+ assertEquals(module2ExpectedEnclosedElements.keySet(), seenModules);
}
return false;
@@ -374,6 +401,617 @@
}
+ @Test
+ public void testModuleInRootElements(Path base) throws Exception {
+ Path moduleSrc = base.resolve("module-src");
+ Path m1 = moduleSrc.resolve("m1");
+
+ Path classes = base.resolve("classes");
+
+ Files.createDirectories(classes);
+
+ tb.writeJavaFiles(m1,
+ "module m1 { exports api; }",
+ "package api; public class Api { }");
+
+ List<String> log = new JavacTask(tb)
+ .options("-processor", ModuleInRootElementsAP.class.getName())
+ .outdir(classes)
+ .files(findJavaFiles(moduleSrc))
+ .run()
+ .writeAll()
+ .getOutputLines(Task.OutputKind.STDERR);
+
+ assertEquals(Arrays.asList("module: m1"), log);
+ }
+
+ @SupportedAnnotationTypes("*")
+ public static final class ModuleInRootElementsAP extends AbstractProcessor {
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ roundEnv.getRootElements()
+ .stream()
+ .filter(el -> el.getKind() == ElementKind.MODULE)
+ .forEach(mod -> System.err.println("module: " + mod.getSimpleName()));
+
+ return false;
+ }
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ }
+
+ @Test
+ public void testAnnotationsInModuleInfo(Path base) throws Exception {
+ Path moduleSrc = base.resolve("module-src");
+ Path m1 = moduleSrc.resolve("m1");
+
+ tb.writeJavaFiles(m1,
+ "@Deprecated module m1 { }");
+
+ Path m2 = moduleSrc.resolve("m2");
+
+ tb.writeJavaFiles(m2,
+ "@SuppressWarnings(\"\") module m2 { }");
+
+ Path classes = base.resolve("classes");
+
+ Files.createDirectories(classes);
+
+ List<String> log = new JavacTask(tb)
+ .options("-processor", AnnotationsInModuleInfoPrint.class.getName())
+ .outdir(classes)
+ .files(findJavaFiles(m1))
+ .run()
+ .writeAll()
+ .getOutputLines(Task.OutputKind.DIRECT);
+
+ List<String> expectedLog = Arrays.asList("Note: AP Invoked",
+ "Note: AP Invoked");
+
+ assertEquals(expectedLog, log);
+
+ new JavacTask(tb)
+ .options("-processor", AnnotationsInModuleInfoFail.class.getName())
+ .outdir(classes)
+ .files(findJavaFiles(m2))
+ .run()
+ .writeAll();
+ }
+
+ @SupportedAnnotationTypes("java.lang.Deprecated")
+ public static final class AnnotationsInModuleInfoPrint extends AbstractProcessor {
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ processingEnv.getMessager().printMessage(Kind.NOTE, "AP Invoked");
+ return false;
+ }
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ }
+
+ @SupportedAnnotationTypes("java.lang.Deprecated")
+ public static final class AnnotationsInModuleInfoFail extends AbstractProcessor {
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ throw new AssertionError();
+ }
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ }
+
+ @Test
+ public void testGenerateInMultiModeAPI(Path base) throws Exception {
+ Path moduleSrc = base.resolve("module-src");
+ Path classes = base.resolve("classes");
+
+ Files.createDirectories(classes);
+
+ Path m1 = moduleSrc.resolve("m1");
+
+ tb.writeJavaFiles(m1,
+ "module m1 { exports api1; }",
+ "package api1; public class Api { GenApi ga; impl.Impl i; }");
+
+ writeFile("1", m1, "api1", "api");
+ writeFile("1", m1, "impl", "impl");
+
+ Path m2 = moduleSrc.resolve("m2");
+
+ tb.writeJavaFiles(m2,
+ "module m2 { requires m1; exports api2; }",
+ "package api2; public class Api { api1.GenApi ga1; GenApi qa2; impl.Impl i;}");
+
+ writeFile("2", m2, "api2", "api");
+ writeFile("2", m2, "impl", "impl");
+
+ for (FileType fileType : FileType.values()) {
+ if (Files.isDirectory(classes)) {
+ tb.cleanDirectory(classes);
+ } else {
+ Files.createDirectories(classes);
+ }
+
+ new JavacTask(tb)
+ .options("-processor", MultiModeAPITestAP.class.getName(),
+ "--module-source-path", moduleSrc.toString(),
+ "-Afiletype=" + fileType.name())
+ .outdir(classes)
+ .files(findJavaFiles(moduleSrc))
+ .run()
+ .writeAll();
+
+ assertFileExists(classes, "m1", "api1", "GenApi.class");
+ assertFileExists(classes, "m1", "impl", "Impl.class");
+ assertFileExists(classes, "m1", "api1", "gen1");
+ assertFileExists(classes, "m2", "api2", "GenApi.class");
+ assertFileExists(classes, "m2", "impl", "Impl.class");
+ assertFileExists(classes, "m2", "api2", "gen1");
+ }
+ }
+
+ enum FileType {
+ SOURCE,
+ CLASS;
+ }
+
+ public static abstract class GeneratingAP extends AbstractProcessor {
+
+ void createSource(CreateFileObject file, String name, String content) {
+ try (Writer out = file.create().openWriter()) {
+ out.write(content);
+ } catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ void createClass(CreateFileObject file, String name, String content) {
+ String fileNameStub = name.replace(".", File.separator);
+
+ try (OutputStream out = file.create().openOutputStream()) {
+ Path scratch = Files.createDirectories(Paths.get(""));
+ Path scratchSrc = scratch.resolve(fileNameStub + ".java").toAbsolutePath();
+
+ Files.createDirectories(scratchSrc.getParent());
+
+ try (Writer w = Files.newBufferedWriter(scratchSrc)) {
+ w.write(content);
+ }
+
+ Path scratchClasses = scratch.resolve("classes");
+
+ Files.createDirectories(scratchClasses);
+
+ JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
+ try (StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null)) {
+ List<String> options = Arrays.asList("-d", scratchClasses.toString());
+ Iterable<? extends JavaFileObject> files = fm.getJavaFileObjects(scratchSrc);
+ CompilationTask task = comp.getTask(null, fm, null, options, null, files);
+
+ if (!task.call()) {
+ throw new AssertionError("compilation failed");
+ }
+ }
+
+ Path classfile = scratchClasses.resolve(fileNameStub + ".class");
+
+ Files.copy(classfile, out);
+ } catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ void doReadResource(CreateFileObject file, String expectedContent) {
+ try {
+ StringBuilder actualContent = new StringBuilder();
+
+ try (Reader r = file.create().openReader(true)) {
+ int read;
+
+ while ((read = r.read()) != (-1)) {
+ actualContent.append((char) read);
+ }
+
+ }
+
+ assertEquals(expectedContent, actualContent.toString());
+ } catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ public interface CreateFileObject {
+ public FileObject create() throws IOException;
+ }
+
+ void expectFilerException(Callable<Object> c) {
+ try {
+ c.call();
+ throw new AssertionError("Expected exception not thrown");
+ } catch (FilerException ex) {
+ //expected
+ } catch (Exception ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ }
+
+ @SupportedAnnotationTypes("*")
+ @SupportedOptions({"filetype", "modulename"})
+ public static final class MultiModeAPITestAP extends GeneratingAP {
+
+ int round;
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (round++ != 0)
+ return false;
+
+ createClass("m1", "api1.GenApi", "package api1; public class GenApi {}");
+ createClass("m1", "impl.Impl", "package impl; public class Impl {}");
+ createClass("m2", "api2.GenApi", "package api2; public class GenApi {}");
+ createClass("m2", "impl.Impl", "package impl; public class Impl {}");
+
+ createResource("m1", "api1", "gen1");
+ createResource("m2", "api2", "gen1");
+
+ readResource("m1", "api1", "api", "1");
+ readResource("m1", "impl", "impl", "1");
+ readResource("m2", "api2", "api", "2");
+ readResource("m2", "impl", "impl", "2");
+
+ Filer filer = processingEnv.getFiler();
+
+ expectFilerException(() -> filer.createSourceFile("fail.Fail"));
+ expectFilerException(() -> filer.createClassFile("fail.Fail"));
+ expectFilerException(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "fail", "fail"));
+ expectFilerException(() -> filer.getResource(StandardLocation.MODULE_SOURCE_PATH, "fail", "fail"));
+
+ //must not generate to unnamed package:
+ expectFilerException(() -> filer.createSourceFile("m1/Fail"));
+ expectFilerException(() -> filer.createClassFile("m1/Fail"));
+
+ //cannot generate resources to modules that are not root modules:
+ expectFilerException(() -> filer.createSourceFile("java.base/fail.Fail"));
+ expectFilerException(() -> filer.createClassFile("java.base/fail.Fail"));
+ expectFilerException(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "java.base/fail", "Fail"));
+
+ return false;
+ }
+
+ void createClass(String expectedModule, String name, String content) {
+ Filer filer = processingEnv.getFiler();
+ FileType filetype = FileType.valueOf(processingEnv.getOptions().getOrDefault("filetype", ""));
+
+ switch (filetype) {
+ case SOURCE:
+ createSource(() -> filer.createSourceFile(expectedModule + "/" + name), name, content);
+ break;
+ case CLASS:
+ createClass(() -> filer.createClassFile(expectedModule + "/" + name), name, content);
+ break;
+ default:
+ throw new AssertionError("Unexpected filetype: " + filetype);
+ }
+ }
+
+ void createResource(String expectedModule, String pkg, String relName) {
+ try {
+ Filer filer = processingEnv.getFiler();
+
+ filer.createResource(StandardLocation.CLASS_OUTPUT, expectedModule + "/" + pkg, relName)
+ .openOutputStream()
+ .close();
+ } catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ void readResource(String expectedModule, String pkg, String relName, String expectedContent) {
+ Filer filer = processingEnv.getFiler();
+
+ doReadResource(() -> filer.getResource(StandardLocation.MODULE_SOURCE_PATH, expectedModule + "/" + pkg, relName),
+ expectedContent);
+ }
+
+ }
+
+ @Test
+ public void testGenerateInSingleNameModeAPI(Path base) throws Exception {
+ Path classes = base.resolve("classes");
+
+ Files.createDirectories(classes);
+
+ Path m1 = base.resolve("module-src");
+
+ tb.writeJavaFiles(m1,
+ "module m1 { }");
+
+ writeFile("3", m1, "impl", "resource");
+
+ new JavacTask(tb)
+ .options("-processor", SingleNameModeAPITestAP.class.getName(),
+ "-sourcepath", m1.toString())
+ .outdir(classes)
+ .files(findJavaFiles(m1))
+ .run()
+ .writeAll();
+
+ assertFileExists(classes, "impl", "Impl1.class");
+ assertFileExists(classes, "impl", "Impl2.class");
+ assertFileExists(classes, "impl", "Impl3");
+ assertFileExists(classes, "impl", "Impl4.class");
+ assertFileExists(classes, "impl", "Impl5.class");
+ assertFileExists(classes, "impl", "Impl6");
+ assertFileExists(classes, "impl", "Impl7.class");
+ assertFileExists(classes, "impl", "Impl8.class");
+ assertFileExists(classes, "impl", "Impl9");
+ }
+
+
+ @SupportedAnnotationTypes("*")
+ public static final class SingleNameModeAPITestAP extends GeneratingAP {
+
+ int round;
+
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (round++ != 0)
+ return false;
+
+ Filer filer = processingEnv.getFiler();
+
+ createSource(() -> filer.createSourceFile("impl.Impl1"), "impl.Impl1", "package impl; class Impl1 {}");
+ createClass(() -> filer.createClassFile("impl.Impl2"), "impl.Impl2", "package impl; class Impl2 {}");
+ createSource(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "impl", "Impl3"), "impl.Impl3", "");
+ doReadResource(() -> filer.getResource(StandardLocation.SOURCE_PATH, "impl", "resource"), "3");
+
+ createSource(() -> filer.createSourceFile("m1/impl.Impl4"), "impl.Impl4", "package impl; class Impl4 {}");
+ createClass(() -> filer.createClassFile("m1/impl.Impl5"), "impl.Impl5", "package impl; class Impl5 {}");
+ createSource(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "m1/impl", "Impl6"), "impl.Impl6", "");
+ doReadResource(() -> filer.getResource(StandardLocation.SOURCE_PATH, "m1/impl", "resource"), "3");
+
+ TypeElement jlObject = processingEnv.getElementUtils().getTypeElement("java.lang.Object");
+
+ //"broken" originating element:
+ createSource(() -> filer.createSourceFile("impl.Impl7", jlObject), "impl.Impl7", "package impl; class Impl7 {}");
+ createClass(() -> filer.createClassFile("impl.Impl8", jlObject), "impl.Impl8", "package impl; class Impl8 {}");
+ createSource(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "impl", "Impl9", jlObject), "impl.Impl9", "");
+
+ //must not generate to unnamed package:
+ expectFilerException(() -> filer.createSourceFile("Fail"));
+ expectFilerException(() -> filer.createClassFile("Fail"));
+ expectFilerException(() -> filer.createSourceFile("m1/Fail"));
+ expectFilerException(() -> filer.createClassFile("m1/Fail"));
+
+ //cannot generate resources to modules that are not root modules:
+ expectFilerException(() -> filer.createSourceFile("java.base/fail.Fail"));
+ expectFilerException(() -> filer.createClassFile("java.base/fail.Fail"));
+ expectFilerException(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "java.base/fail", "Fail"));
+
+ return false;
+ }
+
+ }
+
+ @Test
+ public void testGenerateInUnnamedModeAPI(Path base) throws Exception {
+ Path classes = base.resolve("classes");
+
+ Files.createDirectories(classes);
+
+ Path src = base.resolve("src");
+
+ tb.writeJavaFiles(src,
+ "class T {}");
+
+ new JavacTask(tb)
+ .options("-processor", UnnamedModeAPITestAP.class.getName(),
+ "-sourcepath", src.toString())
+ .outdir(classes)
+ .files(findJavaFiles(src))
+ .run()
+ .writeAll();
+
+ assertFileExists(classes, "Impl1.class");
+ assertFileExists(classes, "Impl2.class");
+ }
+
+ @Test
+ public void testGenerateInNoModeAPI(Path base) throws Exception {
+ Path classes = base.resolve("classes");
+
+ Files.createDirectories(classes);
+
+ Path src = base.resolve("src");
+
+ tb.writeJavaFiles(src,
+ "class T {}");
+
+ new JavacTask(tb)
+ .options("-processor", UnnamedModeAPITestAP.class.getName(),
+ "-source", "8", "-target", "8",
+ "-sourcepath", src.toString())
+ .outdir(classes)
+ .files(findJavaFiles(src))
+ .run()
+ .writeAll();
+
+ assertFileExists(classes, "Impl1.class");
+ assertFileExists(classes, "Impl2.class");
+ }
+
+ @SupportedAnnotationTypes("*")
+ public static final class UnnamedModeAPITestAP extends GeneratingAP {
+
+ int round;
+
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (round++ != 0)
+ return false;
+
+ Filer filer = processingEnv.getFiler();
+
+ //must not generate to unnamed package:
+ createSource(() -> filer.createSourceFile("Impl1"), "Impl1", "class Impl1 {}");
+ createClass(() -> filer.createClassFile("Impl2"), "Impl2", "class Impl2 {}");
+
+ return false;
+ }
+
+ }
+
+ @Test
+ public void testDisambiguateAnnotations(Path base) throws Exception {
+ Path classes = base.resolve("classes");
+
+ Files.createDirectories(classes);
+
+ Path src = base.resolve("src");
+ Path m1 = src.resolve("m1");
+
+ tb.writeJavaFiles(m1,
+ "module m1 { exports api; }",
+ "package api; public @interface A {}",
+ "package api; public @interface B {}");
+
+ Path m2 = src.resolve("m2");
+
+ tb.writeJavaFiles(m2,
+ "module m2 { exports api; }",
+ "package api; public @interface A {}",
+ "package api; public @interface B {}");
+
+ Path m3 = src.resolve("m3");
+
+ tb.writeJavaFiles(m3,
+ "module m3 { requires m1; }",
+ "package impl; import api.*; @A @B public class T {}");
+
+ Path m4 = src.resolve("m4");
+
+ tb.writeJavaFiles(m4,
+ "module m4 { requires m2; }",
+ "package impl; import api.*; @A @B public class T {}");
+
+ List<String> log;
+ List<String> expected;
+
+ log = new JavacTask(tb)
+ .options("-processor", SelectAnnotationATestAP.class.getName() + "," + SelectAnnotationBTestAP.class.getName(),
+ "--module-source-path", src.toString(),
+ "-m", "m1,m2")
+ .outdir(classes)
+ .run()
+ .writeAll()
+ .getOutputLines(OutputKind.STDERR);
+
+ expected = Arrays.asList("");
+
+ if (!expected.equals(log)) {
+ throw new AssertionError("Output does not match; output: " + log);
+ }
+
+ log = new JavacTask(tb)
+ .options("-processor", SelectAnnotationATestAP.class.getName() + "," + SelectAnnotationBTestAP.class.getName(),
+ "--module-source-path", src.toString(),
+ "-m", "m3")
+ .outdir(classes)
+ .run()
+ .writeAll()
+ .getOutputLines(OutputKind.STDERR);
+
+ expected = Arrays.asList("SelectAnnotationBTestAP",
+ "SelectAnnotationBTestAP");
+
+ if (!expected.equals(log)) {
+ throw new AssertionError("Output does not match; output: " + log);
+ }
+
+ log = new JavacTask(tb)
+ .options("-processor", SelectAnnotationATestAP.class.getName() + "," + SelectAnnotationBTestAP.class.getName(),
+ "--module-source-path", src.toString(),
+ "-m", "m4")
+ .outdir(classes)
+ .run()
+ .writeAll()
+ .getOutputLines(OutputKind.STDERR);
+
+ expected = Arrays.asList("SelectAnnotationATestAP",
+ "SelectAnnotationBTestAP",
+ "SelectAnnotationATestAP",
+ "SelectAnnotationBTestAP");
+
+ if (!expected.equals(log)) {
+ throw new AssertionError("Output does not match; output: " + log);
+ }
+ }
+
+ @SupportedAnnotationTypes("m2/api.A")
+ public static final class SelectAnnotationATestAP extends AbstractProcessor {
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ System.err.println("SelectAnnotationATestAP");
+
+ return false;
+ }
+
+ }
+
+ @SupportedAnnotationTypes("api.B")
+ public static final class SelectAnnotationBTestAP extends AbstractProcessor {
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ System.err.println("SelectAnnotationBTestAP");
+
+ return false;
+ }
+
+ }
+
+ private static void writeFile(String content, Path base, String... pathElements) throws IOException {
+ Path file = resolveFile(base, pathElements);
+
+ Files.createDirectories(file.getParent());
+
+ try (Writer out = Files.newBufferedWriter(file)) {
+ out.append(content);
+ }
+ }
+
private static void assertNonNull(String msg, Object val) {
if (val == null) {
throw new AssertionError(msg);
@@ -392,4 +1030,22 @@
}
}
+ private static void assertFileExists(Path base, String... pathElements) {
+ Path file = resolveFile(base, pathElements);
+
+ if (!Files.exists(file)) {
+ throw new AssertionError("Expected file: " + file + " exist, but it does not.");
+ }
+ }
+
+ static Path resolveFile(Path base, String... pathElements) {
+ Path file = base;
+
+ for (String el : pathElements) {
+ file = file.resolve(el);
+ }
+
+ return file;
+ }
+
}
--- a/langtools/test/tools/javac/processing/options/testPrintProcessorInfo/Test.out Tue Dec 13 10:48:18 2016 +0100
+++ b/langtools/test/tools/javac/processing/options/testPrintProcessorInfo/Test.out Tue Dec 13 10:49:28 2016 +0100
@@ -1,4 +1,4 @@
-Processor Test matches [java.lang.Override] and returns true.
+Processor Test matches [java.base/java.lang.Override] and returns true.
- compiler.note.proc.messager: round 1
Processor Test matches [] and returns true.
- compiler.note.proc.messager: round 2