langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java
changeset 36526 3b41f1c69604
parent 34752 9c262a013456
child 37848 3c8ff4204d2d
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java	Tue Mar 15 13:48:30 2016 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java	Thu Mar 17 19:04:28 2016 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 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
@@ -25,15 +25,23 @@
 
 package com.sun.tools.javac.file;
 
+import java.io.Closeable;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.UncheckedIOException;
-import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.DirectoryIteratorException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.ProviderNotFoundException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -43,15 +51,20 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.NoSuchElementException;
 import java.util.Set;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import java.util.zip.ZipFile;
 
+import javax.lang.model.SourceVersion;
 import javax.tools.JavaFileManager;
 import javax.tools.JavaFileManager.Location;
 import javax.tools.StandardJavaFileManager;
@@ -59,13 +72,14 @@
 
 import com.sun.tools.javac.code.Lint;
 import com.sun.tools.javac.main.Option;
+import com.sun.tools.javac.resources.CompilerProperties.Errors;
+import com.sun.tools.javac.resources.CompilerProperties.Warnings;
 import com.sun.tools.javac.util.ListBuffer;
 import com.sun.tools.javac.util.Log;
+import com.sun.tools.javac.util.Pair;
 import com.sun.tools.javac.util.StringUtils;
 
-import static javax.tools.StandardLocation.CLASS_PATH;
 import static javax.tools.StandardLocation.PLATFORM_CLASS_PATH;
-import static javax.tools.StandardLocation.SOURCE_PATH;
 
 import static com.sun.tools.javac.main.Option.BOOTCLASSPATH;
 import static com.sun.tools.javac.main.Option.DJAVA_ENDORSED_DIRS;
@@ -103,16 +117,35 @@
      */
     private boolean warn;
 
-    // Used by Locations(for now) to indicate that the PLATFORM_CLASS_PATH
-    // should use the jrt: file system.
-    // When Locations has been converted to use java.nio.file.Path,
-    // Locations can use Paths.get(URI.create("jrt:"))
-    static final Path JRT_MARKER_FILE = Paths.get("JRT_MARKER_FILE");
+    private ModuleNameReader moduleNameReader;
+
+    static final Path javaHome = Paths.get(System.getProperty("java.home"));
+    static final Path thisSystemModules = javaHome.resolve("lib").resolve("modules");
+
+    Map<Path, FileSystem> fileSystems = new LinkedHashMap<>();
+    List<Closeable> closeables = new ArrayList<>();
 
     Locations() {
         initHandlers();
     }
 
