8173914: StandardJavaFileManager.setLocationForModule
authorjjg
Fri, 24 Feb 2017 15:23:14 -0800
changeset 44019 284fa2ebd030
parent 44018 d7e24dd75175
child 44020 ef5f709f4fd8
8173914: StandardJavaFileManager.setLocationForModule Reviewed-by: jlahoda
langtools/src/java.compiler/share/classes/javax/tools/StandardJavaFileManager.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java
langtools/test/tools/javac/file/SetLocationForModule.java
--- a/langtools/src/java.compiler/share/classes/javax/tools/StandardJavaFileManager.java	Thu Feb 23 13:28:55 2017 -0800
+++ b/langtools/src/java.compiler/share/classes/javax/tools/StandardJavaFileManager.java	Fri Feb 24 15:23:14 2017 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2006, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2017, 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
@@ -142,6 +142,17 @@
  * files in the {@linkplain java.nio.file.FileSystems#getDefault() default file system.}
  * It is recommended that implementations should support Path objects from any filesystem.</p>
  *
+ *
+ * @apiNote
+ * Some methods on this interface take a {@code Collection<? extends Path>}
+ * instead of {@code Iterable<? extends Path>}.
+ * This is to prevent the possibility of accidentally calling the method
+ * with a single {@code Path} as such an argument, because although
+ * {@code Path} implements {@code Iterable<Path>}, it would almost never be
+ * correct to call these methods with a single {@code Path} and have it be treated as
+ * an {@code Iterable} of its components.
+ *
+ *
  * @author Peter von der Ah&eacute;
  * @since 1.6
  */
@@ -266,6 +277,10 @@
      * Associates the given search path with the given location.  Any
      * previous value will be discarded.
      *
+     * If the location is a module-oriented or output location, any module-specific
+     * associations set up by {@linkplain #setLocationForModule setLocationForModule}
+     * will be cancelled.
+     *
      * @param location a location
      * @param files a list of files, if {@code null} use the default
      * search path for this location
@@ -279,24 +294,18 @@
         throws IOException;
 
     /**
-     * Associates the given search path with the given location.  Any
-     * previous value will be discarded.
+     * Associates the given search path with the given location.
+     * Any previous value will be discarded.
      *
-     * @apiNote
-     * The type of the {@code paths} parameter is a {@code Collection}
-     * and not {@code Iterable}. This is to prevent the possibility of
-     * accidentally calling the method with a single {@code Path} as
-     * the second argument, because although {@code Path} implements
-     * {@code Iterable<Path>}, it would almost never be correct to call
-     * this method with a single {@code Path} and have it be treated as
-     * an {@code Iterable} of its components.
-     *
+     * If the location is a module-oriented or output location, any module-specific
+     * associations set up by {@linkplain #setLocationForModule setLocationForModule}
+     * will be cancelled.
      *
      * @implSpec
      * The default implementation converts each path to a file and calls
      * {@link #getJavaFileObjectsFromFiles getJavaObjectsFromFiles}.
-     * IllegalArgumentException will be thrown if any of the paths
-     * cannot be converted to a file.
+     * {@linkplain IllegalArgumentException IllegalArgumentException}
+     * will be thrown if any of the paths cannot be converted to a file.
      *
      * @param location a location
      * @param paths a list of paths, if {@code null} use the default
@@ -316,6 +325,37 @@
     }
 
     /**
+     * Associates the given search path with the given module and location,
+     * which must be a module-oriented or output location.
+     * Any previous value will be discarded.
+     * This overrides any default association derived from the search path
+     * associated with the location itself.
+     *
+     * All such module-specific associations will be cancelled if a
+     * new search path is associated with the location by calling
+     * {@linkplain #setLocation setLocation } or
+     * {@linkplain #setLocationFromPaths setLocationFromPaths}.
+     *
+     * @throws IllegalStateException if the location is not a module-oriented
+     *  or output location.
+     * @throws UnsupportedOperationException if this operation is not supported by
+     *  this file manager.
+     * @throws IOException if {@code location} is an output location and
+     * {@code paths} does not represent an existing directory
+     *
+     * @param location the location
+     * @param moduleName the name of the module
+     * @param paths the search path to associate with the location and module.
+     *
+     * @see setLocation
+     * @see setLocationFromPaths
+     */
+    default void setLocationForModule(Location location, String moduleName,
+            Collection<? extends Path> paths) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Returns the search path associated with the given location.
      *
      * @param location a location
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java	Thu Feb 23 13:28:55 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java	Fri Feb 24 15:23:14 2017 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2017, 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
@@ -1005,6 +1005,14 @@
     }
 
     @Override @DefinedBy(Api.COMPILER)
+    public void setLocationForModule(Location location, String moduleName, Collection<? extends Path> paths)
+            throws IOException {
+        nullCheck(location);
+        checkModuleOrientedOrOutputLocation(location);
+        locations.setLocationForModule(location, nullCheck(moduleName), nullCheck(paths));
+    }
+
+    @Override @DefinedBy(Api.COMPILER)
     public String inferModuleName(Location location) {
         checkNotModuleOrientedLocation(location);
         return locations.inferModuleName(location);
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java	Thu Feb 23 13:28:55 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java	Fri Feb 24 15:23:14 2017 -0800
@@ -398,7 +398,7 @@
      * @see #initHandlers
      * @see #getHandler
      */
-    protected abstract class LocationHandler {
+    protected static abstract class LocationHandler {
 
         /**
          * @see JavaFileManager#handleOption
@@ -420,7 +420,13 @@
         /**
          * @see StandardJavaFileManager#setLocation
          */
-        abstract void setPaths(Iterable<? extends Path> files) throws IOException;
+        abstract void setPaths(Iterable<? extends Path> paths) throws IOException;
+
+        /**
+         * @see StandardJavaFileManager#setLocationForModule
+         */
+        abstract void setPathsForModule(String moduleName, Iterable<? extends Path> paths)
+                throws IOException;
 
         /**
          * @see JavaFileManager#getLocationForModule(Location, String)
@@ -454,7 +460,7 @@
     /**
      * A LocationHandler for a given Location, and associated set of options.
      */
-    private abstract class BasicLocationHandler extends LocationHandler {
+    private static abstract class BasicLocationHandler extends LocationHandler {
 
         final Location location;
         final Set<Option> options;
@@ -473,6 +479,36 @@
                     ? EnumSet.noneOf(Option.class)
                     : EnumSet.copyOf(Arrays.asList(options));
         }
+
+        @Override
+        void setPathsForModule(String moduleName, Iterable<? extends Path> files) throws IOException {
+            // should not happen: protected by check in JavacFileManager
+            throw new UnsupportedOperationException("not supported for " + location);
+        }
+
+        protected Path checkSingletonDirectory(Iterable<? extends Path> paths) throws IOException {
+            Iterator<? extends Path> pathIter = paths.iterator();
+            if (!pathIter.hasNext()) {
+                throw new IllegalArgumentException("empty path for directory");
+            }
+            Path path = pathIter.next();
+            if (pathIter.hasNext()) {
+                throw new IllegalArgumentException("path too long for directory");
+            }
+            checkDirectory(path);
+            return path;
+        }
+
+        protected Path checkDirectory(Path path) throws IOException {
+            Objects.requireNonNull(path);
+            if (!Files.exists(path)) {
+                throw new FileNotFoundException(path + ": does not exist");
+            }
+            if (!Files.isDirectory(path)) {
+                throw new IOException(path + ": not a directory");
+            }
+            return path;
+        }
     }
 
     /**
@@ -483,8 +519,7 @@
     private class OutputLocationHandler extends BasicLocationHandler {
 
         private Path outputDir;
-        private Map<String, Location> moduleLocations;
-        private Map<Path, Location> pathLocations;
+        private ModuleTable moduleTable;
 
         OutputLocationHandler(Location location, Option... options) {
             super(location, options);
@@ -510,51 +545,51 @@
         }
 
         @Override
-        void setPaths(Iterable<? extends Path> files) throws IOException {
-            if (files == null) {
+        void setPaths(Iterable<? extends Path> paths) throws IOException {
+            if (paths == null) {
                 outputDir = null;
             } else {
-                Iterator<? extends Path> pathIter = files.iterator();
-                if (!pathIter.hasNext()) {
-                    throw new IllegalArgumentException("empty path for directory");
-                }
-                Path dir = pathIter.next();
-                if (pathIter.hasNext()) {
-                    throw new IllegalArgumentException("path too long for directory");
-                }
-                if (!Files.exists(dir)) {
-                    throw new FileNotFoundException(dir + ": does not exist");
-                } else if (!Files.isDirectory(dir)) {
-                    throw new IOException(dir + ": not a directory");
-                }
-                outputDir = dir;
+                outputDir = checkSingletonDirectory(paths);
             }
-            moduleLocations = null;
-            pathLocations = null;
+            moduleTable = null;
+            listed = false;
         }
 
         @Override
         Location getLocationForModule(String name) {
-            if (moduleLocations == null) {
-                moduleLocations = new HashMap<>();
-                pathLocations = new HashMap<>();
+            if (moduleTable == null) {
+                moduleTable = new ModuleTable();
             }
-            Location l = moduleLocations.get(name);
+            ModuleLocationHandler l = moduleTable.get(name);
             if (l == null) {
                 Path out = outputDir.resolve(name);
-                l = new ModuleLocationHandler(location.getName() + "[" + name + "]",
-                        name,
-                        Collections.singleton(out),
-                        true);
-                moduleLocations.put(name, l);
-                pathLocations.put(out.toAbsolutePath(), l);
-           }
+                l = new ModuleLocationHandler(this, location.getName() + "[" + name + "]",
+                        name, Collections.singletonList(out), true);
+                moduleTable.add(l);
+            }
             return l;
         }
 
         @Override
+        void setPathsForModule(String name, Iterable<? extends Path> paths) throws IOException {
+            Path out = checkSingletonDirectory(paths);
+            if (moduleTable == null) {
+                moduleTable = new ModuleTable();
+            }
+            ModuleLocationHandler l = moduleTable.get(name);
+            if (l == null) {
+                l = new ModuleLocationHandler(this, location.getName() + "[" + name + "]",
+                        name, Collections.singletonList(out), true);
+                moduleTable.add(l);
+           } else {
+                l.searchPath = Collections.singletonList(out);
+                moduleTable.updatePaths(l);
+            }
+        }
+
+        @Override
         Location getLocationForModule(Path dir) {
-            return (pathLocations == null) ? null : pathLocations.get(dir);
+            return (moduleTable == null) ? null : moduleTable.get(dir);
         }
 
         private boolean listed;
@@ -569,11 +604,11 @@
                 }
                 listed = true;
             }
-            if (moduleLocations == null)
+
+            if (moduleTable == null || moduleTable.isEmpty())
                 return Collections.emptySet();
-            Set<Location> locns = new LinkedHashSet<>();
-            moduleLocations.forEach((k, v) -> locns.add(v));
-            return Collections.singleton(locns);
+
+            return Collections.singleton(moduleTable.locations());
         }
     }
 
@@ -860,14 +895,16 @@
      * The Location can be specified to accept overriding classes from the
      * {@code --patch-module <module>=<path> } parameter.
      */
-    private class ModuleLocationHandler extends LocationHandler implements Location {
-        protected final String name;
-        protected final String moduleName;
-        protected final Collection<Path> searchPath;
-        protected final boolean output;
+    private static class ModuleLocationHandler extends LocationHandler implements Location {
+        private final LocationHandler parent;
+        private final String name;
+        private final String moduleName;
+        private final boolean output;
+        Collection<Path> searchPath;
 
-        ModuleLocationHandler(String name, String moduleName, Collection<Path> searchPath,
-                boolean output) {
+        ModuleLocationHandler(LocationHandler parent, String name, String moduleName,
+                Collection<Path> searchPath, boolean output) {
+            this.parent = parent;
             this.name = name;
             this.moduleName = moduleName;
             this.searchPath = searchPath;
@@ -891,21 +928,79 @@
 
         @Override // defined by LocationHandler
         Collection<Path> getPaths() {
-            // For now, we always return searchPathWithOverrides. This may differ from the
-            // JVM behavior if there is a module-info.class to be found in the overriding
-            // classes.
-            return searchPath;
+            return Collections.unmodifiableCollection(searchPath);
         }
 
         @Override // defined by LocationHandler
-        void setPaths(Iterable<? extends Path> files) throws IOException {
-            throw new UnsupportedOperationException();
+        void setPaths(Iterable<? extends Path> paths) throws IOException {
+            // defer to the parent to determine if this is acceptable
+            parent.setPathsForModule(moduleName, paths);
+        }
+
+        @Override // defined by LocationHandler
+        void setPathsForModule(String moduleName, Iterable<? extends Path> paths) {
+            throw new UnsupportedOperationException("not supported for " + name);
         }
 
         @Override // defined by LocationHandler
         String inferModuleName() {
             return moduleName;
         }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+    }
+
+    /**
+     * A table of module location handlers, indexed by name and path.
+     */
+    private static class ModuleTable {
+        private final Map<String, ModuleLocationHandler> nameMap = new LinkedHashMap<>();
+        private final Map<Path, ModuleLocationHandler> pathMap = new LinkedHashMap<>();
+
+        void add(ModuleLocationHandler h) {
+            nameMap.put(h.moduleName, h);
+            for (Path p : h.searchPath) {
+                pathMap.put(p.toAbsolutePath().normalize(), h);
+            }
+        }
+
+        void updatePaths(ModuleLocationHandler h) {
+            // use iterator, to be able to remove old entries
+            for (Iterator<Map.Entry<Path, ModuleLocationHandler>> iter = pathMap.entrySet().iterator();
+                    iter.hasNext(); ) {
+                Map.Entry<Path, ModuleLocationHandler> e = iter.next();
+                if (e.getValue() == h) {
+                    iter.remove();
+                }
+            }
+            for (Path p : h.searchPath) {
+                pathMap.put(p.toAbsolutePath().normalize(), h);
+            }
+        }
+
+        ModuleLocationHandler get(String name) {
+            return nameMap.get(name);
+        }
+
+        ModuleLocationHandler get(Path path) {
+            return pathMap.get(path);
+        }
+
+        void clear() {
+            nameMap.clear();
+            pathMap.clear();
+        }
+
+        boolean isEmpty() {
+            return nameMap.isEmpty();
+        }
+
+        Set<Location> locations() {
+            return Collections.unmodifiableSet(nameMap.values().stream().collect(Collectors.toSet()));
+        }
     }
 
     /**
@@ -913,7 +1008,7 @@
      * like UPGRADE_MODULE_PATH and MODULE_PATH.
      */
     private class ModulePathLocationHandler extends SimpleLocationHandler {
-        private Map<String, ModuleLocationHandler> pathModules;
+        private ModuleTable moduleTable;
 
         ModulePathLocationHandler(Location location, Option... options) {
             super(location, options);
@@ -930,8 +1025,8 @@
 
         @Override
         public Location getLocationForModule(String moduleName) {
-            initPathModules();
-            return pathModules.get(moduleName);
+            initModuleLocations();
+            return moduleTable.get(moduleName);
         }
 
         @Override
@@ -950,20 +1045,49 @@
                 }
             }
             super.setPaths(paths);
+            moduleTable = null;
         }
 
-        private void initPathModules() {
-            if (pathModules != null) {
+        @Override
+        void setPathsForModule(String name, Iterable<? extends Path> paths) throws IOException {
+            List<Path> checkedPaths = checkPaths(paths);
+            // how far should we go to validate the paths provide a module?
+            // e.g. contain module-info with the correct name?
+            initModuleLocations();
+            ModuleLocationHandler l = moduleTable.get(name);
+            if (l == null) {
+                l = new ModuleLocationHandler(this, location.getName() + "[" + name + "]",
+                        name, checkedPaths, true);
+                moduleTable.add(l);
+           } else {
+                l.searchPath = checkedPaths;
+                moduleTable.updatePaths(l);
+            }
+        }
+
+        private List<Path> checkPaths(Iterable<? extends Path> paths) throws IOException {
+            Objects.requireNonNull(paths);
+            List<Path> validPaths = new ArrayList<>();
+            for (Path p : paths) {
+                validPaths.add(checkDirectory(p));
+            }
+            return validPaths;
+        }
+
+        private void initModuleLocations() {
+            if (moduleTable != null) {
                 return;
             }
 
-            pathModules = new LinkedHashMap<>();
+            moduleTable = new ModuleTable();
 
             for (Set<Location> set : listLocationsForModules()) {
                 for (Location locn : set) {
                     if (locn instanceof ModuleLocationHandler) {
-                        ModuleLocationHandler h = (ModuleLocationHandler) locn;
-                        pathModules.put(h.moduleName, h);
+                        ModuleLocationHandler l = (ModuleLocationHandler) locn;
+                        if (!moduleTable.nameMap.containsKey(l.moduleName)) {
+                            moduleTable.add(l);
+                        }
                     }
                 }
             }
@@ -1052,8 +1176,9 @@
                         String moduleName = readModuleName(moduleInfoClass);
                         String name = location.getName()
                                 + "[" + pathIndex + ":" + moduleName + "]";
-                        ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
-                                Collections.singleton(path), false);
+                        ModuleLocationHandler l = new ModuleLocationHandler(
+                                ModulePathLocationHandler.this, name, moduleName,
+                                Collections.singletonList(path), false);
                         return Collections.singleton(l);
                     } catch (ModuleNameReader.BadClassFile e) {
                         log.error(Errors.LocnBadModuleInfo(path));
@@ -1077,8 +1202,9 @@
                     Path modulePath = module.snd;
                     String name = location.getName()
                             + "[" + pathIndex + "." + (index++) + ":" + moduleName + "]";
-                    ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
-                            Collections.singleton(modulePath), false);
+                    ModuleLocationHandler l = new ModuleLocationHandler(
+                            ModulePathLocationHandler.this, name, moduleName,
+                            Collections.singletonList(modulePath), false);
                     result.add(l);
                 }
                 return result;
@@ -1094,8 +1220,9 @@
                 Path modulePath = module.snd;
                 String name = location.getName()
                         + "[" + pathIndex + ":" + moduleName + "]";
-                ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
-                        Collections.singleton(modulePath), false);
+                ModuleLocationHandler l = new ModuleLocationHandler(
+                        ModulePathLocationHandler.this, name, moduleName,
+                        Collections.singletonList(modulePath), false);
                 return Collections.singleton(l);
             }
 
@@ -1212,9 +1339,7 @@
     }
 
     private class ModuleSourcePathLocationHandler extends BasicLocationHandler {
-
-        private Map<String, Location> moduleLocations;
-        private Map<Path, Location> pathLocations;
+        private ModuleTable moduleTable;
 
         ModuleSourcePathLocationHandler() {
             super(StandardLocation.MODULE_SOURCE_PATH,
@@ -1233,7 +1358,7 @@
                 expandBraces(s, segments);
             }
 
-            Map<String, Collection<Path>> map = new LinkedHashMap<>();
+            Map<String, List<Path>> map = new LinkedHashMap<>();
             final String MARKER = "*";
             for (String seg: segments) {
                 int markStart = seg.indexOf(MARKER);
@@ -1258,13 +1383,12 @@
                 }
             }
 
-            moduleLocations = new LinkedHashMap<>();
-            pathLocations = new LinkedHashMap<>();
-            map.forEach((k, v) -> {
-                String name = location.getName() + "[" + k + "]";
-                ModuleLocationHandler h = new ModuleLocationHandler(name, k, v, false);
-                moduleLocations.put(k, h);
-                v.forEach(p -> pathLocations.put(normalize(p), h));
+            moduleTable = new ModuleTable();
+            map.forEach((modName, modPath) -> {
+                String locnName = location.getName() + "[" + modName + "]";
+                ModuleLocationHandler l = new ModuleLocationHandler(this, locnName, modName,
+                        modPath, false);
+                moduleTable.add(l);
             });
         }
 
@@ -1273,7 +1397,7 @@
             return (ch == File.separatorChar) || (ch == '/');
         }
 
-        void add(Map<String, Collection<Path>> map, Path prefix, Path suffix) {
+        void add(Map<String, List<Path>> map, Path prefix, Path suffix) {
             if (!Files.isDirectory(prefix)) {
                 if (warn) {
                     String key = Files.exists(prefix)
@@ -1288,7 +1412,7 @@
                     Path path = (suffix == null) ? entry : entry.resolve(suffix);
                     if (Files.isDirectory(path)) {
                         String name = entry.getFileName().toString();
-                        Collection<Path> paths = map.get(name);
+                        List<Path> paths = map.get(name);
                         if (paths == null)
                             map.put(name, paths = new ArrayList<>());
                         paths.add(path);
@@ -1364,7 +1488,7 @@
 
         @Override
         boolean isSet() {
-            return (moduleLocations != null);
+            return (moduleTable != null);
         }
 
         @Override
@@ -1378,22 +1502,51 @@
         }
 
         @Override
+        void setPathsForModule(String name, Iterable<? extends Path> paths) throws IOException {
+            List<Path> validPaths = checkPaths(paths);
+
+            if (moduleTable == null)
+                moduleTable = new ModuleTable();
+
+            ModuleLocationHandler l = moduleTable.get(name);
+            if (l == null) {
+                l = new ModuleLocationHandler(this,
+                        location.getName() + "[" + name + "]",
+                        name,
+                        validPaths,
+                        true);
+                moduleTable.add(l);
+           } else {
+                l.searchPath = validPaths;
+                moduleTable.updatePaths(l);
+            }
+        }
+
+        private List<Path> checkPaths(Iterable<? extends Path> paths) throws IOException {
+            Objects.requireNonNull(paths);
+            List<Path> validPaths = new ArrayList<>();
+            for (Path p : paths) {
+                validPaths.add(checkDirectory(p));
+            }
+            return validPaths;
+        }
+
+        @Override
         Location getLocationForModule(String name) {
-            return (moduleLocations == null) ? null : moduleLocations.get(name);
+            return (moduleTable == null) ? null : moduleTable.get(name);
         }
 
         @Override
         Location getLocationForModule(Path dir) {
-            return (pathLocations == null) ? null : pathLocations.get(dir);
+            return (moduleTable == null) ? null : moduleTable.get(dir);
         }
 
         @Override
         Iterable<Set<Location>> listLocationsForModules() {
-            if (moduleLocations == null)
+            if (moduleTable == null)
                 return Collections.emptySet();
-            Set<Location> locns = new LinkedHashSet<>();
-            moduleLocations.forEach((k, v) -> locns.add(v));
-            return Collections.singleton(locns);
+
+            return Collections.singleton(moduleTable.locations());
         }
 
     }
@@ -1401,8 +1554,7 @@
     private class SystemModulesLocationHandler extends BasicLocationHandler {
         private Path systemJavaHome;
         private Path modules;
-        private Map<String, ModuleLocationHandler> systemModules;
-        private Map<Path, Location> pathLocations;
+        private ModuleTable moduleTable;
 
         SystemModulesLocationHandler() {
             super(StandardLocation.SYSTEM_MODULES, Option.SYSTEM);
@@ -1437,23 +1589,38 @@
             if (files == null) {
                 systemJavaHome = null;
             } else {
-                Iterator<? extends Path> pathIter = files.iterator();
-                if (!pathIter.hasNext()) {
-                    throw new IllegalArgumentException("empty path for directory"); // TODO: FIXME
-                }
-                Path dir = pathIter.next();
-                if (pathIter.hasNext()) {
-                    throw new IllegalArgumentException("path too long for directory"); // TODO: FIXME
-                }
-                if (!Files.exists(dir)) {
-                    throw new FileNotFoundException(dir + ": does not exist");
-                } else if (!Files.isDirectory(dir)) {
-                    throw new IOException(dir + ": not a directory");
-                }
+                Path dir = checkSingletonDirectory(files);
                 update(dir);
             }
         }
 
+        @Override
+        void setPathsForModule(String name, Iterable<? extends Path> paths) throws IOException {
+            List<Path> checkedPaths = checkPaths(paths);
+            initSystemModules();
+            ModuleLocationHandler l = moduleTable.get(name);
+            if (l == null) {
+                l = new ModuleLocationHandler(this,
+                        location.getName() + "[" + name + "]",
+                        name,
+                        checkedPaths,
+                        true);
+                moduleTable.add(l);
+           } else {
+                l.searchPath = checkedPaths;
+                moduleTable.updatePaths(l);
+            }
+        }
+
+        private List<Path> checkPaths(Iterable<? extends Path> paths) throws IOException {
+            Objects.requireNonNull(paths);
+            List<Path> validPaths = new ArrayList<>();
+            for (Path p : paths) {
+                validPaths.add(checkDirectory(p));
+            }
+            return validPaths;
+        }
+
         private void update(Path p) {
             if (!isCurrentPlatform(p) && !Files.exists(p.resolve("lib").resolve("jrt-fs.jar")) &&
                     !Files.exists(systemJavaHome.resolve("modules")))
@@ -1473,31 +1640,27 @@
         @Override
         Location getLocationForModule(String name) throws IOException {
             initSystemModules();
-            return systemModules.get(name);
+            return moduleTable.get(name);
         }
 
         @Override
         Location getLocationForModule(Path dir) throws IOException {
             initSystemModules();
-            return (pathLocations == null) ? null : pathLocations.get(dir);
+            return moduleTable.get(dir);
         }
 
         @Override
         Iterable<Set<Location>> listLocationsForModules() throws IOException {
             initSystemModules();
-            Set<Location> locns = new LinkedHashSet<>();
-            for (Location l: systemModules.values())
-                locns.add(l);
-            return Collections.singleton(locns);
+            return Collections.singleton(moduleTable.locations());
         }
 
         private void initSystemModules() throws IOException {
-            if (systemModules != null) {
+            if (moduleTable != null)
                 return;
-            }
 
             if (systemJavaHome == null) {
-                systemModules = Collections.emptyMap();
+                moduleTable = new ModuleTable();
                 return;
             }
 
@@ -1535,24 +1698,21 @@
                 }
             }
 
-            systemModules = new LinkedHashMap<>();
-            pathLocations = new LinkedHashMap<>();
+            moduleTable = new ModuleTable();
             try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules, Files::isDirectory)) {
                 for (Path entry : stream) {
                     String moduleName = entry.getFileName().toString();
                     String name = location.getName() + "[" + moduleName + "]";
-                    ModuleLocationHandler h = new ModuleLocationHandler(name, moduleName,
-                            Collections.singleton(entry), false);
-                    systemModules.put(moduleName, h);
-                    pathLocations.put(normalize(entry), h);
+                    ModuleLocationHandler h = new ModuleLocationHandler(this,
+                            name, moduleName, Collections.singletonList(entry), false);
+                    moduleTable.add(h);
                 }
             }
         }
     }
 
     private class PatchModulesLocationHandler extends BasicLocationHandler {
-        private final Map<String, ModuleLocationHandler> moduleLocations = new HashMap<>();
-        private final Map<Path, Location> pathLocations = new HashMap<>();
+        private final ModuleTable moduleTable = new ModuleTable();
 
         PatchModulesLocationHandler() {
             super(StandardLocation.PATCH_MODULE_PATH, Option.PATCH_MODULE);
@@ -1576,11 +1736,9 @@
                     SearchPath mPatchPath = new SearchPath()
                             .addFiles(v.substring(eq + 1));
                     String name = location.getName() + "[" + moduleName + "]";
-                    ModuleLocationHandler h = new ModuleLocationHandler(name, moduleName, mPatchPath, false);
-                    moduleLocations.put(moduleName, h);
-                    for (Path r : mPatchPath) {
-                        pathLocations.put(normalize(r), h);
-                    }
+                    ModuleLocationHandler h = new ModuleLocationHandler(this, name,
+                            moduleName, mPatchPath, false);
+                    moduleTable.add(h);
                 } else {
                     // Should not be able to get here;
                     // this should be caught and handled in Option.PATCH_MODULE
@@ -1593,7 +1751,7 @@
 
         @Override
         boolean isSet() {
-            return !moduleLocations.isEmpty();
+            return !moduleTable.isEmpty();
         }
 
         @Override
@@ -1606,24 +1764,25 @@
             throw new UnsupportedOperationException();
         }
 
+        @Override // defined by LocationHandler
+        void setPathsForModule(String moduleName, Iterable<? extends Path> files) throws IOException {
+            throw new UnsupportedOperationException(); // not yet
+        }
+
         @Override
         Location getLocationForModule(String name) throws IOException {
-            return moduleLocations.get(name);
+            return moduleTable.get(name);
         }
 
         @Override
         Location getLocationForModule(Path dir) throws IOException {
-            return (pathLocations == null) ? null : pathLocations.get(dir);
+            return moduleTable.get(dir);
         }
 
         @Override
         Iterable<Set<Location>> listLocationsForModules() throws IOException {
-            Set<Location> locns = new LinkedHashSet<>();
-            for (Location l: moduleLocations.values())
-                locns.add(l);
-            return Collections.singleton(locns);
+            return Collections.singleton(moduleTable.locations());
         }
-
     }
 
     Map<Location, LocationHandler> handlersForLocation;
@@ -1644,7 +1803,6 @@
             new OutputLocationHandler(StandardLocation.NATIVE_HEADER_OUTPUT, Option.H),
             new ModuleSourcePathLocationHandler(),
             new PatchModulesLocationHandler(),
-            // TODO: should UPGRADE_MODULE_PATH be merged with SYSTEM_MODULES?
             new ModulePathLocationHandler(StandardLocation.UPGRADE_MODULE_PATH, Option.UPGRADE_MODULE_PATH),
             new ModulePathLocationHandler(StandardLocation.MODULE_PATH, Option.MODULE_PATH),
             new SystemModulesLocationHandler(),
@@ -1704,6 +1862,20 @@
         return (h == null ? null : h.getLocationForModule(dir));
     }
 
+    void setLocationForModule(Location location, String moduleName,
+            Iterable<? extends Path> files) throws IOException {
+        LocationHandler h = getHandler(location);
+        if (h == null) {
+            if (location.isOutputLocation()) {
+                h = new OutputLocationHandler(location);
+            } else {
+                h = new ModulePathLocationHandler(location);
+            }
+            handlersForLocation.put(location, h);
+        }
+        h.setPathsForModule(moduleName, files);
+    }
+
     String inferModuleName(Location location) {
         LocationHandler h = getHandler(location);
         return (h == null ? null : h.inferModuleName());
@@ -1737,5 +1909,4 @@
             return p.toAbsolutePath().normalize();
         }
     }
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/file/SetLocationForModule.java	Fri Feb 24 15:23:14 2017 -0800
@@ -0,0 +1,328 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8173914
+ * @summary JavaFileManager.setLocationForModule
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ *          jdk.compiler/com.sun.tools.javac.main
+ * @library /tools/lib
+ * @build toolbox.JavacTask toolbox.TestRunner toolbox.ToolBox SetLocationForModule
+ * @run main SetLocationForModule
+ */
+
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileManager.Location;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+
+import toolbox.JavacTask;
+import toolbox.TestRunner;
+import toolbox.TestRunner.Test;
+import toolbox.ToolBox;
+
+public class SetLocationForModule extends TestRunner {
+
+    public static void main(String... args) throws Exception {
+        new SetLocationForModule().runTests(m -> new Object[] { Paths.get(m.getName()) });
+    }
+
+    public SetLocationForModule() {
+        super(System.err);
+    }
+
+    private final JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
+    private final ToolBox tb = new ToolBox();
+
+    @Test
+    public void testBasic(Path base) throws IOException {
+        try (StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null)) {
+            Location[] locns = {
+                StandardLocation.SOURCE_PATH,
+                StandardLocation.CLASS_PATH,
+                StandardLocation.PLATFORM_CLASS_PATH,
+            };
+            // set a value
+            Path out = Files.createDirectories(base.resolve("out"));
+            for (Location locn : locns) {
+                checkException("unsupported for location",
+                        IllegalArgumentException.class,
+                        "location is not an output location or a module-oriented location: " + locn,
+                        () -> fm.setLocationForModule(locn, "m", List.of(out)));
+            }
+        }
+    }
+
+    @Test
+    public void testModulePath(Path base) throws IOException {
+        try (StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null)) {
+            Path src = base.resolve("src");
+            Path src_m = src.resolve("m");
+            tb.writeJavaFiles(src_m, "module m { }");
+
+            Location locn = StandardLocation.MODULE_PATH;
+
+            Path modules1 = Files.createDirectories(base.resolve("modules1"));
+            new JavacTask(tb)
+                    .outdir(modules1)
+                    .options("--module-source-path", src.toString())
+                    .files(tb.findJavaFiles(src))
+                    .run();
+            fm.setLocationFromPaths(locn, List.of(modules1));
+
+            Location m = fm.getLocationForModule(locn, "m");
+            checkEqual("default setting",
+                    fm.getLocationAsPaths(m), modules1.resolve("m"));
+
+            Path override1 = Files.createDirectories(base.resolve("override1"));
+            fm.setLocationForModule(locn, "m", List.of(override1));
+            checkEqual("override setting 1",
+                    fm.getLocationAsPaths(m), override1);
+
+            Path override2 = Files.createDirectories(base.resolve("override2"));
+            fm.setLocationFromPaths(m, List.of(override2));
+            checkEqual("override setting 2",
+                    fm.getLocationAsPaths(m), override2);
+
+            Path modules2 = Files.createDirectories(base.resolve("modules2"));
+            fm.setLocationFromPaths(locn, List.of(modules2));
+
+            checkEqual("updated setting",
+                    fm.getLocationAsPaths(m), modules2.resolve("m"));
+        }
+    }
+
+    @Test
+    public void testModuleSourcePath(Path base) throws IOException {
+        try (StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null)) {
+
+            Location locn = StandardLocation.MODULE_SOURCE_PATH;
+
+            Path src1 = Files.createDirectories(base.resolve("src1"));
+            Path src1_m = src1.resolve("m");
+            tb.writeJavaFiles(src1_m, "module m { }");
+//            fm.setLocationFromPaths(locn, List.of(src1));
+            fm.handleOption("--module-source-path", List.of(src1.toString()).iterator());
+
+            Location m = fm.getLocationForModule(locn, "m");
+            checkEqual("default setting",
+                    fm.getLocationAsPaths(m), src1.resolve("m"));
+
+            Path override1 = Files.createDirectories(base.resolve("override1"));
+            tb.writeJavaFiles(override1, "module m { }");
+            fm.setLocationForModule(locn, "m", List.of(override1));
+            checkEqual("override setting 1",
+                    fm.getLocationAsPaths(m), override1);
+
+            Path override2 = Files.createDirectories(base.resolve("override2"));
+            tb.writeJavaFiles(override2, "module m { }");
+            fm.setLocationFromPaths(m, List.of(override2));
+            checkEqual("override setting 2",
+                    fm.getLocationAsPaths(m), override2);
+
+            Path src2 = Files.createDirectories(base.resolve("src2"));
+            Path src2_m = src2.resolve("m");
+            tb.writeJavaFiles(src2_m, "module m { }");
+//            fm.setLocationFromPaths(locn, List.of(src2));
+            fm.handleOption("--module-source-path", List.of(src2.toString()).iterator());
+
+            checkEqual("updated setting",
+                    fm.getLocationAsPaths(m), src2.resolve("m"));
+        }
+    }
+
+    @Test
+    public void testOutput(Path base) throws IOException {
+        try (StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null)) {
+            Location locn = StandardLocation.CLASS_OUTPUT;
+
+            Path out1 = Files.createDirectories(base.resolve("out1"));
+            fm.setLocationFromPaths(locn, List.of(out1));
+
+            Location m = fm.getLocationForModule(locn, "m");
+            checkEqual("default setting",
+                    fm.getLocationAsPaths(m), out1.resolve("m"));
+
+            Path override1 = Files.createDirectories(base.resolve("override1"));
+            fm.setLocationForModule(locn, "m", List.of(override1));
+            checkEqual("override setting 1",
+                    fm.getLocationAsPaths(m), override1);
+
+            Path override2 = Files.createDirectories(base.resolve("override2"));
+            fm.setLocationFromPaths(m, List.of(override2));
+            checkEqual("override setting 2",
+                    fm.getLocationAsPaths(m), override2);
+
+            Path out2 = Files.createDirectories(base.resolve("out2"));
+            fm.setLocationFromPaths(locn, List.of(out2));
+
+            checkEqual("updated setting",
+                    fm.getLocationAsPaths(m), out2.resolve("m"));
+        }
+    }
+
+    @Test
+    public void testOutput_invalid(Path base) throws IOException {
+        try (StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null)) {
+            Location locn = StandardLocation.CLASS_OUTPUT;
+            // set a top default
+            Path out1 = Files.createDirectories(base.resolve("out1"));
+            fm.setLocationFromPaths(locn, List.of(out1));
+            // getLocnForModule
+            Location m = fm.getLocationForModule(locn, "m");
+            checkEqual("default setting",
+                    fm.getLocationAsPaths(m), out1.resolve("m"));
+
+            checkException("empty arg list",
+                    IllegalArgumentException.class, "empty path for directory",
+                    () -> fm.setLocationFromPaths(m, Collections.emptyList()));
+
+            Path out2 = Files.createDirectories(base.resolve("out2"));
+            checkException("empty arg list",
+                    IllegalArgumentException.class, "path too long for directory",
+                    () -> fm.setLocationFromPaths(m, List.of(out2, out2)));
+
+            Path notExist = base.resolve("notExist");
+            checkException("not exist",
+                    FileNotFoundException.class, notExist + ": does not exist",
+                    () -> fm.setLocationFromPaths(m, List.of(notExist)));
+
+            Path file = Files.createFile(base.resolve("file.txt"));
+            checkException("not exist",
+                    IOException.class, file + ": not a directory",
+                    () -> fm.setLocationFromPaths(m, List.of(file)));
+        }
+    }
+
+    @Test
+    public void testOutput_nested(Path base) throws IOException {
+        try (StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null)) {
+            Location locn = StandardLocation.CLASS_OUTPUT;
+
+            Path out1 = Files.createDirectories(base.resolve("out1"));
+            fm.setLocationForModule(locn, "m", List.of(out1));
+
+            Location m = fm.getLocationForModule(locn, "m");
+            checkEqual("initial setting",
+                    fm.getLocationAsPaths(m), out1);
+
+            Path out2 = Files.createDirectories(base.resolve("out2"));
+            checkException("create nested module",
+                    UnsupportedOperationException.class, "not supported for CLASS_OUTPUT[m]",
+                    () -> fm.setLocationForModule(m, "x", List.of(out2)));
+        }
+    }
+
+    @Test
+    public void testSystemModules(Path base) throws IOException {
+        try (StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null)) {
+            Location locn = StandardLocation.SYSTEM_MODULES;
+
+            Location javaCompiler = fm.getLocationForModule(locn, "java.compiler");
+            // cannot easily verify default setting: could be jrt: or exploded image
+
+            Path override1 = Files.createDirectories(base.resolve("override1"));
+            fm.setLocationForModule(locn, "java.compiler", List.of(override1));
+            checkEqual("override setting 1",
+                    fm.getLocationAsPaths(javaCompiler), override1);
+
+            Path override2 = Files.createDirectories(base.resolve("override2"));
+            fm.setLocationFromPaths(javaCompiler, List.of(override2));
+            checkEqual("override setting 2",
+                    fm.getLocationAsPaths(javaCompiler), override2);
+        }
+    }
+
+    @Test
+    public void testTemplate(Path base) {
+        // set a top default
+        // getLocnForModule
+        // set a value
+        // getLocnForModule
+        // reset
+        // getLocationForModule
+    }
+
+    interface RunnableWithException {
+        public void run() throws Exception;
+    }
+
+    void checkException(String message,
+            Class<? extends Throwable> expectedException, String expectedMessage,
+            RunnableWithException r) {
+        try {
+            r.run();
+            error(message + ": expected exception not thrown: " + expectedException);
+        } catch (Exception | Error t) {
+            if (expectedException.isAssignableFrom(t.getClass())) {
+                checkEqual("exception message",
+                        t.getMessage(), expectedMessage);
+
+            } else {
+                error(message + ": unexpected exception\n"
+                        + "expect: " + expectedException + "\n"
+                        + " found: " + t);
+            }
+        }
+    }
+
+    void checkEqual(String message, Iterable<? extends Path> found, Path... expect) {
+        List<Path> fList = asList(found);
+        List<Path> eList = List.of(expect);
+        if (!Objects.equals(fList, fList)) {
+            error(message + ": lists not equal\n"
+                    + "expect: " + eList + "\n"
+                    + " found: " + fList);
+        }
+    }
+
+    void checkEqual(String message, String found, String expect) {
+        if (!Objects.equals(found, expect)) {
+            error(message + ": strings not equal\n"
+                    + "expect: " + expect + "\n"
+                    + " found: " + found);
+        }
+    }
+
+    List<Path> asList(Iterable<? extends Path> a) {
+        List<Path> list = new ArrayList<>();
+        for (Path p : a) {
+            list.add(p);
+        }
+        return list;
+    }
+}
+