# HG changeset patch # User jlahoda # Date 1481622568 -3600 # Node ID 050370edaade4488f86622704004e2820474b3ac # Parent 058fc03646d92619f84fe37d26e340d687cad111 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 diff -r 058fc03646d9 -r 050370edaade langtools/src/java.compiler/share/classes/javax/annotation/processing/Filer.java --- 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 originating elements 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. * *

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. * - *

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 decorator-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). + * + *

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"}. * - *

Note that to use a particular {@linkplain + *

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"}. + * + *

Creating a source file in or for an unnamed package in a named + * module is not 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). + * + *

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. * - *

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. + *

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"}. + * + *

Creating a class file in or for an unnamed package in a named + * module is not 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}. * - *

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. + * + *

Files created via this method are not 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. * + *

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; } diff -r 058fc03646d9 -r 050370edaade langtools/src/java.compiler/share/classes/javax/annotation/processing/Processor.java --- 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 not 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 + * not 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 "name.*" * representing the set of all annotation types with canonical - * names beginning with "name.". 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 "name.". + * + * 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. * *

Each string returned in the set must be accepted by the * following grammar: @@ -248,9 +266,12 @@ *

*
*
SupportedAnnotationTypeString: - *
TypeName DotStaropt + *
ModulePrefixopt TypeName DotStaropt *
* * + *
ModulePrefix: + *
TypeName / + * *
DotStar: *
. * *
diff -r 058fc03646d9 -r 050370edaade langtools/src/java.compiler/share/classes/javax/annotation/processing/RoundEnvironment.java --- 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 included in this + * package elements, module elements, and type elements included 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 included in this + * package elements, module elements, and type elements included 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, diff -r 058fc03646d9 -r 050370edaade langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java --- 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 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 allRequires = new HashSet<>(); diff -r 058fc03646d9 -r 050370edaade langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java --- 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); } diff -r 058fc03646d9 -r 050370edaade langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacFiler.java --- 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 { 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 generatedClasses; + private final Map> 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 aggregateGeneratedSourceNames; + private final Set> aggregateGeneratedSourceNames; /** * Names of all created class files. Its iterators should * preserve insertion order. */ - private final Set aggregateGeneratedClassNames; + private final Set> 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()); generatedSourceNames = synchronizedSet(new LinkedHashSet()); generatedSourceFileObjects = synchronizedSet(new LinkedHashSet()); - generatedClasses = synchronizedMap(new LinkedHashMap()); + generatedClasses = synchronizedMap(new LinkedHashMap<>()); openTypeNames = synchronizedSet(new LinkedHashSet()); @@ -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 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 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 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 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 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 getGeneratedClasses() { + public Map> 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; diff -r 058fc03646d9 -r 050370edaade langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java --- 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 annotationsPresent, List topLevelClasses, - List packageInfoFiles) { + List packageInfoFiles, + List moduleInfoFiles) { Map 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 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 treesToClean; /** The classes to be compiler that have were generated. */ - Map genClassFiles; + Map> genClassFiles; /** The set of annotations to be processed this round. */ Set annotationsPresent; @@ -994,6 +998,8 @@ List topLevelClasses; /** The set of package-info files to be processed this round. */ List packageInfoFiles; + /** The set of module-info files to be processed this round. */ + List moduleInfoFiles; /** Create a round (common code). */ private Round(int number, Set 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 newSourceFiles, Map newClassFiles) { + Set newSourceFiles, Map> 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 newClasses = enterClassFiles(newClassFiles); - genClassFiles.putAll(newClassFiles); + for (Entry> 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 newSourceFiles, Map newClassFiles) { + Round next(Set newSourceFiles, Map> 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 enterClassFiles(Map classFiles) { + private List enterClassFiles(Map> modulesAndClassFiles) { List list = List.nil(); - for (Map.Entry 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> moduleAndClassFiles : modulesAndClassFiles.entrySet()) { + for (Map.Entry 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 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 getModuleInfoFiles(List units) { + List 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 List join(List list1, List 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 diff -r 058fc03646d9 -r 050370edaade langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/MatchingUtils.java --- 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. */ 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); } } diff -r 058fc03646d9 -r 050370edaade langtools/test/tools/javac/modules/AnnotationProcessing.java --- 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> module2ExpectedEnclosedElements; + private Set seenModules = new HashSet<>(); @Override public boolean process(Set 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 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 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 log = new JavacTask(tb) + .options("-processor", AnnotationsInModuleInfoPrint.class.getName()) + .outdir(classes) + .files(findJavaFiles(m1)) + .run() + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + + List 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 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 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 options = Arrays.asList("-d", scratchClasses.toString()); + Iterable 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 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 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 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 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 log; + List 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 annotations, RoundEnvironment roundEnv) { + System.err.println("SelectAnnotationATestAP"); + + return false; + } + + } + + @SupportedAnnotationTypes("api.B") + public static final class SelectAnnotationBTestAP extends AbstractProcessor { + + @Override + public boolean process(Set 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; + } + } diff -r 058fc03646d9 -r 050370edaade langtools/test/tools/javac/processing/options/testPrintProcessorInfo/Test.out --- 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