+    public void close() throws IOException {
+        ListBuffer<IOException> list = new ListBuffer<>();
+        closeables.forEach(closeable -> {
+            try {
+                closeable.close();
+            } catch (IOException ex) {
+                list.add(ex);
+            }
+        });
+        if (list.nonEmpty()) {
+            IOException ex = new IOException();
+            for (IOException e: list)
+                ex.addSuppressed(e);
+            throw ex;
+        }
+    }
+
     // could replace Lint by "boolean warn"
     void update(Log log, Lint lint, FSInfo fsInfo) {
         this.log = log;
@@ -269,10 +302,13 @@
 
             if (fsInfo.isFile(file)) {
                 /* File is an ordinary file. */
-                if (!isArchive(file) && !file.getFileName().toString().endsWith(".jimage")) {
+                if (!isArchive(file)
+                        && !file.getFileName().toString().endsWith(".jmod")
+                        && !file.endsWith("modules")) {
                     /* Not a recognized extension; open it to see if
                      it looks like a valid zip file. */
                     try {
+                        // TODO: use of ZipFile should be updated
                         ZipFile z = new ZipFile(file.toFile());
                         z.close();
                         if (warn) {
@@ -295,7 +331,7 @@
             super.add(file);
             canonicalValues.add(canonFile);
 
-            if (expandJarClassPaths && fsInfo.isFile(file) && !file.getFileName().toString().endsWith(".jimage")) {
+            if (expandJarClassPaths && fsInfo.isFile(file) && !file.endsWith("modules")) {
                 addJarClassPath(file, warn);
             }
         }
@@ -316,15 +352,74 @@
     }
 
     /**
-     * Base class for handling support for the representation of Locations. Implementations are
-     * responsible for handling the interactions between the command line options for a location,
-     * and API access via setLocation.
+     * Base class for handling support for the representation of Locations.
+     *
+     * Locations are (by design) opaque handles that can easily be implemented
+     * by enums like StandardLocation. Within JavacFileManager, each Location
+     * has an associated LocationHandler, which provides much of the appropriate
+     * functionality for the corresponding Location.
      *
      * @see #initHandlers
      * @see #getHandler
      */
     protected abstract class LocationHandler {
 
+        /**
+         * @see JavaFileManager#handleOption
+         */
+        abstract boolean handleOption(Option option, String value);
+
+        /**
+         * @see StandardJavaFileManager#hasLocation
+         */
+        boolean isSet() {
+            return (getPaths() != null);
+        }
+
+        /**
+         * @see StandardJavaFileManager#getLocation
+         */
+        abstract Collection<Path> getPaths();
+
+        /**
+         * @see StandardJavaFileManager#setLocation
+         */
+        abstract void setPaths(Iterable<? extends Path> files) throws IOException;
+
+        /**
+         * @see JavaFileManager#getModuleLocation(Location, String)
+         */
+        Location getModuleLocation(String moduleName) throws IOException {
+            return null;
+        }
+
+        /**
+         * @see JavaFileManager#getModuleLocation(Location, JavaFileObject, String)
+         */
+        Location getModuleLocation(Path dir) {
+            return null;
+        }
+
+        /**
+         * @see JavaFileManager#inferModuleName
+         */
+        String inferModuleName() {
+            return null;
+        }
+
+        /**
+         * @see JavaFileManager#listModuleLocations
+         */
+        Iterable<Set<Location>> listModuleLocations() throws IOException {
+            return null;
+        }
+    }
+
+    /**
+     * A LocationHandler for a given Location, and associated set of options.
+     */
+    private abstract class BasicLocationHandler extends LocationHandler {
+
         final Location location;
         final Set<Option> options;
 
@@ -336,37 +431,23 @@
          * @param options the options affecting this location
          * @see #initHandlers
          */
-        protected LocationHandler(Location location, Option... options) {
+        protected BasicLocationHandler(Location location, Option... options) {
             this.location = location;
             this.options = options.length == 0
                     ? EnumSet.noneOf(Option.class)
                     : EnumSet.copyOf(Arrays.asList(options));
         }
-
-        /**
-         * @see JavaFileManager#handleOption
-         */
-        abstract boolean handleOption(Option option, String value);
-
-        /**
-         * @see StandardJavaFileManager#getLocation
-         */
-        abstract Collection<Path> getLocation();
-
-        /**
-         * @see StandardJavaFileManager#setLocation
-         */
-        abstract void setLocation(Iterable<? extends Path> files) throws IOException;
     }
 
     /**
      * General purpose implementation for output locations, such as -d/CLASS_OUTPUT and
-     * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.) The value is a single
-     * file, possibly null.
+     * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.)
+     * The value is a single file, possibly null.
      */
-    private class OutputLocationHandler extends LocationHandler {
+    private class OutputLocationHandler extends BasicLocationHandler {
 
         private Path outputDir;
+        private Map<String, Location> moduleLocations;
 
         OutputLocationHandler(Location location, Option... options) {
             super(location, options);
@@ -387,12 +468,12 @@
         }
 
         @Override
-        Collection<Path> getLocation() {
+        Collection<Path> getPaths() {
             return (outputDir == null) ? null : Collections.singleton(outputDir);
         }
 
         @Override
-        void setLocation(Iterable<? extends Path> files) throws IOException {
+        void setPaths(Iterable<? extends Path> files) throws IOException {
             if (files == null) {
                 outputDir = null;
             } else {
@@ -411,15 +492,32 @@
                 }
                 outputDir = dir;
             }
+            moduleLocations = null;
+        }
+
+        @Override
+        Location getModuleLocation(String name) {
+            if (moduleLocations == null)
+                moduleLocations = new HashMap<>();
+            Location l = moduleLocations.get(name);
+            if (l == null) {
+                l = new ModuleLocationHandler(location.getName() + "[" + name + "]",
+                        name,
+                        Collections.singleton(outputDir.resolve(name)),
+                        true, false);
+                moduleLocations.put(name, l);
+            }
+            return l;
         }
     }
 
     /**
-     * General purpose implementation for search path locations, such as -sourcepath/SOURCE_PATH and
-     * -processorPath/ANNOTATION_PROCESSOR_PATH. All options are treated as equivalent (i.e. aliases.)
+     * General purpose implementation for search path locations,
+     * such as -sourcepath/SOURCE_PATH and -processorPath/ANNOTATION_PROCESSOR_PATH.
+     * All options are treated as equivalent (i.e. aliases.)
      * The value is an ordered set of files and/or directories.
      */
-    private class SimpleLocationHandler extends LocationHandler {
+    private class SimpleLocationHandler extends BasicLocationHandler {
 
         protected Collection<Path> searchPath;
 
@@ -438,12 +536,12 @@
         }
 
         @Override
-        Collection<Path> getLocation() {
+        Collection<Path> getPaths() {
             return searchPath;
         }
 
         @Override
-        void setLocation(Iterable<? extends Path> files) {
+        void setPaths(Iterable<? extends Path> files) {
             SearchPath p;
             if (files == null) {
                 p = computePath(null);
@@ -463,8 +561,8 @@
     }
 
     /**
-     * Subtype of SimpleLocationHandler for -classpath/CLASS_PATH. If no value is given, a default
-     * is provided, based on system properties and other values.
+     * Subtype of SimpleLocationHandler for -classpath/CLASS_PATH.
+     * If no value is given, a default is provided, based on system properties and other values.
      */
     private class ClassPathLocationHandler extends SimpleLocationHandler {
 
@@ -474,7 +572,7 @@
         }
 
         @Override
-        Collection<Path> getLocation() {
+        Collection<Path> getPaths() {
             lazy();
             return searchPath;
         }
@@ -511,19 +609,22 @@
 
         private void lazy() {
             if (searchPath == null) {
-                setLocation(null);
+                setPaths(null);
             }
         }
     }
 
     /**
-     * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH. Various options are supported for
-     * different components of the platform class path. Setting a value with setLocation overrides
-     * all existing option values. Setting any option overrides any value set with setLocation, and
-     * reverts to using default values for options that have not been set. Setting -bootclasspath or
-     * -Xbootclasspath overrides any existing value for -Xbootclasspath/p: and -Xbootclasspath/a:.
+     * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH.
+     * Various options are supported for different components of the
+     * platform class path.
+     * Setting a value with setLocation overrides all existing option values.
+     * Setting any option overrides any value set with setLocation, and
+     * reverts to using default values for options that have not been set.
+     * Setting -bootclasspath or -Xbootclasspath overrides any existing
+     * value for -Xbootclasspath/p: and -Xbootclasspath/a:.
      */
-    private class BootClassPathLocationHandler extends LocationHandler {
+    private class BootClassPathLocationHandler extends BasicLocationHandler {
 
         private Collection<Path> searchPath;
         final Map<Option, String> optionValues = new EnumMap<>(Option.class);
@@ -579,13 +680,13 @@
         }
 
         @Override
-        Collection<Path> getLocation() {
+        Collection<Path> getPaths() {
             lazy();
             return searchPath;
         }
 
         @Override
-        void setLocation(Iterable<? extends Path> files) {
+        void setPaths(Iterable<? extends Path> files) {
             if (files == null) {
                 searchPath = null;  // reset to "uninitialized"
             } else {
@@ -597,8 +698,6 @@
         }
 
         SearchPath computePath() throws IOException {
-            String java_home = System.getProperty("java.home");
-
             SearchPath path = new SearchPath();
 
             String bootclasspathOpt = optionValues.get(BOOTCLASSPATH);
@@ -618,7 +717,7 @@
                 path.addFiles(bootclasspathOpt);
             } else {
                 // Standard system classes for this compiler's release.
-                Collection<Path> systemClasses = systemClasses(java_home);
+                Collection<Path> systemClasses = systemClasses();
                 if (systemClasses != null) {
                     path.addFiles(systemClasses, false);
                 } else {
@@ -637,7 +736,7 @@
                 path.addDirectories(extdirsOpt);
             } else {
                 // Add lib/jfxrt.jar to the search path
-               Path jfxrt = Paths.get(java_home, "lib", "jfxrt.jar");
+               Path jfxrt = javaHome.resolve("lib/jfxrt.jar");
                 if (Files.exists(jfxrt)) {
                     path.addFile(jfxrt, false);
                 }
@@ -658,21 +757,14 @@
          *
          * @throws UncheckedIOException if an I/O errors occurs
          */
-        private Collection<Path> systemClasses(String java_home) throws IOException {
-            // Return .jimage files if available
-            Path libModules = Paths.get(java_home, "lib", "modules");
-            if (Files.exists(libModules)) {
-                try (Stream<Path> files = Files.list(libModules)) {
-                    boolean haveJImageFiles =
-                            files.anyMatch(f -> f.getFileName().toString().endsWith(".jimage"));
-                    if (haveJImageFiles) {
-                        return addAdditionalBootEntries(Collections.singleton(JRT_MARKER_FILE));
-                    }
-                }
+        private Collection<Path> systemClasses() throws IOException {
+            // Return "modules" jimage file if available
+            if (Files.isRegularFile(thisSystemModules)) {
+                return addAdditionalBootEntries(Collections.singleton(thisSystemModules));
             }
 
             // Exploded module image
-            Path modules = Paths.get(java_home, "modules");
+            Path modules = javaHome.resolve("modules");
             if (Files.isDirectory(modules.resolve("java.base"))) {
                 try (Stream<Path> listedModules = Files.list(modules)) {
                     return addAdditionalBootEntries(listedModules.collect(Collectors.toList()));
@@ -686,18 +778,18 @@
         //ensure bootclasspath prepends/appends are reflected in the systemClasses
         private Collection<Path> addAdditionalBootEntries(Collection<Path> modules) throws IOException {
             String files = System.getProperty("sun.boot.class.path");
-
             if (files == null)
                 return modules;
 
             Set<Path> paths = new LinkedHashSet<>();
 
+            // The JVM no longer supports -Xbootclasspath/p:, so any interesting
+            // entries should be appended to the set of modules.
+
+            paths.addAll(modules);
+
             for (String s : files.split(Pattern.quote(File.pathSeparator))) {
-                if (s.endsWith(".jimage")) {
-                    paths.addAll(modules);
-                } else if (!s.isEmpty()) {
-                    paths.add(Paths.get(s));
-                }
+                paths.add(Paths.get(s));
             }
 
             return paths;
@@ -715,6 +807,690 @@
         }
     }
 
+    /**
+     * A LocationHander to represent modules found from a module-oriented
+     * location such as MODULE_SOURCE_PATH, UPGRADE_MODULE_PATH,
+     * SYSTEM_MODULES and MODULE_PATH.
+     *
+     * The Location can be specified to accept overriding classes from the
+     * -Xpatch:dir parameter.
+     */
+    private class ModuleLocationHandler extends LocationHandler implements Location {
+        protected final String name;
+        protected final String moduleName;
+        protected final Collection<Path> searchPath;
+        protected final Collection<Path> searchPathWithOverrides;
+        protected final boolean output;
+
+        ModuleLocationHandler(String name, String moduleName, Collection<Path> searchPath,
+                boolean output, boolean allowOverrides) {
+            this.name = name;
+            this.moduleName = moduleName;
+            this.searchPath = searchPath;
+            this.output = output;
+
+            if (allowOverrides) {
+                if (patchMap != null) {
+                    SearchPath mPatch = patchMap.get(moduleName);
+                    if (mPatch != null) {
+                        SearchPath sp = new SearchPath();
+                        sp.addAll(mPatch);
+                        sp.addAll(searchPath);
+                        searchPathWithOverrides = sp;
+                    } else {
+                        searchPathWithOverrides = searchPath;
+                    }
+                } else {
+                     // for old style patch option; retained for transition
+                    Set<Path> overrides = new LinkedHashSet<>();
+                    if (moduleOverrideSearchPath != null) {
+                       for (Path p: moduleOverrideSearchPath) {
+                           Path o = p.resolve(moduleName);
+                           if (Files.isDirectory(o)) {
+                               overrides.add(o);
+                           }
+                       }
+                    }
+
+                    if (!overrides.isEmpty()) {
+                        overrides.addAll(searchPath);
+                        searchPathWithOverrides = overrides;
+                    } else {
+                        searchPathWithOverrides = searchPath;
+                    }
+                }
+            } else {
+                searchPathWithOverrides = searchPath;
+            }
+        }
+
+        @Override // defined by Location
+        public String getName() {
+            return name;
+        }
+
+        @Override // defined by Location
+        public boolean isOutputLocation() {
+            return output;
+        }
+
+        @Override // defined by LocationHandler
+        boolean handleOption(Option option, String value) {
+            throw new UnsupportedOperationException();
+        }
+
+        @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 searchPathWithOverrides;
+        }
+
+        @Override // defined by LocationHandler
+        void setPaths(Iterable<? extends Path> files) throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override // defined by LocationHandler
+        String inferModuleName() {
+            return moduleName;
+        }
+    }
+
+    /**
+     * A LocationHandler for simple module-oriented search paths,
+     * like UPGRADE_MODULE_PATH and MODULE_PATH.
+     */
+    private class ModulePathLocationHandler extends SimpleLocationHandler {
+        ModulePathLocationHandler(Location location, Option... options) {
+            super(location, options);
+        }
+
+        @Override
+        public boolean handleOption(Option option, String value) {
+            if (!options.contains(option)) {
+                return false;
+            }
+            setPaths(value == null ? null : getPathEntries(value));
+            return true;
+        }
+
+        @Override
+        Iterable<Set<Location>> listModuleLocations() {
+            if (searchPath == null)
+                return Collections.emptyList();
+
+            return () -> new ModulePathIterator();
+        }
+
+        @Override
+        void setPaths(Iterable<? extends Path> paths) {
+            if (paths != null) {
+                for (Path p: paths) {
+                    checkValidModulePathEntry(p);
+                }
+            }
+            super.setPaths(paths);
+        }
+
+        private void checkValidModulePathEntry(Path p) {
+            if (Files.isDirectory(p)) {
+                // either an exploded module or a directory of modules
+                return;
+            }
+
+            String name = p.getFileName().toString();
+            int lastDot = name.lastIndexOf(".");
+            if (lastDot > 0) {
+                switch (name.substring(lastDot)) {
+                    case ".jar":
+                    case ".jmod":
+                        return;
+                }
+            }
+            throw new IllegalArgumentException(p.toString());
+        }
+
+        class ModulePathIterator implements Iterator<Set<Location>> {
+            Iterator<Path> pathIter = searchPath.iterator();
+            int pathIndex = 0;
+            Set<Location> next = null;
+
+            @Override
+            public boolean hasNext() {
+                if (next != null)
+                    return true;
+
+                while (next == null) {
+                    if (pathIter.hasNext()) {
+                        Path path = pathIter.next();
+                        if (Files.isDirectory(path)) {
+                            next = scanDirectory(path);
+                        } else {
+                            next = scanFile(path);
+                        }
+                        pathIndex++;
+                    } else
+                        return false;
+                }
+                return true;
+            }
+
+            @Override
+            public Set<Location> next() {
+                hasNext();
+                if (next != null) {
+                    Set<Location> result = next;
+                    next = null;
+                    return result;
+                }
+                throw new NoSuchElementException();
+            }
+
+            private Set<Location> scanDirectory(Path path) {
+                Set<Path> paths = new LinkedHashSet<>();
+                Path moduleInfoClass = null;
+                try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
+                    for (Path entry: stream) {
+                        if (entry.endsWith("module-info.class")) {
+                            moduleInfoClass = entry;
+                            break;  // no need to continue scanning
+                        }
+                        paths.add(entry);
+                    }
+                } catch (DirectoryIteratorException | IOException ignore) {
+                    log.error(Errors.LocnCantReadDirectory(path));
+                    return Collections.emptySet();
+                }
+
+                if (moduleInfoClass != null) {
+                    // It's an exploded module directly on the module path.
+                    // We can't infer module name from the directory name, so have to
+                    // read module-info.class.
+                    try {
+                        String moduleName = readModuleName(moduleInfoClass);
+                        String name = location.getName()
+                                + "[" + pathIndex + ":" + moduleName + "]";
+                        ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
+                                Collections.singleton(path), false, true);
+                        return Collections.singleton(l);
+                    } catch (ModuleNameReader.BadClassFile e) {
+                        log.error(Errors.LocnBadModuleInfo(path));
+                        return Collections.emptySet();
+                    } catch (IOException e) {
+                        log.error(Errors.LocnCantReadFile(path));
+                        return Collections.emptySet();
+                    }
+                }
+
+                // A directory of modules
+                Set<Location> result = new LinkedHashSet<>();
+                int index = 0;
+                for (Path entry : paths) {
+                    Pair<String,Path> module = inferModuleName(entry);
+                    if (module == null) {
+                        // diagnostic reported if necessary; skip to next
+                        continue;
+                    }
+                    String moduleName = module.fst;
+                    Path modulePath = module.snd;
+                    String name = location.getName()
+                            + "[" + pathIndex + "." + (index++) + ":" + moduleName + "]";
+                    ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
+                            Collections.singleton(modulePath), false, true);
+                    result.add(l);
+                }
+                return result;
+            }
+
+            private Set<Location> scanFile(Path path) {
+                Pair<String,Path> module = inferModuleName(path);
+                if (module == null) {
+                    // diagnostic reported if necessary
+                    return Collections.emptySet();
+                }
+                String moduleName = module.fst;
+                Path modulePath = module.snd;
+                String name = location.getName()
+                        + "[" + pathIndex + ":" + moduleName + "]";
+                ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
+                        Collections.singleton(modulePath), false, true);
+                return Collections.singleton(l);
+            }
+
+            private Pair<String,Path> inferModuleName(Path p) {
+                if (Files.isDirectory(p)) {
+                    if (Files.exists(p.resolve("module-info.class"))) {
+                        String name = p.getFileName().toString();
+                        if (SourceVersion.isName(name))
+                            return new Pair<>(name, p);
+                    }
+                    return null;
+                }
+
+                if (p.getFileName().toString().endsWith(".jar")) {
+                    try (FileSystem fs = FileSystems.newFileSystem(p, null)) {
+                        Path moduleInfoClass = fs.getPath("module-info.class");
+                        if (Files.exists(moduleInfoClass)) {
+                            String moduleName = readModuleName(moduleInfoClass);
+                            return new Pair<>(moduleName, p);
+                        }
+                    } catch (ModuleNameReader.BadClassFile e) {
+                        log.error(Errors.LocnBadModuleInfo(p));
+                        return null;
+                    } catch (IOException e) {
+                        log.error(Errors.LocnCantReadFile(p));
+                        return null;
+                    }
+
+                    //automatic module:
+                    String fn = p.getFileName().toString();
+                    //from ModulePath.deriveModuleDescriptor:
+
+                    // drop .jar
+                    String mn = fn.substring(0, fn.length()-4);
+
+                    // find first occurrence of -${NUMBER}. or -${NUMBER}$
+                    Matcher matcher = Pattern.compile("-(\\d+(\\.|$))").matcher(mn);
+                    if (matcher.find()) {
+                        int start = matcher.start();
+
+                        mn = mn.substring(0, start);
+                    }
+
+                    // finally clean up the module name
+                    mn =  mn.replaceAll("[^A-Za-z0-9]", ".")  // replace non-alphanumeric
+                            .replaceAll("(\\.)(\\1)+", ".")   // collapse repeating dots
+                            .replaceAll("^\\.", "")           // drop leading dots
+                            .replaceAll("\\.$", "");          // drop trailing dots
+
+
+                    if (!mn.isEmpty()) {
+                        return new Pair<>(mn, p);
+                    }
+
+                    log.error(Errors.LocnCantGetModuleNameForJar(p));
+                    return null;
+                }
+
+                if (p.getFileName().toString().endsWith(".jmod")) {
+                    try {
+                        FileSystem fs = fileSystems.get(p);
+                        if (fs == null) {
+                            URI uri = URI.create("jar:" + p.toUri());
+                            fs = FileSystems.newFileSystem(uri, Collections.emptyMap(), null);
+                            try {
+                                Path moduleInfoClass = fs.getPath("classes/module-info.class");
+                                String moduleName = readModuleName(moduleInfoClass);
+                                Path modulePath = fs.getPath("classes");
+                                fileSystems.put(p, fs);
+                                closeables.add(fs);
+                                fs = null; // prevent fs being closed in the finally clause
+                                return new Pair<>(moduleName, modulePath);
+                            } finally {
+                                if (fs != null)
+                                    fs.close();
+                            }
+                        }
+                    } catch (ProviderNotFoundException e) {
+                        // will be thrown if the file is not a valid zip file
+                        log.error(Errors.LocnCantReadFile(p));
+                        return null;
+                    } catch (ModuleNameReader.BadClassFile e) {
+                        log.error(Errors.LocnBadModuleInfo(p));
+                    } catch (IOException e) {
+                        log.error(Errors.LocnCantReadFile(p));
+                        return null;
+                    }
+                }
+
+                if (warn && false) {  // temp disable
+                    log.warning(Warnings.LocnUnknownFileOnModulePath(p));
+                }
+                return null;
+            }
+
+            private String readModuleName(Path path) throws IOException, ModuleNameReader.BadClassFile {
+                if (moduleNameReader == null)
+                    moduleNameReader = new ModuleNameReader();
+                return moduleNameReader.readModuleName(path);
+            }
+        }
+
+    }
+
+    private class ModuleSourcePathLocationHandler extends BasicLocationHandler {
+
+        private Map<String, Location> moduleLocations;
+        private Map<Path, Location> pathLocations;
+
+
+        ModuleSourcePathLocationHandler() {
+            super(StandardLocation.MODULE_SOURCE_PATH,
+                    Option.MODULESOURCEPATH);
+        }
+
+        @Override
+        boolean handleOption(Option option, String value) {
+            init(value);
+            return true;
+        }
+
+        void init(String value) {
+            Collection<String> segments = new ArrayList<>();
+            for (String s: value.split(File.pathSeparator)) {
+                expandBraces(s, segments);
+            }
+
+            Map<String, Collection<Path>> map = new LinkedHashMap<>();
+            final String MARKER = "*";
+            for (String seg: segments) {
+                int markStart = seg.indexOf(MARKER);
+                if (markStart == -1) {
+                    add(map, Paths.get(seg), null);
+                } else {
+                    if (markStart == 0 || !isSeparator(seg.charAt(markStart - 1))) {
+                        throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg);
+                    }
+                    Path prefix = Paths.get(seg.substring(0, markStart - 1));
+                    Path suffix;
+                    int markEnd = markStart + MARKER.length();
+                    if (markEnd == seg.length()) {
+                        suffix = null;
+                    } else if (!isSeparator(seg.charAt(markEnd))
+                            || seg.indexOf(MARKER, markEnd) != -1) {
+                        throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg);
+                    } else {
+                        suffix = Paths.get(seg.substring(markEnd + 1));
+                    }
+                    add(map, prefix, suffix);
+                }
+            }
+
+            moduleLocations = new LinkedHashMap<>();
+            pathLocations = new LinkedHashMap<>();
+            map.forEach((k, v) -> {
+                String name = location.getName() + "[" + k + "]";
+                ModuleLocationHandler h = new ModuleLocationHandler(name, k, v, false, false);
+                moduleLocations.put(k, h);
+                v.forEach(p -> pathLocations.put(normalize(p), h));
+            });
+        }
+
+        private boolean isSeparator(char ch) {
+            // allow both separators on Windows
+            return (ch == File.separatorChar) || (ch == '/');
+        }
+
+        void add(Map<String, Collection<Path>> map, Path prefix, Path suffix) {
+            if (!Files.isDirectory(prefix)) {
+                if (warn) {
+                    String key = Files.exists(prefix)
+                            ? "dir.path.element.not.directory"
+                            : "dir.path.element.not.found";
+                    log.warning(Lint.LintCategory.PATH, key, prefix);
+                }
+                return;
+            }
+            try (DirectoryStream<Path> stream = Files.newDirectoryStream(prefix, path -> Files.isDirectory(path))) {
+                for (Path entry: stream) {
+                    Path path = (suffix == null) ? entry : entry.resolve(suffix);
+                    if (Files.isDirectory(path)) {
+                        String name = entry.getFileName().toString();
+                        Collection<Path> paths = map.get(name);
+                        if (paths == null)
+                            map.put(name, paths = new ArrayList<>());
+                        paths.add(path);
+                    }
+                }
+            } catch (IOException e) {
+                // TODO? What to do?
+                System.err.println(e);
+            }
+        }
+
+        private void expandBraces(String value, Collection<String> results) {
+            int depth = 0;
+            int start = -1;
+            String prefix = null;
+            String suffix = null;
+            for (int i = 0; i < value.length(); i++) {
+                switch (value.charAt(i)) {
+                    case '{':
+                        depth++;
+                        if (depth == 1) {
+                            prefix = value.substring(0, i);
+                            suffix = value.substring(getMatchingBrace(value, i) + 1);
+                            start = i + 1;
+                        }
+                        break;
+
+                    case ',':
+                        if (depth == 1) {
+                            String elem = value.substring(start, i);
+                            expandBraces(prefix + elem + suffix, results);
+                            start = i + 1;
+                        }
+                        break;
+
+                    case '}':
+                        switch (depth) {
+                            case 0:
+                                throw new IllegalArgumentException("mismatched braces");
+
+                            case 1:
+                                String elem = value.substring(start, i);
+                                expandBraces(prefix + elem + suffix, results);
+                                return;
+
+                            default:
+                                depth--;
+                        }
+                        break;
+                }
+            }
+            if (depth > 0)
+                throw new IllegalArgumentException("mismatched braces");
+            results.add(value);
+        }
+
+        int getMatchingBrace(String value, int offset) {
+            int depth = 1;
+            for (int i = offset + 1; i < value.length(); i++) {
+                switch (value.charAt(i)) {
+                    case '{':
+                        depth++;
+                        break;
+
+                    case '}':
+                        if (--depth == 0)
+                            return i;
+                        break;
+                }
+            }
+            throw new IllegalArgumentException("mismatched braces");
+        }
+
+        @Override
+        boolean isSet() {
+            return (moduleLocations != null);
+        }
+
+        @Override
+        Collection<Path> getPaths() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        void setPaths(Iterable<? extends Path> files) throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        Location getModuleLocation(String name) {
+            return (moduleLocations == null) ? null : moduleLocations.get(name);
+        }
+
+        @Override
+        Location getModuleLocation(Path dir) {
+            return (pathLocations == null) ? null : pathLocations.get(dir);
+        }
+
+        @Override
+        Iterable<Set<Location>> listModuleLocations() {
+            if (moduleLocations == null)
+                return Collections.emptySet();
+            Set<Location> locns = new LinkedHashSet<>();
+            moduleLocations.forEach((k, v) -> locns.add(v));
+            return Collections.singleton(locns);
+        }
+
+    }
+
+    private class SystemModulesLocationHandler extends BasicLocationHandler {
+        private Path javaHome;
+        private Path modules;
+        private Map<String, ModuleLocationHandler> systemModules;
+
+        SystemModulesLocationHandler() {
+            super(StandardLocation.SYSTEM_MODULES, Option.SYSTEM);
+            javaHome = Paths.get(System.getProperty("java.home"));
+        }
+
+        @Override
+        boolean handleOption(Option option, String value) {
+            if (!options.contains(option)) {
+                return false;
+            }
+
+            if (value == null) {
+                javaHome = Paths.get(System.getProperty("java.home"));
+            } else if (value.equals("none")) {
+                javaHome = null;
+            } else {
+                update(Paths.get(value));
+            }
+
+            modules = null;
+            return true;
+        }
+
+        @Override
+        Collection<Path> getPaths() {
+            return (javaHome == null) ? null : Collections.singleton(javaHome);
+        }
+
+        @Override
+        void setPaths(Iterable<? extends Path> files) throws IOException {
+            if (files == null) {
+                javaHome = 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");
+                }
+                update(dir);
+            }
+        }
+
+        private void update(Path p) {
+            if (!isCurrentPlatform(p) && !Files.exists(p.resolve("jrt-fs.jar")) && !Files.exists(javaHome.resolve("modules")))
+                throw new IllegalArgumentException(p.toString());
+            javaHome = p;
+            modules = null;
+        }
+
+        private boolean isCurrentPlatform(Path p) {
+            Path jh = Paths.get(System.getProperty("java.home"));
+            try {
+                return Files.isSameFile(p, jh);
+            } catch (IOException ex) {
+                throw new IllegalArgumentException(p.toString(), ex);
+            }
+        }
+
+        @Override
+        Location getModuleLocation(String name) throws IOException {
+            initSystemModules();
+            return systemModules.get(name);
+        }
+
+        @Override
+        Iterable<Set<Location>> listModuleLocations() throws IOException {
+            initSystemModules();
+            Set<Location> locns = new LinkedHashSet<>();
+            for (Location l: systemModules.values())
+                locns.add(l);
+            return Collections.singleton(locns);
+        }
+
+        private void initSystemModules() throws IOException {
+            if (systemModules != null) {
+                return;
+            }
+
+            if (javaHome == null) {
+                systemModules = Collections.emptyMap();
+                return;
+            }
+
+            if (modules == null) {
+                try {
+                    URI jrtURI = URI.create("jrt:/");
+                    FileSystem jrtfs;
+
+                    if (isCurrentPlatform(javaHome)) {
+                        jrtfs = FileSystems.getFileSystem(jrtURI);
+                    } else {
+                        try {
+                            Map<String, String> attrMap =
+                                    Collections.singletonMap("java.home", javaHome.toString());
+                            jrtfs = FileSystems.newFileSystem(jrtURI, attrMap);
+                        } catch (ProviderNotFoundException ex) {
+                            URL javaHomeURL = javaHome.resolve("jrt-fs.jar").toUri().toURL();
+                            ClassLoader currentLoader = Locations.class.getClassLoader();
+                            URLClassLoader fsLoader =
+                                    new URLClassLoader(new URL[] {javaHomeURL}, currentLoader);
+
+                            jrtfs = FileSystems.newFileSystem(jrtURI, Collections.emptyMap(), fsLoader);
+
+                            closeables.add(fsLoader);
+                        }
+
+                        closeables.add(jrtfs);
+                    }
+
+                    modules = jrtfs.getPath("/modules");
+                } catch (FileSystemNotFoundException | ProviderNotFoundException e) {
+                    modules = javaHome.resolve("modules");
+                    if (!Files.exists(modules))
+                        throw new IOException("can't find system classes", e);
+                }
+            }
+
+            systemModules = new LinkedHashMap<>();
+            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, true);
+                    systemModules.put(moduleName, h);
+                }
+            }
+        }
+    }
+
     Map<Location, LocationHandler> handlersForLocation;
     Map<Option, LocationHandler> handlersForOption;
 
@@ -722,17 +1498,23 @@
         handlersForLocation = new HashMap<>();
         handlersForOption = new EnumMap<>(Option.class);
 
-        LocationHandler[] handlers = {
+        BasicLocationHandler[] handlers = {
             new BootClassPathLocationHandler(),
             new ClassPathLocationHandler(),
             new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCEPATH),
             new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSORPATH),
-            new OutputLocationHandler((StandardLocation.CLASS_OUTPUT), Option.D),
-            new OutputLocationHandler((StandardLocation.SOURCE_OUTPUT), Option.S),
-            new OutputLocationHandler((StandardLocation.NATIVE_HEADER_OUTPUT), Option.H)
+            new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, Option.PROCESSORMODULEPATH),
+            new OutputLocationHandler(StandardLocation.CLASS_OUTPUT, Option.D),
+            new OutputLocationHandler(StandardLocation.SOURCE_OUTPUT, Option.S),
+            new OutputLocationHandler(StandardLocation.NATIVE_HEADER_OUTPUT, Option.H),
+            new ModuleSourcePathLocationHandler(),
+            // TODO: should UPGRADE_MODULE_PATH be merged with SYSTEM_MODULES?
+            new ModulePathLocationHandler(StandardLocation.UPGRADE_MODULE_PATH, Option.UPGRADEMODULEPATH),
+            new ModulePathLocationHandler(StandardLocation.MODULE_PATH, Option.MODULEPATH, Option.MP),
+            new SystemModulesLocationHandler(),
         };
 
-        for (LocationHandler h : handlers) {
+        for (BasicLocationHandler h : handlers) {
             handlersForLocation.put(h.location, h);
             for (Option o : h.options) {
                 handlersForOption.put(o, h);
@@ -740,14 +1522,56 @@
         }
     }
 
+    private SearchPath moduleOverrideSearchPath; // for old style patch option; retained for transition
+    private Map<String, SearchPath> patchMap;
+
     boolean handleOption(Option option, String value) {
-        LocationHandler h = handlersForOption.get(option);
-        return (h == null ? false : h.handleOption(option, value));
+        switch (option) {
+            case XPATCH:
+                if (value.contains("=")) {
+                    Map<String, SearchPath> map = new LinkedHashMap<>();
+                    for (String entry: value.split(",")) {
+                        int eq = entry.indexOf('=');
+                        if (eq > 0) {
+                            String mName = entry.substring(0, eq);
+                            SearchPath mPatchPath = new SearchPath()
+                                    .addFiles(entry.substring(eq + 1));
+                            boolean ok = true;
+                            for (Path p: mPatchPath) {
+                                Path mi = p.resolve("module-info.class");
+                                if (Files.exists(mi)) {
+                                    log.error(Errors.LocnModuleInfoNotAllowedOnPatchPath(mi));
+                                    ok = false;
+                                }
+                            }
+                            if (ok && !mPatchPath.isEmpty()) {
+                                map.computeIfAbsent(mName, (_x) -> new SearchPath())
+                                        .addAll(mPatchPath);
+                            }
+                        } else {
+                            log.error(Errors.LocnInvalidArgForXpatch(entry));
+                        }
+                    }
+                    patchMap = map;
+                } else {
+                     // for old style patch option; retained for transition
+                    moduleOverrideSearchPath = new SearchPath().addFiles(value);
+                }
+                return true;
+            default:
+                LocationHandler h = handlersForOption.get(option);
+                return (h == null ? false : h.handleOption(option, value));
+        }
+    }
+
+    boolean hasLocation(Location location) {
+        LocationHandler h = getHandler(location);
+        return (h == null ? false : h.isSet());
     }
 
     Collection<Path> getLocation(Location location) {
         LocationHandler h = getHandler(location);
-        return (h == null ? null : h.getLocation());
+        return (h == null ? null : h.getPaths());
     }
 
     Path getOutputLocation(Location location) {
@@ -768,12 +1592,34 @@
             }
             handlersForLocation.put(location, h);
         }
-        h.setLocation(files);
+        h.setPaths(files);
+    }
+
+    Location getModuleLocation(Location location, String name) throws IOException {
+        LocationHandler h = getHandler(location);
+        return (h == null ? null : h.getModuleLocation(name));
+    }
+
+    Location getModuleLocation(Location location, Path dir) {
+        LocationHandler h = getHandler(location);
+        return (h == null ? null : h.getModuleLocation(dir));
+    }
+
+    String inferModuleName(Location location) {
+        LocationHandler h = getHandler(location);
+        return (h == null ? null : h.inferModuleName());
+    }
+
+    Iterable<Set<Location>> listModuleLocations(Location location) throws IOException {
+        LocationHandler h = getHandler(location);
+        return (h == null ? null : h.listModuleLocations());
     }
 
     protected LocationHandler getHandler(Location location) {
         Objects.requireNonNull(location);
-        return handlersForLocation.get(location);
+        return (location instanceof LocationHandler)
+                ? (LocationHandler) location
+                : handlersForLocation.get(location);
     }
 
     /**
@@ -785,4 +1631,12 @@
                 && (n.endsWith(".jar") || n.endsWith(".zip"));
     }
 
+    static Path normalize(Path p) {
+        try {
+            return p.toRealPath();
+        } catch (IOException e) {
+            return p.toAbsolutePath().normalize();
+        }
+    }
+
 }