changeset 3816:a079b797c83d

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
author jlahoda
date Tue, 13 Dec 2016 10:49:28 +0100
parents cea064fe9c1d
children 50135a630f35
files src/java.compiler/share/classes/javax/annotation/processing/Filer.java src/java.compiler/share/classes/javax/annotation/processing/Processor.java src/java.compiler/share/classes/javax/annotation/processing/RoundEnvironment.java src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacFiler.java src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java src/jdk.compiler/share/classes/com/sun/tools/javac/util/MatchingUtils.java test/tools/javac/modules/AnnotationProcessing.java test/tools/javac/processing/options/testPrintProcessorInfo/Test.out
diffstat 10 files changed, 1016 insertions(+), 138 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.compiler/share/classes/javax/annotation/processing/Filer.java	Tue Dec 13 10:48:18 2016 +0100
+++ b/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/src/java.compiler/share/classes/javax/annotation/processing/Processor.java	Tue Dec 13 10:48:18 2016 +0100
+++ b/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 &quot;<tt><i>name</i>.*</tt>&quot;
      * representing the set of all annotation types with canonical
-     * names beginning with &quot;<tt><i>name.</i></tt>&quot;.  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 &quot;<tt><i>name.</i></tt>&quot;.
+     *
+     * 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/src/java.compiler/share/classes/javax/annotation/processing/RoundEnvironment.java	Tue Dec 13 10:48:18 2016 +0100
+++ b/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/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java	Tue Dec 13 10:48:18 2016 +0100
+++ b/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/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java	Tue Dec 13 10:48:18 2016 +0100
+++ b/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/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacFiler.java	Tue Dec 13 10:48:18 2016 +0100
+++ b/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);
     }
 
-    @DefinedBy(Api.ANNOTATION_PROCESSING)
-    public JavaFileObject createClassFile(CharSequence name,
-                                           Element... originatingElements) throws IOException {
-        return createSourceOrClassFile(false, name.toString());
+    @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);
     }
 
-    private JavaFileObject createSourceOrClassFile(boolean isSourceFile, String name) throws IOException {
+    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(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/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java	Tue Dec 13 10:48:18 2016 +0100
+++ b/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/src/jdk.compiler/share/classes/com/sun/tools/javac/util/MatchingUtils.java	Tue Dec 13 10:48:18 2016 +0100
+++ b/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/test/tools/javac/modules/AnnotationProcessing.java	Tue Dec 13 10:48:18 2016 +0100
+++ b/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/test/tools/javac/processing/options/testPrintProcessorInfo/Test.out	Tue Dec 13 10:48:18 2016 +0100
+++ b/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