changeset 14510:85e0957f1d06

Change Configuration.resolve to take a parent Configuration rather than parent Layer Change Configuration.reads to read a set of read dependences rather than descriptors
author alanb
date Fri, 27 Nov 2015 15:34:17 +0000
parents be2e47bf00ce
children a92829ff8629
files make/src/classes/build/tools/jigsaw/GenGraphs.java make/src/classes/build/tools/jigsaw/ModuleSummary.java src/java.base/share/classes/java/lang/ClassLoader.java src/java.base/share/classes/java/lang/module/Configuration.java src/java.base/share/classes/java/lang/module/Resolver.java src/java.base/share/classes/java/lang/reflect/Layer.java src/java.base/share/classes/java/lang/reflect/Module.java src/java.base/share/classes/java/util/ServiceLoader.java src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java src/java.base/share/classes/sun/launcher/LauncherHelper.java src/jdk.jlink/share/classes/jdk/tools/jlink/JlinkTask.java src/jdk.jlink/share/classes/jdk/tools/jlink/TaskHelper.java test/java/lang/Class/getResource/Main.java test/java/lang/ModuleClassLoader/Basic.java test/jdk/jigsaw/module/AutomaticModulesTest.java test/jdk/jigsaw/module/ConfigurationTest.java test/jdk/jigsaw/reflect/Layer/LayerTest.java test/jdk/jigsaw/reflect/Module/BasicModuleTest.java test/jdk/jigsaw/reflect/Proxy/ProxyClassAccessTest.java test/jdk/jigsaw/reflect/Proxy/ProxyLayerTest.java test/jdk/jigsaw/scenarios/automaticmodules/src/basictest/test/Main.java test/jdk/jigsaw/scenarios/container/src/container/container/Main.java test/jdk/jigsaw/util/ServiceLoader/ServicesTest.java
diffstat 23 files changed, 2094 insertions(+), 1159 deletions(-) [+]
line wrap: on
line diff
--- a/make/src/classes/build/tools/jigsaw/GenGraphs.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/make/src/classes/build/tools/jigsaw/GenGraphs.java	Fri Nov 27 15:34:17 2015 +0000
@@ -38,9 +38,8 @@
 import java.util.TreeSet;
 import java.util.function.Function;
 import java.util.stream.Collectors;
-import java.lang.reflect.Layer;
-import java.lang.reflect.Module;
 import java.lang.module.Configuration;
+import java.lang.module.Configuration.ReadDependence;
 import java.lang.module.ModuleReference;
 import java.lang.module.ModuleFinder;
 import java.lang.module.ModuleDescriptor;
@@ -86,14 +85,14 @@
             }
             mods.add(name);
             Configuration cf = Configuration.resolve(finder,
-                    Layer.empty(),
+                    Configuration.empty(),
                     ModuleFinder.empty(),
                     name);
             genGraphs.genDotFile(dir, name, cf);
         }
 
         Configuration cf = Configuration.resolve(finder,
-                Layer.empty(),
+                Configuration.empty(),
                 ModuleFinder.empty(),
                 mods);
         genGraphs.genDotFile(dir, "jdk", cf);
@@ -205,7 +204,8 @@
             String mn = md.name();
             builder.addNode(mn);
             cf.reads(md).stream()
-                    .map(d -> d.name())
+                    .map(ReadDependence::descriptor)
+                    .map(ModuleDescriptor::name)
                     .forEach(d -> builder.addEdge(mn, d));
         });
 
--- a/make/src/classes/build/tools/jigsaw/ModuleSummary.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/make/src/classes/build/tools/jigsaw/ModuleSummary.java	Fri Nov 27 15:34:17 2015 +0000
@@ -42,7 +42,6 @@
 import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
-import java.lang.reflect.Layer;
 import java.lang.module.Configuration;
 import java.lang.module.ModuleReference;
 import java.lang.module.ModuleFinder;
@@ -288,7 +287,7 @@
 
     static Configuration resolve(Set<String> roots) {
         return Configuration.resolve(ModuleFinder.ofInstalled(),
-                                     Layer.empty(),
+                                     Configuration.empty(),
                                      ModuleFinder.empty(),
                                      roots);
     }
--- a/src/java.base/share/classes/java/lang/ClassLoader.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/src/java.base/share/classes/java/lang/ClassLoader.java	Fri Nov 27 15:34:17 2015 +0000
@@ -811,14 +811,12 @@
         if (p == null) {
             URL url = null;
             if (m.isNamed() && m.getLayer() != null) {
-                Optional<Configuration> cf = m.getLayer().configuration();
-                if (cf.isPresent()) {
-                    ModuleReference mref = cf.get().findModule(m.getName()).orElse(null);
-                    URI uri = mref != null ? mref.location().orElse(null) : null;
-                    try {
-                        url = uri != null ? uri.toURL() : null;
-                    } catch (MalformedURLException e) {
-                    }
+                Configuration cf = m.getLayer().configuration();
+                ModuleReference mref = cf.findModule(m.getName()).orElse(null);
+                URI uri = mref != null ? mref.location().orElse(null) : null;
+                try {
+                    url = uri != null ? uri.toURL() : null;
+                } catch (MalformedURLException e) {
                 }
             }
             p = definePackage(pn, null, null, null, null, null, null, url);
--- a/src/java.base/share/classes/java/lang/module/Configuration.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/src/java.base/share/classes/java/lang/module/Configuration.java	Fri Nov 27 15:34:17 2015 +0000
@@ -25,9 +25,10 @@
 
 package java.lang.module;
 
-import java.lang.reflect.Layer;
+import java.lang.reflect.Layer;  // javadoc
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -38,10 +39,11 @@
  *
  * <p> Resolution is the process of computing the transitive closure of a set
  * of root modules over a set of observable modules with respect to a
- * dependence relation. Computing the transitive closure results in a
+ * dependence relation. Computing the transitive closure leads to a
  * <em>dependence graph</em> that is then transformed to a <em>readability
  * graph</em> by adding edges indicated by the readability relation ({@code
- * requires} or {@code requires public}). </p>
+ * requires} or {@code requires public}). A {@code Configuration} encapsulates
+ * the resulting readability graph. </p>
  *
  * <p> Suppose we have the following observable modules: </p>
  * <pre> {@code
@@ -60,12 +62,11 @@
  *     m2 --> m3
  * } </pre>
  *
- * <p> Resolution is an additive process. The dependence relation may include
- * dependences on modules that have already been instantiated in the Java
- * virtual machine (in a module {@link Layer Layer}). The result is a
- * <em>relative configuration</em> that is relative to a module layer and
- * where readability graph has read edges to modules in the layer (or
- * parent layers). </p>
+ * <p> Resolution is an additive process. When computing the transitive closure
+ * then the dependence relation may include dependences on modules in parent
+ * configurations. The result is a <em>relative configuration</em> that is
+ * relative to a parent configuration and where the readability graph may have
+ * read edges to modules in parent configurations. </p>
  *
  * <p> Suppose we have the following observable modules: </p>
  * <pre> {@code
@@ -73,17 +74,17 @@
  *     module m2 { }
  * } </pre>
  *
- * <p> If module {@code m1} is resolved with the {@link Layer#boot() boot}
- * layer as the parent layer then the resulting configuration contains two
- * modules ({@code m1}, {@code m2}). The edges in its readability graph
- * are:
+ * <p> If module {@code m1} is resolved with the configuration for the {@link
+ * Layer#boot() boot} layer as the parent then the resulting configuration
+ * contains two modules ({@code m1}, {@code m2}). The edges in its readability
+ * graph are:
  * <pre> {@code
  *     m1 --> m2
  *     m1 --> java.xml
  * } </pre>
- * where module {@code java.xml} is in the parent layer. For simplicity, this
- * example omits the implicitly declared dependence on the {@code java.base}
- * module. </p>
+ * where module {@code java.xml} is in the parent configuration. For
+ * simplicity, this example omits the implicitly declared dependence on the
+ * {@code java.base} module. </p>
  *
  * <p> Service binding is the process of augmenting a configuration with
  * modules from the set of observable modules induced by the service-use
@@ -127,17 +128,18 @@
  * type. </p>
  *
  * <p> The following example invokes the {@code resolve} method to resolve a
- * module named <em>myapp</em>. It then invokes {@code bind} on the
- * configuration to obtain a new configuration with additional modules (and
- * edges) induced by service-use relationships. </p>
+ * module named <em>myapp</em> with the configuration for the boot layer as
+ * the parent configuration. It then invokes {@code bind} on the configuration
+ * to obtain a new configuration with additional modules (and edges) induced
+ * by service-use relationships. </p>
  *
  * <pre>{@code
  *     ModuleFinder finder = ModuleFinder.of(dir1, dir2, dir3);
  *
  *     Configuration cf
- *         = Configuration.resolve(ModuleFinder.empty(),
- *                                 Layer.boot(),
- *                                 finder,
+ *         = Configuration.resolve(finder,
+ *                                 Layer.boot().configuration(),
+ *                                 ModuleFinder.empty(),
  *                                 "myapp")
  *                        .bind();
  * }</pre>
@@ -148,24 +150,37 @@
 
 public final class Configuration {
 
-    private final Layer parent;
-    private final Resolver.Resolution resolution;
+    // @see Configuration#empty()
+    private static final Configuration EMPTY_CONFIGURATION = new Configuration();
 
-    private Configuration(Layer parent, Resolver.Resolution resolution) {
+    private final Configuration parent;
+    private final Resolver.Result result;
+
+    private Configuration() {
+        this.parent = null;
+        this.result = null;
+    }
+
+    Configuration(Configuration parent, Resolver.Selected selected) {
         this.parent = parent;
-        this.resolution = resolution;
+        this.result = selected.finish(this);
     }
 
+    Resolver.Result result() {
+        return result;
+    }
+
+
     /**
      * Resolves the collection of root modules, specified by module names,
      * returning the resulting configuration.
      *
-     * <p> Each root module is located using the given {@code beforeFinder}
-     * and if not found, using the given {@code afterFinder}. Their transitive
-     * dependences are located using the given {@code beforeFinder}, or if not
-     * found then the parent {@code Layer}, or if not found then the given
-     * {@code afterFinder}. Dependences located in the parent {@code Layer}
-     * are resolved no further. </p>
+     * <p> Each root module is located using the given {@code beforeFinder}, or
+     * if not found then the parent configuration, or if not found then the
+     * given {@code afterFinder}. The same search order is used to locate
+     * transitive dependences. Root modules or dependences that are located in
+     * the parent configuration are resolved no further and are not included in
+     * the resulting configuration. </p>
      *
      * <p> When all modules have been resolved then the resulting <em>dependency
      * graph</em> is checked to ensure that it does not contain cycles. A
@@ -202,10 +217,11 @@
      * @param  beforeFinder
      *         The module finder to find modules
      * @param  parent
-     *         The parent layer, may be the {@link Layer#empty() empty} layer
+     *         The parent configuration, may be the {@link #empty() empty}
+     *         configuration
      * @param  afterFinder
      *         The module finder to locate modules when not located by the
-     *         {@code beforeFinder} or parent layer
+     *         {@code beforeFinder} or in the parent configuration
      * @param  roots
      *         The possibly-empty collection of module names of the modules
      *         to resolve
@@ -217,19 +233,19 @@
      *         fail for any of the reasons listed
      */
     public static Configuration resolve(ModuleFinder beforeFinder,
-                                        Layer parent,
+                                        Configuration parent,
                                         ModuleFinder afterFinder,
                                         Collection<String> roots)
     {
         Objects.requireNonNull(beforeFinder);
         Objects.requireNonNull(parent);
         Objects.requireNonNull(afterFinder);
+        Objects.requireNonNull(roots);
 
-        Resolver.Resolution resolution =
-            Resolver.resolve(beforeFinder, parent, afterFinder, roots);
+        Resolver resolver = new Resolver(beforeFinder, parent, afterFinder);
+        return resolver.resolve(roots);
+    }
 
-        return new Configuration(parent, resolution);
-    }
 
     /**
      * Resolves the root modules, specified by module names, returning the
@@ -237,16 +253,17 @@
      *
      * This method is equivalent to:
      * <pre>{@code
-     *   resolve(beforeFinder, layer, afterFinder, Arrays.asList(roots));
+     *   resolve(beforeFinder, parent, afterFinder, Arrays.asList(roots));
      * }</pre>
      *
      * @param  beforeFinder
      *         The module finder to find modules
      * @param  parent
-     *         The parent layer, may be the {@link Layer#empty() empty} layer
+     *         The parent configuration, may be the {@link #empty() empty}
+     *         configuration
      * @param  afterFinder
      *         The module finder to locate modules when not located by the
-     *         {@code beforeFinder} or parent layer
+     *         {@code beforeFinder} or in the parent configuration
      * @param  roots
      *         The possibly-empty array of module names of the modules
      *         to resolve
@@ -258,28 +275,41 @@
      *         fail for any of the reasons listed
      */
     public static Configuration resolve(ModuleFinder beforeFinder,
-                                        Layer parent,
+                                        Configuration parent,
                                         ModuleFinder afterFinder,
-                                        String... roots)
-    {
+                                        String... roots) {
         return resolve(beforeFinder, parent, afterFinder, Arrays.asList(roots));
     }
 
+
     /**
-     * Returns the parent {@code Layer} on which this configuration is based.
+     * Returns the <em>empty</em> configuration. The empty configuration does
+     * contain any modules and does not have a parent.
      *
-     * @return The parent layer
+     * @return The empty configuration
      */
-    public Layer layer() {
-        return parent;
+    public static Configuration empty() {
+        return EMPTY_CONFIGURATION;
     }
 
+
     /**
-     * Returns a new configuration that is this configuration augmented with
+     * Returns this configuration's parent unless this is the {@linkplain #empty
+     * empty configuration}, which has no parent.
+     *
+     * @return This configuration's parent
+     */
+    public Optional<Configuration> parent() {
+        return Optional.ofNullable(parent);
+    }
+
+
+    /**
+     * Returns a configuration that is this configuration augmented with
      * modules induced by the service-use relation.
      *
      * <p> Service binding works by examining all modules in the configuration
-     * and its parent layers with {@link ModuleDescriptor#uses()
+     * and its parent configuration with {@link ModuleDescriptor#uses()
      * service-dependences}. All observable modules that {@link
      * ModuleDescriptor#provides() provide} an implementation of one or more of
      * the service types are added to the configuration and resolved as if by
@@ -291,19 +321,22 @@
      * ResolutionException} for exactly the same reasons as the {@link #resolve
      * resolve} methods. </p>
      *
-     * @return A new configuration that is this configuration augmented
-     *         with modules that are induced by the service-use relation
+     * @return A configuration that is this configuration augmented with
+     *         modules that are induced by the service-use relation
      *
      * @throws ResolutionException If resolution or the post-resolution checks
      *         fail for any of the reasons listed
-     *
-     * @apiNote This method is not required to be thread safe
      */
     public Configuration bind() {
-        Resolver.Resolution r = resolution.bind();
-        return new Configuration(parent, r);
+        if (result == null) {
+            return this;
+        } else {
+            Resolver resolver = result.resolver();
+            return resolver.bind(this);
+        }
     }
 
+
     /**
      * Returns an immutable set of the module descriptors in this
      * configuration.
@@ -312,9 +345,14 @@
      *         for the modules in this configuration
      */
     public Set<ModuleDescriptor> descriptors() {
-        return resolution.selected();
+        if (result == null) {
+            return Collections.emptySet();
+        } else {
+            return result.descriptors();
+        }
     }
 
+
     /**
      * Returns an immutable set of the module references to the modules in this
      * configuration.
@@ -323,66 +361,159 @@
      *         to modules in this configuration
      */
     public Set<ModuleReference> modules() {
-        return resolution.references();
+        if (result == null) {
+            return Collections.emptySet();
+        } else {
+            return result.modules();
+        }
     }
 
+
     /**
-     * Returns the {@code ModuleDescriptor} for the named module.
+     * Returns the {@code ModuleDescriptor} with the given name in this
+     * configuration, or if not in this configuration, the {@linkplain #parent
+     * parent} configuration.
      *
-     * @apiNote Should this check parent to be consistent with Layer#findModule?
+     * @param  name
+     *         The name of the module to find
+     *
+     * @return The module with the given name or an empty {@code Optional}
+     *         if there isn't a module with this name in this configuration
+     *         or any parent configuration
+     */
+    public Optional<ModuleDescriptor> findDescriptor(String name) {
+        Objects.requireNonNull(name);
+        if (result == null)
+            return Optional.empty();
+        ModuleDescriptor descriptor = result.findDescriptor(name);
+        if (descriptor != null)
+            return Optional.of(descriptor);
+        return parent().flatMap(cf -> cf.findDescriptor(name));
+    }
+
+
+    /**
+     * Returns the {@code ModuleReference} for the named module in this
+     * configuration, or if not in this configuration, the {@linkplain #parent
+     * parent} configuration.
      *
      * @param name
      *        The name of the module to find
      *
-     * @return The module descriptor for the module with the given name or an
-     *         empty {@code Optional} if not in this configuration
+     * @return The module with the given name or an empty {@code Optional}
+     *         if there isn't a module with this name in this configuration
+     *         or any parent configuration
      */
-    public Optional<ModuleDescriptor> findDescriptor(String name) {
-        return findModule(name).map(ModuleReference::descriptor);
+    public Optional<ModuleReference> findModule(String name) {
+        Objects.requireNonNull(name);
+        if (result == null)
+            return Optional.empty();
+        ModuleReference mref = result.findModule(name);
+        if (mref != null)
+            return Optional.of(mref);
+        return parent().flatMap(cf -> cf.findModule(name));
     }
 
+
     /**
-     * Returns the {@code ModuleReference} for the named module.
+     * Represents a vertex in a readability graph.
      *
-     * @apiNote Should this check parent to be consistent with Layer#findModule?
+     * {@link Configuration} defines the {@link Configuration#reads reads}
+     * method to obtain the set of {@code ReadDependence} that a module in the
+     * configuration reads.
      *
-     * @param name
-     *        The name of the module to find
-     *
-     * @return The reference to a module with the given name or an empty
-     *         {@code Optional} if not in this configuration
+     * @since 1.9
      */
-    public Optional<ModuleReference> findModule(String name) {
-        return resolution.findModule(name);
+    public static final class ReadDependence {
+        private final Configuration cf;
+        private final ModuleDescriptor descriptor;
+
+        ReadDependence(Configuration cf, ModuleDescriptor descriptor) {
+            this.cf = Objects.requireNonNull(cf);
+            this.descriptor = Objects.requireNonNull(descriptor);
+        }
+
+        /**
+         * Returns the configuration.
+         *
+         * @return The configuration
+         */
+        public Configuration configuration() {
+            return cf;
+        }
+
+        /**
+         * Returns the module descriptor.
+         *
+         * @return The module descriptor.
+         */
+        public ModuleDescriptor descriptor() {
+            return descriptor;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(cf, descriptor);
+        }
+
+        @Override
+        public boolean equals(Object ob) {
+            if (!(ob instanceof ReadDependence))
+                return false;
+
+            ReadDependence that = (ReadDependence) ob;
+            return Objects.equals(this.cf, that.cf)
+                    && Objects.equals(this.descriptor, that.descriptor);
+        }
+
+        @Override
+        public String toString() {
+            return System.identityHashCode(cf) + "/" + descriptor.name();
+        }
     }
 
+
     /**
-     * Returns an immutable set of the read dependences of the given module
-     * descriptor. The set may include {@code ModuleDescriptor}s for modules
-     * that are in a parent {@code Layer} rather than this configuration.
+     * Returns an immutable set of the read dependences for a named module in
+     * this configuration.
      *
      * @return A possibly-empty unmodifiable set of the read dependences
      *
      * @throws IllegalArgumentException
-     *         If the module descriptor is not in this configuration
+     *         If the module is not in this configuration
      */
-    public Set<ModuleDescriptor> reads(ModuleDescriptor descriptor) {
-        Set<ModuleDescriptor> reads = resolution.reads(descriptor);
-        if (reads == null) {
-            throw new IllegalArgumentException(descriptor.name() +
-                " not in this configuration");
+    public Set<ReadDependence> reads(ModuleDescriptor descriptor) {
+        String name = descriptor.name();
+        if (result.findDescriptor(name) == null)
+            throw new IllegalArgumentException(name + " not in this Configuration");
+
+        ReadDependence rd = new ReadDependence(this, descriptor);
+        return result.reads(rd);
+    }
+
+
+    /**
+     * Returns the {@code ReadDependence} for the named module in this
+     * configuration, or if not in this configuration, the parent configuration.
+     * Returns {@code null} if not found.
+     */
+    ReadDependence findReadDependence(String name) {
+        if (result == null) {
+            return null;
+        } else {
+            ModuleDescriptor descriptor = result.findDescriptor(name);
+            if (descriptor != null)
+                return new ReadDependence(this, descriptor);
+            return parent.findReadDependence(name);
         }
-        return reads;
     }
 
+
     /**
      * Returns an immutable set of the module descriptors in this {@code
      * Configuration} that provide one or more implementations of the given
      * service.
      *
-     * If this {@code Configuration} is not the result of {@link #bind()
-     * binding} then an empty set is returned.
-     *
      * @param  st
      *         The service type
      *
@@ -390,9 +521,15 @@
      *         implementations of the given service
      */
     public Set<ModuleDescriptor> provides(String st) {
-        return resolution.provides(st);
+        Objects.requireNonNull(st);
+        if (result == null) {
+            return Collections.emptySet();
+        } else {
+            return result.provides(st);
+        }
     }
 
+
     @Override
     public String toString() {
         return descriptors().stream()
--- a/src/java.base/share/classes/java/lang/module/Resolver.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/src/java.base/share/classes/java/lang/module/Resolver.java	Fri Nov 27 15:34:17 2015 +0000
@@ -25,170 +25,257 @@
 
 package java.lang.module;
 
+import java.lang.reflect.Layer;
 import java.lang.module.ModuleDescriptor.Provides;
-import java.lang.module.ModuleDescriptor.Requires;
-import java.lang.reflect.Layer;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
+import java.lang.module.ModuleDescriptor.Requires.Modifier;
+import java.util.*;
 import java.util.Map.Entry;
-import java.util.Optional;
-import java.util.Set;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
+import java.lang.module.Configuration.ReadDependence;
 
 import jdk.internal.module.Hasher.DependencyHashes;
 
 /**
- * The resolver used by {@link Configuration#resolve} and {@link Configuration#bind}.
- *
- * TODO:
- * - replace makeGraph with efficient implementation for multiple layers
- * - replace cycle detection. The current DFS is fast at startup for the boot
- *   layer but isn't generally scalable
- * - automatic modules => self references in readability graph
+ * The resolver used by {@link Configuration#resolve} and {@link
+ * Configuration#bind}.
  */
 
 final class Resolver {
 
     /**
-     * The result of resolution or binding.
+     * Accumulates references to modules selected by the resolver during
+     * resolution.
+     *
+     * When resolution is complete then the {@code finish} method should be
+     * invoked to perform the post-resolution checks, generate the readability
+     * graph and create a {@code Result} object.
      */
-    final class Resolution {
+    static final class Selected {
 
-        // the set of module descriptors
-        private final Set<ModuleDescriptor> selected;
+        private final Resolver resolver;
 
-        // maps name to module reference for modules in this resolution
+        // module name -> module reference
         private final Map<String, ModuleReference> nameToReference;
 
-        // set of nameToReference.values()
-        private final Set<ModuleReference> references;
-
-        // the readability graph
-        private final Map<ModuleDescriptor, Set<ModuleDescriptor>> graph;
-
-        // maps a service type (by name) to the set of modules that provide services
-        private final Map<String, Set<ModuleDescriptor>> serviceToProviders;
-
-        Resolution(Set<ModuleDescriptor> selected,
-                   Map<String, ModuleReference> nameToReference,
-                   Map<ModuleDescriptor, Set<ModuleDescriptor>> graph,
-                   Map<String, Set<ModuleDescriptor>> serviceToProviders)
-        {
-            this.selected = Collections.unmodifiableSet(selected);
-            this.nameToReference = Collections.unmodifiableMap(nameToReference);
-            Set<ModuleReference> refs = new HashSet<>(nameToReference.values());
-            this.references = Collections.unmodifiableSet(refs);
-            this.graph = graph; // no need to make defensive copy
-            this.serviceToProviders = serviceToProviders; // no need to make copy
+        /**
+         * Creates a new Selected object for use by the resolver.
+         */
+        Selected(Resolver resolver) {
+            this.resolver = resolver;
+            this.nameToReference = new HashMap<>();
         }
 
-        Set<ModuleDescriptor> selected() {
-            return selected;
+        /**
+         * Creates a new Selected object that starts out with the modules
+         * in the given Result object.
+         */
+        Selected(Result result) {
+            this.resolver = result.resolver;
+            this.nameToReference = result.copySelected();
         }
 
-        Set<ModuleReference> references() {
-            return references;
+        Resolver resolver() {
+            return resolver;
         }
 
-        Optional<ModuleReference> findModule(String name) {
-            return Optional.ofNullable(nameToReference.get(name));
+        void add(ModuleReference mref) {
+            String name = mref.descriptor().name();
+            nameToReference.put(name, mref);
         }
 
-        Set<ModuleDescriptor> reads(ModuleDescriptor descriptor) {
-            Set<ModuleDescriptor> reads = graph.get(descriptor);
+        boolean contains(String name) {
+            return nameToReference.containsKey(name);
+        }
+
+        int count() {
+            return nameToReference.size();
+        }
+
+        Collection<ModuleReference> modules() {
+            return nameToReference.values();
+        }
+
+        ModuleReference findModule(String name) {
+            return nameToReference.get(name);
+        }
+
+        ModuleDescriptor findDescriptor(String name) {
+            ModuleReference mref = nameToReference.get(name);
+            return (mref != null) ? mref.descriptor() : null;
+        }
+
+        /**
+         * Finish the resolution or binding process.
+         */
+        Result finish(Configuration cf) {
+
+            resolver.detectCycles(this);
+
+            resolver.checkHashes(this);
+
+            // generate the readability graph
+            Map<ReadDependence, Set<ReadDependence>> graph
+                = resolver.makeGraph(this, cf);
+
+            resolver.checkExportSuppliers(graph);
+
+            return new Result(resolver, nameToReference, graph);
+        }
+
+    }
+
+
+    /**
+     * The final result of resolution or binding.
+     *
+     * A Result encapsulates the readability graph,
+     */
+    static final class Result {
+
+        private final Resolver resolver;
+
+        // module name -> module reference for the selected modules
+        private final Map<String, ModuleReference> nameToReference;
+
+        // the module references of the selected modules
+        private final Set<ModuleReference> modules;
+
+        // the module descriptors of the selected modules
+        private final Set<ModuleDescriptor> descriptors;
+
+        // readability graph
+        private final Map<ReadDependence, Set<ReadDependence>> graph;
+
+        Result(Resolver resolver,
+               Map<String, ModuleReference> nameToReference,
+               Map<ReadDependence, Set<ReadDependence>> graph)
+        {
+            int nselected = nameToReference.size();
+            Map<String, ModuleReference> map = new HashMap<>(nselected);
+            Set<ModuleReference> modules = new HashSet<>(nselected);
+            Set<ModuleDescriptor> descriptors = new HashSet<>(nselected);
+
+            for (Entry<String, ModuleReference> e : nameToReference.entrySet()) {
+                String name = e.getKey();
+                ModuleReference mref = e.getValue();
+
+                map.put(name, mref);
+                modules.add(mref);
+                descriptors.add(mref.descriptor());
+            }
+
+            this.resolver = resolver;
+            this.nameToReference = map; // no need to copy
+            this.modules = Collections.unmodifiableSet(modules);
+            this.descriptors = Collections.unmodifiableSet(descriptors);
+            this.graph = graph; // no need to copy
+        }
+
+        Resolver resolver() {
+            return resolver;
+        }
+
+        Map<String, ModuleReference> copySelected() {
+            return new HashMap<>(nameToReference);
+        }
+
+        Set<ModuleReference> modules() {
+            return modules;
+        }
+
+        Set<ModuleDescriptor> descriptors() {
+            return descriptors;
+        }
+
+        ModuleReference findModule(String name) {
+            return nameToReference.get(name);
+        }
+
+        ModuleDescriptor findDescriptor(String name) {
+            ModuleReference mref = nameToReference.get(name);
+            return (mref != null) ? mref.descriptor() : null;
+        }
+
+        Set<ReadDependence> reads(ReadDependence rd) {
+            Set<ReadDependence> reads = graph.get(rd);
             if (reads == null) {
-                return null;
+                return Collections.emptySet();
             } else {
                 return Collections.unmodifiableSet(reads);
             }
         }
 
-        Set<ModuleDescriptor> provides(String sn) {
-            Set<ModuleDescriptor> provides = serviceToProviders.get(sn);
+
+        // maps a service type to the set of provider modules (created lazily)
+        private volatile Map<String, Set<ModuleDescriptor>> serviceToProviders;
+
+        /**
+         * Returns an immutable set of the module descriptors that provide
+         * one or more implementations of the given service type.
+         */
+        Set<ModuleDescriptor> provides(String st) {
+            Map<String, Set<ModuleDescriptor>> map = this.serviceToProviders;
+
+            // create the map if needed
+            if (map == null) {
+                map = new HashMap<>();
+                for (ModuleDescriptor descriptor : descriptors()) {
+                    Map<String, Provides> provides = descriptor.provides();
+                    for (String sn : provides.keySet()) {
+                        map.computeIfAbsent(sn, k -> new HashSet<>()).add(descriptor);
+                    }
+                }
+                this.serviceToProviders = map;
+            }
+
+            Set<ModuleDescriptor> provides = map.get(st);
             if (provides == null) {
                 return Collections.emptySet();
             } else {
                 return Collections.unmodifiableSet(provides);
             }
         }
+    }
 
-        /**
-         * Returns a new Resolution that this is this Resolution augmented with
-         * modules (located via the module reference finders) that are induced
-         * by service-use relationships.
-         */
-        Resolution bind() {
-            return Resolver.this.bind();
-        }
+
+    private final ModuleFinder beforeFinder;
+    private final Configuration parent;
+    private final ModuleFinder afterFinder;
+
+    Resolver(ModuleFinder beforeFinder,
+             Configuration parent,
+             ModuleFinder afterFinder)
+    {
+        this.beforeFinder = beforeFinder;
+        this.parent = parent;
+        this.afterFinder = afterFinder;
 
     }
 
-
-    private final ModuleFinder beforeFinder;
-    private final Layer layer;
-    private final ModuleFinder afterFinder;
-
-    // the set of module descriptors, added to at each iteration of resolve
-    private final Set<ModuleDescriptor> selected = new HashSet<>();
-
-    // map of module names to references
-    private final Map<String, ModuleReference> nameToReference = new HashMap<>();
-
-    // cached by resolve
-    private Map<ModuleDescriptor, Set<ModuleDescriptor>> cachedGraph;
-
-
-    private Resolver(ModuleFinder beforeFinder,
-                     Layer layer,
-                     ModuleFinder afterFinder)
-    {
-        this.beforeFinder = beforeFinder;
-        this.layer = layer;
-        this.afterFinder = afterFinder;
-    }
-
-
     /**
-     * Resolves the given named modules. Module dependences are resolved by
-     * locating them (in order) using the given {@code beforeFinder}, {@code
-     * layer}, and {@code afterFinder}.
+     * Resolves the given named modules, returning a Configuration that
+     * encapsulates the result.
      *
      * @throws ResolutionException
      */
-    static Resolution resolve(ModuleFinder beforeFinder,
-                              Layer layer,
-                              ModuleFinder afterFinder,
-                              Collection<String> roots)
-    {
-        Resolver resolver = new Resolver(beforeFinder, layer, afterFinder);
-        return resolver.resolve(roots);
-    }
-
-    /**
-     * Resolve the given collection of modules (by name).
-     */
-    private Resolution resolve(Collection<String> roots) {
+    Configuration resolve(Collection<String> roots) {
 
         long start = trace_start("Resolve");
 
+        Selected selected = new Selected(this);
+
         // create the visit stack to get us started
         Deque<ModuleDescriptor> q = new ArrayDeque<>();
         for (String root : roots) {
 
-            ModuleReference mref = find(beforeFinder, root);
+            // find root module
+            ModuleReference mref = findWithBeforeFinder(root);
             if (mref == null) {
-                // ## Does it make sense to attempt to locate root modules with
-                //    a finder other than the beforeFinder?
-                mref = find(afterFinder, root);
+                if (parent.findDescriptor(root).isPresent()) {
+                    // in parent, nothing to do
+                    continue;
+                }
+                mref = findWithAfterFinder(root);
                 if (mref == null) {
                     fail("Module %s not found", root);
                 }
@@ -200,124 +287,101 @@
                     trace("  (%s)", mref.location().get());
             }
 
-            nameToReference.put(root, mref);
+            selected.add(mref);
             q.push(mref.descriptor());
         }
 
-        resolve(q);
+        resolve(q, selected);
 
-        detectCycles();
-
-        checkHashes();
-
-        Map<ModuleDescriptor, Set<ModuleDescriptor>> graph = makeGraph();
-        cachedGraph = graph;
-
-        // As Layer.create will ensure that the boot Layer does not contain any
-        // duplicate packages (even concealed) then we can skip this check to
-        // reduce overhead at startup
-        //if (Layer.boot() != null)
-        checkExportSuppliers(graph);
-
-        Resolution r = new Resolution(selected,
-                                      nameToReference,
-                                      graph,
-                                      Collections.emptyMap());
+        Configuration cf = new Configuration(parent, selected);
 
         if (TRACE) {
             long duration = System.currentTimeMillis() - start;
-            Set<String> names
-                = selected.stream()
+            Set<String> names = cf.descriptors().stream()
                     .map(ModuleDescriptor::name)
                     .sorted()
                     .collect(Collectors.toSet());
-            trace("Resolve completed in %s ms", duration);
+            trace("Resolver completed in %s ms", duration);
             names.stream().sorted().forEach(name -> trace("  %s", name));
         }
 
-        return r;
+        return cf;
     }
 
+
     /**
      * Poll the given {@code Deque} for modules to resolve. On completion the
-     * {@code Deque} will be empty and the selected set will contain the modules
-     * that were selected.
+     * {@code Deque} will be empty and any selected modules will be added to
+     * the given Resolution.
      *
-     * @return The set of module (descriptors) selected by this invocation of
-     *         resolve
+     * @return The set of module selected by this invocation of resolve
      */
-    private Set<ModuleDescriptor> resolve(Deque<ModuleDescriptor> q) {
+    private Set<ModuleDescriptor> resolve(Deque<ModuleDescriptor> q,
+                                          Selected selected)
+    {
+
         Set<ModuleDescriptor> newlySelected = new HashSet<>();
 
         while (!q.isEmpty()) {
             ModuleDescriptor descriptor = q.poll();
-            assert nameToReference.containsKey(descriptor.name());
-            newlySelected.add(descriptor);
 
             // process dependences
             for (ModuleDescriptor.Requires requires : descriptor.requires()) {
                 String dn = requires.name();
 
-                // before finder
-                ModuleReference mref = find(beforeFinder, dn);
+                // find dependence
+                ModuleReference mref = findWithBeforeFinder(dn);
+                if (mref == null) {
+                    if (parent.findDescriptor(dn).isPresent())
+                        continue;
 
-                // already defined to the runtime
-                if (mref == null && layer.findModule(dn).isPresent()) {
-                    trace("Module %s found in parent Layer", dn);
-                    continue;
+                    mref = findWithAfterFinder(dn);
+                    if (mref == null) {
+                        fail("Module %s not found, required by %s",
+                                dn, descriptor.name());
+                    }
                 }
 
-                // after finder
-                if (mref == null) {
-                    mref = find(afterFinder, dn);
+                if (!selected.contains(dn)) {
+                    selected.add(mref);
+                    q.offer(mref.descriptor());
+                    newlySelected.add(mref.descriptor());
+
+                    if (TRACE) {
+                        trace("Module %s located, required by %s",
+                                dn, descriptor.name());
+                        if (mref.location().isPresent())
+                            trace("  (%s)", mref.location().get());
+                    }
                 }
 
-                // not found
-                if (mref == null) {
-                    fail("Module %s not found, required by %s",
-                        dn, descriptor.name());
-                }
-
-                // check if module descriptor has already been seen
-                ModuleDescriptor other = mref.descriptor();
-                if (!selected.contains(other) && !newlySelected.contains(other)) {
-
-                    if (nameToReference.put(dn, mref) == null) {
-                        if (TRACE) {
-                            trace("Module %s located, required by %s",
-                                dn, descriptor.name());
-                            if (mref.location().isPresent())
-                                trace("  (%s)", mref.location().get());
-                        }
-                    }
-
-                    newlySelected.add(other);
-
-                    q.offer(other);
-                }
             }
         }
 
-        // add the newly selected modules the selected set
-        selected.addAll(newlySelected);
-
         return newlySelected;
     }
 
+
     /**
-     * Updates the Resolver with modules (located via the module reference finders)
-     * that are induced by service-use relationships.
+     * Returns a configuration that is the given configuration augmented with
+     * modules induced by the service-use relation.
      */
-    private Resolution bind() {
+    Configuration bind(Configuration cf) {
 
         long start = trace_start("Bind");
 
-        // Scan the finders for all available service provider modules. As java.base
-        // uses services then all finders will need to be scanned anyway.
+        Result result = cf.result();
+
+        Selected selected = new Selected(result);
+
+        // Scan the finders for all available service provider modules. As
+        // java.base uses services then then module finders will be scanned
+        // anyway.
         Map<String, Set<ModuleReference>> availableProviders = new HashMap<>();
         for (ModuleReference mref : findAll()) {
             ModuleDescriptor descriptor = mref.descriptor();
             if (!descriptor.provides().isEmpty()) {
+
                 for (String sn : descriptor.provides().keySet()) {
                     // computeIfAbsent
                     Set<ModuleReference> providers = availableProviders.get(sn);
@@ -327,22 +391,21 @@
                     }
                     providers.add(mref);
                 }
+
             }
         }
 
-        int initialSize = selected.size();
-
         // create the visit stack
         Deque<ModuleDescriptor> q = new ArrayDeque<>();
 
         // the initial set of modules that may use services
-        Set<ModuleDescriptor> candidateConsumers;
-        if (layer == null) {
-            candidateConsumers = selected;
-        } else {
-            candidateConsumers = allModuleDescriptorsInLayer(layer);
-            candidateConsumers.addAll(selected);
+        Set<ModuleDescriptor> candidateConsumers = new HashSet<>();
+        Configuration p = parent;
+        while (p != null) {
+            candidateConsumers.addAll(p.descriptors());
+            p = p.parent().orElse(null);
         }
+        candidateConsumers.addAll(result.descriptors());
 
         // Where there is a consumer of a service then resolve all modules
         // that provide an implementation of that service
@@ -357,15 +420,15 @@
                                 if (!provider.equals(descriptor)) {
 
                                     trace("Module %s provides %s, used by %s",
-                                          provider.name(), service, descriptor.name());
+                                        provider.name(), service, descriptor.name());
 
-                                    if (!selected.contains(provider)) {
-                                        if (nameToReference.put(provider.name(), mref) == null) {
+                                    String pn = provider.name();
+                                    if (!selected.contains(pn)) {
 
-                                            if (TRACE && mref.location().isPresent())
-                                                trace("  (%s)", mref.location().get());
+                                        if (TRACE && mref.location().isPresent())
+                                            trace("  (%s)", mref.location().get());
 
-                                        }
+                                        selected.add(mref);
                                         q.push(provider);
                                     }
                                 }
@@ -375,291 +438,28 @@
                 }
             }
 
-            candidateConsumers = resolve(q);
+            candidateConsumers = resolve(q, selected);
 
         } while (!candidateConsumers.isEmpty());
 
-
-        // For debugging purposes, print out the service consumers in the
-        // selected set that use providers in a parent layer
-        if (TRACE) {
-            Set<ModuleDescriptor> allModules = allModuleDescriptorsInLayer(layer);
-            for (ModuleDescriptor descriptor : selected) {
-                if (!descriptor.uses().isEmpty()) {
-                    for (String service : descriptor.uses()) {
-                        for (ModuleDescriptor other : allModules) {
-                            if (other.provides().get(service) != null) {
-                                trace("Module %s provides %s, used by %s",
-                                        other.name(), service, descriptor.name());
-                            }
-                        }
-                    }
-                }
-            }
+        // if binding has increased the number of modules then we need to
+        // create a new Configuration
+        if (selected.count() > result.modules().size()) {
+            cf = new Configuration(parent, selected);
         }
 
-        Map<ModuleDescriptor, Set<ModuleDescriptor>> graph;
-
-        // If the number of selected modules has increased then the post
-        // resolution checks need to be repeated and the readability graph
-        // re-generated.
-        if (selected.size() > initialSize) {
-
-            detectCycles();
-
-            checkHashes();
-
-            graph = makeGraph();
-
-            checkExportSuppliers(graph);
-
-        } else {
-            graph = cachedGraph;
-        }
-
-        // Finally create the map of service -> provider modules
-        Map<String, Set<ModuleDescriptor>> serviceToProviders = new HashMap<>();
-        for (ModuleDescriptor descriptor : selected) {
-            Map<String, Provides> provides = descriptor.provides();
-            for (Map.Entry<String, Provides> entry : provides.entrySet()) {
-                String sn = entry.getKey();
-
-                // computeIfAbsent
-                Set<ModuleDescriptor> providers = serviceToProviders.get(sn);
-                if (providers == null) {
-                    providers = new HashSet<>();
-                    serviceToProviders.put(sn, providers);
-                }
-                providers.add(descriptor);
-
-            }
-        }
-
-        Resolution r = new Resolution(selected,
-                                      nameToReference,
-                                      graph,
-                                      serviceToProviders);
-
         if (TRACE) {
             long duration = System.currentTimeMillis() - start;
-            Set<String> names = selected.stream()
-                .map(ModuleDescriptor::name)
-                .sorted()
-                .collect(Collectors.toSet());
+            Set<String> names = selected.modules().stream()
+                    .map(ModuleReference::descriptor)
+                    .map(ModuleDescriptor::name)
+                    .sorted()
+                    .collect(Collectors.toSet());
             trace("Bind completed in %s ms", duration);
             names.stream().sorted().forEach(name -> trace("  %s", name));
         }
 
-        return r;
-    }
-
-    /**
-     * Returns the set of module descriptors in this layer and all parent
-     * layers. There may be several modules with the same name in the returned
-     * set.
-     */
-    private static Set<ModuleDescriptor> allModuleDescriptorsInLayer(Layer l) {
-        Set<ModuleDescriptor> result = new HashSet<>();
-        Optional<Layer> ol = l.parent();
-        if (ol.isPresent())
-            result.addAll( allModuleDescriptorsInLayer(ol.get()) );
-        Optional<Configuration> ocf = l.configuration();
-        if (ocf.isPresent())
-            result.addAll(ocf.get().descriptors());
-        return result;
-    }
-
-
-
-    /**
-     * Computes and sets the readability graph for the modules in the given
-     * Resolution object.
-     *
-     * The readability graph is created by propagating "requires" through the
-     * "public requires" edges of the module dependence graph. So if the module
-     * dependence graph has m1 requires m2 && m2 requires public m3 then the
-     * resulting readability graph will contain m1 reads m2, m1
-     * reads m3, and m2 reads m3.
-     */
-    private Map<ModuleDescriptor, Set<ModuleDescriptor>> makeGraph() {
-
-        // name -> ModuleDescriptor lookup
-        int size = selected.size();
-        Map<String, ModuleDescriptor> nameToDescriptor = new HashMap<>(size);
-        for (ModuleDescriptor d : selected) {
-            nameToDescriptor.put(d.name(), d);
-        }
-
-        // the "reads" graph starts as a module dependence graph and
-        // is iteratively updated to be the readability graph
-        Map<ModuleDescriptor, Set<ModuleDescriptor>> g1 = new HashMap<>();
-
-        // the "requires public" graph, contains requires public edges only
-        Map<ModuleDescriptor, Set<ModuleDescriptor>> g2 = new HashMap<>();
-
-        // need "requires public" from the modules in parent layers as
-        // there may be selected modules that have a dependence.
-        Layer l = this.layer;
-        while (l != null) {
-            Configuration cf = l.configuration().orElse(null);
-            if (cf != null) {
-                for (ModuleDescriptor descriptor : cf.descriptors()) {
-                    Set<ModuleDescriptor> requiresPublic = new HashSet<>();
-                    g2.put(descriptor, requiresPublic);
-
-                    for (Requires d : descriptor.requires()) {
-                        if (d.modifiers().contains(Requires.Modifier.PUBLIC)) {
-                            String dn = d.name();
-                            ModuleReference mref = findInLayer(l, dn);
-                            if (mref == null)
-                                throw new InternalError(dn + " not found");
-                            requiresPublic.add(mref.descriptor());
-                        }
-                    }
-                }
-            }
-            l = l.parent().orElse(null);
-        }
-
-        // add the module dependence edges from the newly selected modules
-        for (ModuleDescriptor m : selected) {
-
-            Set<ModuleDescriptor> reads = new HashSet<>();
-            g1.put(m, reads);
-
-            Set<ModuleDescriptor> requiresPublic = new HashSet<>();
-            g2.put(m, requiresPublic);
-
-            for (Requires d: m.requires()) {
-                String dn = d.name();
-                ModuleDescriptor other = nameToDescriptor.get(dn);
-                if (other == null && layer != null) {
-                    ModuleReference mref = findInLayer(layer, dn);
-                    if (mref != null)
-                        other = mref.descriptor();
-                }
-
-                if (other == null)
-                    throw new InternalError(dn + " not found??");
-
-                // m requires other => m reads other
-                reads.add(other);
-
-                // m requires public other
-                if (d.modifiers().contains(Requires.Modifier.PUBLIC)) {
-                    requiresPublic.add(other);
-                }
-            }
-
-            // if m is an automatic module then it requires public all
-            // selected module and all (non-shadowed) modules in parent layers
-            if (m.isAutomatic()) {
-                for (ModuleDescriptor other : nameToDescriptor.values()) {
-                    if (!other.equals(m)) {
-                        reads.add(other);
-                        requiresPublic.add(other);
-                    }
-                }
-
-                l = this.layer;
-                while (l != null) {
-                    Configuration cf = layer.configuration().orElse(null);
-                    if (cf != null) {
-                        for (ModuleDescriptor other : cf.descriptors()) {
-                            String name = other.name();
-                            if (nameToDescriptor.putIfAbsent(name, other) == null) {
-                                reads.add(other);
-                                requiresPublic.add(other);
-                            }
-                        }
-                    }
-                    l = l.parent().orElse(null);
-                }
-            }
-
-        }
-
-        // add to g1 until there are no more requires public to propagate
-        boolean changed;
-        Map<ModuleDescriptor, Set<ModuleDescriptor>> changes = new HashMap<>();
-        do {
-            changed = false;
-            for (Map.Entry<ModuleDescriptor, Set<ModuleDescriptor>> e : g1.entrySet()) {
-                ModuleDescriptor m1 = e.getKey();
-                Set<ModuleDescriptor> m1Reads = e.getValue();
-                for (ModuleDescriptor m2: m1Reads) {
-                    Set<ModuleDescriptor> m2RequiresPublic = g2.get(m2);
-                    for (ModuleDescriptor m3 : m2RequiresPublic) {
-                        if (!m1Reads.contains(m3)) {
-
-                            // computeIfAbsent
-                            Set<ModuleDescriptor> s = changes.get(m1);
-                            if (s == null) {
-                                s = new HashSet<>();
-                                changes.put(m1, s);
-                            }
-                            s.add(m3);
-
-                            changed = true;
-                        }
-                    }
-                }
-            }
-            if (changed) {
-                for (Entry<ModuleDescriptor, Set<ModuleDescriptor>> e : changes.entrySet()) {
-                    ModuleDescriptor m1 = e.getKey();
-                    g1.get(m1).addAll(e.getValue());
-                }
-                changes.clear();
-            }
-
-        } while (changed);
-
-        return g1;
-    }
-
-
-    /**
-     * Checks the hashes in the extended module descriptor to ensure that they
-     * match the hash of the dependency's module reference.
-     */
-    private void checkHashes() {
-
-        for (ModuleDescriptor descriptor : selected) {
-            String mn = descriptor.name();
-
-            // get map of module names to hash
-            Optional<DependencyHashes> ohashes
-                = nameToReference.get(mn).descriptor().hashes();
-            if (!ohashes.isPresent())
-                continue;
-            DependencyHashes hashes = ohashes.get();
-
-            // check dependences
-            for (Requires md: descriptor.requires()) {
-                String dn = md.name();
-                String recordedHash = hashes.hashFor(dn);
-
-                if (recordedHash != null) {
-                    ModuleReference mref = nameToReference.get(dn);
-                    if (mref == null)
-                        mref = findInLayer(layer, dn);
-
-                    if (mref == null)
-                        throw new InternalError(dn + " not found");
-
-                    String actualHash = mref.computeHash(hashes.algorithm());
-                    if (actualHash == null)
-                        fail("Unable to compute the hash of module %s", dn);
-
-                    if (!recordedHash.equals(actualHash)) {
-                        fail("Hash of %s (%s) differs to expected hash (%s)",
-                                dn, actualHash, recordedHash);
-                    }
-                }
-            }
-        }
-
+        return cf;
     }
 
 
@@ -667,14 +467,14 @@
      * Checks the given module graph for cycles.
      *
      * For now the implementation is a simple depth first search on the
-     * dependency graph. We'll replace this later, maybe with Tarjan if we
-     * are also checking connectedness.
+     * dependency graph. We'll replace this later, maybe with Tarjan.
      */
-    private void detectCycles() {
+    private void detectCycles(Selected selected ) {
         visited = new HashSet<>();
         visitPath = new LinkedHashSet<>(); // preserve insertion order
-        for (ModuleDescriptor descriptor : selected) {
-            visit(descriptor);
+        for (ModuleReference mref : selected.modules()) {
+            ModuleDescriptor descriptor = mref.descriptor();
+            visit(selected, descriptor);
         }
     }
 
@@ -684,21 +484,20 @@
     // the modules in the current visit path
     private Set<ModuleDescriptor> visitPath;
 
-    private void visit(ModuleDescriptor descriptor) {
+    private void visit(Selected selected, ModuleDescriptor descriptor) {
         if (!visited.contains(descriptor)) {
             boolean added = visitPath.add(descriptor);
             if (!added) {
                 throw new ResolutionException("Cycle detected: " +
-                                              cycleAsString(descriptor));
+                        cycleAsString(descriptor));
             }
-            for (Requires requires : descriptor.requires()) {
-                ModuleReference mref = nameToReference.get(requires.name());
-                if (mref != null) {
+            for (ModuleDescriptor.Requires requires : descriptor.requires()) {
+                String dn = requires.name();
+                ModuleDescriptor other = selected.findDescriptor(dn);
+                if (other != null && other != descriptor) {
                     // dependency is in this configuration
-                    ModuleDescriptor other = mref.descriptor();
-                    // ignore self reference
                     if (other != descriptor)
-                        visit(other);
+                        visit(selected, other);
                 }
             }
             visitPath.remove(descriptor);
@@ -714,89 +513,221 @@
         list.add(descriptor);
         int index = list.indexOf(descriptor);
         return list.stream()
-                   .skip(index)
-                   .map(ModuleDescriptor::name)
-                   .collect(Collectors.joining(" -> "));
+                .skip(index)
+                .map(ModuleDescriptor::name)
+                .collect(Collectors.joining(" -> "));
     }
 
 
+    /**
+     * Checks the hashes in the module descriptor to ensure that they match
+     * the hash of the dependency's module reference.
+     */
+    private void checkHashes(Selected selected) {
+
+        for (ModuleReference mref : selected.modules()) {
+            ModuleDescriptor descriptor = mref.descriptor();
+
+            // get map of module names to hash
+            Optional<DependencyHashes> ohashes = descriptor.hashes();
+            if (!ohashes.isPresent())
+                continue;
+            DependencyHashes hashes = ohashes.get();
+
+            // check dependences
+            for (ModuleDescriptor.Requires d : descriptor.requires()) {
+                String dn = d.name();
+                String recordedHash = hashes.hashFor(dn);
+
+                if (recordedHash != null) {
+
+                    ModuleReference other = selected.findModule(dn);
+                    if (other == null)
+                        other = parent.findModule(dn).orElse(null);
+                    if (other == null)
+                        throw new InternalError(dn + " not found");
+
+                    String actualHash = other.computeHash(hashes.algorithm());
+                    if (actualHash == null)
+                        fail("Unable to compute the hash of module %s", dn);
+
+                    if (!recordedHash.equals(actualHash)) {
+                        fail("Hash of %s (%s) differs to expected hash (%s)",
+                                dn, actualHash, recordedHash);
+                    }
+
+                }
+
+            }
+        }
+
+    }
+
 
     /**
-     * Returns true if a module of the given module's name is in a parent Layer
+     * Computes and sets the readability graph for the modules in the given
+     * Resolution object.
+     *
+     * The readability graph is created by propagating "requires" through the
+     * "public requires" edges of the module dependence graph. So if the module
+     * dependence graph has m1 requires m2 && m2 requires public m3 then the
+     * resulting readability graph will contain m1 reads m2, m1
+     * reads m3, and m2 reads m3.
+     *
+     * TODO: Use a more efficient algorithm, maybe cache the requires public
+     *       in parent configurations.
      */
-    private boolean inParentLayer(ModuleReference mref) {
-        return layer.findModule(mref.descriptor().name()).isPresent();
-    }
+    private Map<ReadDependence, Set<ReadDependence>> makeGraph(Selected selected,
+                                                               Configuration cf) {
 
-    /**
-     * Invokes the finder's find method to find the given module.
-     */
-    private static ModuleReference find(ModuleFinder finder, String mn) {
-        try {
-            return finder.find(mn).orElse(null);
-        } catch (FindException e) {
-            // unwrap
-            throw new ResolutionException(e.getMessage(), e.getCause());
+        // the "reads" graph starts as a module dependence graph and
+        // is iteratively updated to be the readability graph
+        Map<ReadDependence, Set<ReadDependence>> g1 = new HashMap<>();
+
+        // the "requires public" graph, contains requires public edges only
+        Map<ReadDependence, Set<ReadDependence>> g2 = new HashMap<>();
+
+
+        // need "requires public" from the modules in parent configurations as
+        // there may be selected modules that have a dependency on modules in
+        // the parent configuration.
+
+        Configuration p = parent;
+        while (p != null) {
+            for (ModuleDescriptor descriptor : p.descriptors()) {
+                ReadDependence x = p.findReadDependence(descriptor.name());
+                for (ModuleDescriptor.Requires d : descriptor.requires()) {
+                    if (d.modifiers().contains(Modifier.PUBLIC)) {
+                        String dn = d.name();
+                        ReadDependence y = p.findReadDependence(dn);
+                        if (y == null)
+                            throw new InternalError(dn + " not found");
+                        g2.computeIfAbsent(x, k -> new HashSet<>()).add(y);
+                    }
+                }
+            }
+            p = p.parent().orElse(null);
         }
-    }
 
-    /**
-     * Returns the set of all modules.
-     */
-    private Set<ModuleReference> findAll() {
-        try {
+        // populate g1 and g2 with the dependences from the selected modules
 
-            // only one source of modules?
-            Set<ModuleReference> preModules = beforeFinder.findAll();
-            Set<ModuleReference> postModules = afterFinder.findAll();
-            if (layer == Layer.empty()) {
-                if (preModules.isEmpty())
-                    return postModules;
-                if (postModules.isEmpty())
-                   return preModules;
+        for (ModuleReference mref : selected.modules()) {
+            ModuleDescriptor descriptor = mref.descriptor();
+            ReadDependence x = new ReadDependence(cf, descriptor);
+
+            Set<ReadDependence> reads = new HashSet<>();
+            g1.put(x, reads);
+
+            Set<ReadDependence> requiresPublic = new HashSet<>();
+            g2.put(x, requiresPublic);
+
+            for (ModuleDescriptor.Requires d : descriptor.requires()) {
+                String dn = d.name();
+
+                ReadDependence y;
+                ModuleDescriptor other = selected.findDescriptor(dn);
+                if (other != null) {
+                    y = new ReadDependence(cf, other);
+                } else {
+                    y = parent.findReadDependence(dn);
+                    if (y == null)
+                        throw new InternalError("unable to find " + dn);
+                }
+
+                // m requires other => m reads other
+                reads.add(y);
+
+                // m requires public other
+                if (d.modifiers().contains(Modifier.PUBLIC)) {
+                    requiresPublic.add(y);
+                }
+
             }
 
-            Stream<ModuleReference> s1 = preModules.stream();
-            Stream<ModuleReference> s2
-                = postModules.stream().filter(m -> !inParentLayer(m));
-            return Stream.concat(s1, s2).collect(Collectors.toSet());
+            // if m is an automatic module then it requires public all selected
+            // modules and all modules in parent layers
+            if (descriptor.isAutomatic()) {
+                String name = descriptor.name();
 
-        } catch (FindException e) {
-            // unwrap
-            throw new ResolutionException(e.getMessage(), e.getCause());
+                // requires public all selected modules
+                for (ModuleReference mref2 : selected.modules()) {
+                    ModuleDescriptor other = mref2.descriptor();
+                    if (!name.equals(other.name())) {
+                        ReadDependence rd = new ReadDependence(cf, other);
+                        reads.add(rd);
+                        requiresPublic.add(rd);
+                    }
+                }
+
+                // requires public all modules in parent configurations
+                p = parent;
+                while (p != null) {
+                    for (ModuleDescriptor other : p.descriptors()) {
+                        ReadDependence rd = new ReadDependence(p, other);
+                        reads.add(rd);
+                        requiresPublic.add(rd);
+                    }
+                    p = p.parent().orElse(null);
+                }
+
+            }
+
         }
+
+        // Iteratively update g1 until there are no more requires public to propagate
+        boolean changed;
+        Map<ReadDependence, Set<ReadDependence>> changes = new HashMap<>();
+        do {
+            changed = false;
+            for (Entry<ReadDependence, Set<ReadDependence>> entry : g1.entrySet()) {
+
+                ReadDependence d1 = entry.getKey();
+                Set<ReadDependence> d1Reads = entry.getValue();
+
+                for (ReadDependence d2 : d1Reads) {
+                    Set<ReadDependence> d2RequiresPublic = g2.get(d2);
+                    if (d2RequiresPublic != null) {
+                        for (ReadDependence d3 : d2RequiresPublic) {
+                            if (!d1Reads.contains(d3)) {
+
+                                // computeIfAbsent
+                                Set<ReadDependence> s = changes.get(d1);
+                                if (s == null) {
+                                    s = new HashSet<>();
+                                    changes.put(d1, s);
+                                }
+                                s.add(d3);
+                                changed = true;
+
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (changed) {
+                for (Entry<ReadDependence, Set<ReadDependence>> e : changes.entrySet()) {
+                    ReadDependence d = e.getKey();
+                    g1.get(d).addAll(e.getValue());
+                }
+                changes.clear();
+            }
+
+        } while (changed);
+
+        return g1;
     }
 
-    /**
-     * Returns the {@code ModuleReference} that was used to define the module
-     * with the given name.  If a module of the given name is not in the layer
-     * then the parent layer is searched.
-     */
-    private static ModuleReference findInLayer(Layer layer, String name) {
-        Optional<Configuration> ocf = layer.configuration();
-        if (!ocf.isPresent())
-            return null;
-
-        Optional<ModuleReference> omref = ocf.get().findModule(name);
-        if (omref.isPresent()) {
-            return omref.get();
-        } else {
-            return findInLayer(layer.parent().get(), name);
-        }
-    }
-
-
 
     /**
      * Checks the readability graph to ensure that no two modules export the
      * same package to a module. This includes the case where module M has
      * a local package P and M reads another module that exports P to M.
      */
-    private static void
-    checkExportSuppliers(Map<ModuleDescriptor, Set<ModuleDescriptor>> graph) {
+    private void checkExportSuppliers(Map<ReadDependence, Set<ReadDependence>> graph) {
 
-        for (Map.Entry<ModuleDescriptor, Set<ModuleDescriptor>> e : graph.entrySet()) {
-            ModuleDescriptor descriptor1 = e.getKey();
+        for (Map.Entry<ReadDependence, Set<ReadDependence>> e : graph.entrySet()) {
+            ModuleDescriptor descriptor1 = e.getKey().descriptor();
 
             // the map of packages that are local or exported to descriptor1
             Map<String, ModuleDescriptor> packageToExporter = new HashMap<>();
@@ -807,8 +738,9 @@
             }
 
             // descriptor1 reads descriptor2
-            Set<ModuleDescriptor> reads = e.getValue();
-            for (ModuleDescriptor descriptor2 : reads) {
+            Set<ReadDependence> reads = e.getValue();
+            for (ReadDependence endpoint : reads) {
+                ModuleDescriptor descriptor2 = endpoint.descriptor();
 
                 for (ModuleDescriptor.Exports export : descriptor2.exports()) {
 
@@ -821,13 +753,13 @@
                     // source is exported to descriptor2
                     String source = export.source();
                     ModuleDescriptor other
-                        = packageToExporter.put(source, descriptor2);
+                            = packageToExporter.put(source, descriptor2);
 
                     if (other != null && other != descriptor2) {
                         // package might be local to descriptor1
                         if (other == descriptor1) {
                             fail("Module %s contains package %s"
-                                  + ", module %s exports package %s to %s",
+                                 + ", module %s exports package %s to %s",
                                     descriptor1.name(),
                                     source,
                                     descriptor2.name(),
@@ -851,6 +783,72 @@
 
 
     /**
+     * Invokes the beforeFinder to find method to find the given module.
+     */
+    private ModuleReference findWithBeforeFinder(String mn) {
+        try {
+            return beforeFinder.find(mn).orElse(null);
+        } catch (FindException e) {
+            // unwrap
+            throw new ResolutionException(e.getMessage(), e.getCause());
+        }
+    }
+
+    /**
+     * Invokes the afterFinder to find method to find the given module.
+     */
+    private ModuleReference findWithAfterFinder(String mn) {
+        try {
+            return afterFinder.find(mn).orElse(null);
+        } catch (FindException e) {
+            // unwrap
+            throw new ResolutionException(e.getMessage(), e.getCause());
+        }
+    }
+
+    /**
+     * Returns the set of all modules that are observable with the before
+     * and after ModuleFinders.
+     */
+    private Set<ModuleReference> findAll() {
+        try {
+
+            Set<ModuleReference> beforeModules = beforeFinder.findAll();
+            Set<ModuleReference> afterModules = afterFinder.findAll();
+
+            if (afterModules.isEmpty())
+                return beforeModules;
+
+            if (beforeModules.isEmpty() && parent == Configuration.empty())
+                return afterModules;
+
+            Set<ModuleReference> result = new HashSet<>(beforeModules);
+            for (ModuleReference mref : afterModules) {
+                String name = mref.descriptor().name();
+                if (!beforeFinder.find(name).isPresent()
+                        && !parent.findDescriptor(name).isPresent())
+                    result.add(mref);
+            }
+
+            return result;
+
+        } catch (FindException e) {
+            // unwrap
+            throw new ResolutionException(e.getMessage(), e.getCause());
+        }
+    }
+
+
+    /**
+     * Throw ResolutionException with the given format string and arguments
+     */
+    private static void fail(String fmt, Object ... args) {
+        String msg = String.format(fmt, args);
+        throw new ResolutionException(msg);
+    }
+
+
+    /**
      * Tracing support, limited to boot layer for now.
      */
 
@@ -873,10 +871,4 @@
         }
     }
 
-
-    private static void fail(String fmt, Object ... args) {
-        String msg = String.format(fmt, args);
-        throw new ResolutionException(msg);
-    }
-
 }
--- a/src/java.base/share/classes/java/lang/reflect/Layer.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/src/java.base/share/classes/java/lang/reflect/Layer.java	Fri Nov 27 15:34:17 2015 +0000
@@ -43,23 +43,24 @@
 /**
  * Represents a layer of modules in the Java virtual machine.
  *
- * <p> The following example resolves a module named <em>myapp</em>. It then
- * instantiates the {@link Configuration} as a {@code Layer}. In the example
- * then all modules in the configuration are defined to the same class loader.
- * </p>
+ * <p> The following example invokes the {@code resolve} method to resolve a
+ * module named <em>myapp</em> with the {@link Configuration} for the boot
+ * layer as the parent configuration. It then instantiates the configuration
+ * as a {@code Layer}. In the example then all modules in the configuration
+ * are defined to the same class loader. </p>
  *
  * <pre>{@code
  *     ModuleFinder finder = ModuleFinder.of(dir1, dir2, dir3);
  *
  *     Configuration cf
- *         = Configuration.resolve(ModuleFinder.empty(),
- *                                 Layer.boot(),
- *                                 finder,
+ *         = Configuration.resolve(finder,
+ *                                 Layer.boot().configuration(),
+ *                                 ModuleFinder.empty(),
  *                                 "myapp");
  *
  *     ClassLoader loader = new ModuleClassLoader(cf);
  *
- *     Layer layer = Layer.create(cf, mn -> loader);
+ *     Layer layer = Layer.create(cf, Layer.boot(), mn -> loader);
  *
  *     Class<?> c = layer.findLoader("myapp").loadClass("app.Main");
  * }</pre>
@@ -96,11 +97,15 @@
 
 
     // the empty Layer
-    private static final Layer EMPTY_LAYER = new Layer(null, null);
+    private static final Layer EMPTY_LAYER
+        = new Layer(Configuration.empty(), null, null);
 
     // the configuration from which this Layer was created
     private final Configuration cf;
 
+    // parent layer, null in the case of the empty layer
+    private final Layer parent;
+
     // maps module name to jlr.Module
     private final Map<String, Module> nameToModule;
 
@@ -108,23 +113,27 @@
     /**
      * Creates a new Layer from the modules in the given configuration.
      */
-    private Layer(Configuration cf, ClassLoaderFinder clf) {
+    private Layer(Configuration cf, Layer parent, ClassLoaderFinder clf) {
+
+        this.cf = cf;
+        this.parent = parent;
 
         Map<String, Module> map;
-        if (cf == null) {
+        if (parent == null) {
             map = Collections.emptyMap();
         } else {
             map = Module.defineModules(cf, clf, this);
         }
-
-        this.cf = cf;
         this.nameToModule = map; // no need to do defensive copy
     }
 
 
     /**
-     * Creates a {@code Layer} by defining the modules, as described in the
-     * given {@code Configuration}, to the Java virtual machine.
+     * Creates a {@code Layer} by defining the modules in the given {@code
+     * Configuration} to the Java virtual machine.
+     * The {@link Configuration#parent() parent} of the given configuration is
+     * the configuration that was to used to create the {@link Layer#parent()
+     * parent layer}.
      *
      * <p> Modules are mapped to module-capable class loaders by means of the
      * given {@code ClassLoaderFinder} and defined to the Java virtual machine.
@@ -157,27 +166,42 @@
      * modules in the configuration defined to the run-time.
      *
      * @param  cf
-     *         The configuration to instantiate
+     *         The configuration to instantiate as a layer
+     * @param  parent
+     *         The parent layer
      * @param  clf
      *         The {@code ClassLoaderFinder} to map modules to class loaders
      *
      * @return The newly created layer
      *
+     * @throws IllegalArgumentException
+     *         If the parent of the given configuration is not the configuration
+     *         of the parent {@code Layer}
      * @throws LayerInstantiationException
      *         If creating the {@code Layer} fails for any of the reasons
      *         listed above
      * @throws SecurityException
      *         If denied by the security manager
      */
-    public static Layer create(Configuration cf, ClassLoaderFinder clf) {
+    public static Layer create(Configuration cf, Layer parent, ClassLoaderFinder clf) {
         Objects.requireNonNull(cf);
+        Objects.requireNonNull(parent);
         Objects.requireNonNull(clf);
 
+        // The configuration parent and the configuration for the parent layer
+        // must be the same
+        Optional<Configuration> oparent = cf.parent();
+        if (!oparent.isPresent() || oparent.get() != parent.configuration()) {
+            throw new IllegalArgumentException(
+                    "Parent of configuration != configuration of parent Layer");
+        }
+
         SecurityManager sm = System.getSecurityManager();
         if (sm != null) {
             sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
         }
 
+
         // For now, no two modules in the boot Layer may contain the same
         // package so we use a simple check for the boot Layer to keep
         // the overhead at startup to a minimum
@@ -188,7 +212,7 @@
         }
 
         try {
-            return new Layer(cf, clf);
+            return new Layer(cf, parent, clf);
         } catch (IllegalArgumentException iae) {
             // IAE is thrown by VM when defining the module fails
             throw new LayerInstantiationException(iae.getMessage());
@@ -255,13 +279,12 @@
 
 
     /**
-     * Returns the {@code Configuration} used to create this layer unless this
-     * is the {@linkplain #empty empty layer}, which has no configuration.
+     * Returns the {@code Configuration} used to create this layer.
      *
      * @return The configuration used to create this layer
      */
-    public Optional<Configuration> configuration() {
-        return Optional.ofNullable(cf);
+    public Configuration configuration() {
+        return cf;
     }
 
 
@@ -272,21 +295,18 @@
      * @return This layer's parent
      */
     public Optional<Layer> parent() {
-        if (cf == null) {
-            return Optional.empty();
-        } else {
-            return Optional.of(cf.layer());
-        }
+        return Optional.ofNullable(parent);
     }
 
 
     /**
      * Returns a set of the {@code Module}s in this layer.
      *
-     * @return The set of modules in this layer
+     * @return A possibly-empty unmodifiable set of the modules in this layer
      */
     public Set<Module> modules() {
-        return nameToModule.values().stream().collect(Collectors.toSet());
+        return Collections.unmodifiableSet(
+                nameToModule.values().stream().collect(Collectors.toSet()));
     }
 
 
@@ -328,12 +348,12 @@
      * @return The ClassLoader that the module is defined to
      *
      * @throws IllegalArgumentException if a module of the given name is not
-     * defined in this layer or any parent of this layer
+     *         defined in this layer or any parent of this layer
      *
      * @throws SecurityException if denied by the security manager
      */
     public ClassLoader findLoader(String name) {
-        Module m = nameToModule.get(name);
+        Module m = nameToModule.get(Objects.requireNonNull(name));
         if (m != null)
             return m.getClassLoader();
         Optional<Layer> ol = parent();
--- a/src/java.base/share/classes/java/lang/reflect/Module.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/src/java.base/share/classes/java/lang/reflect/Module.java	Fri Nov 27 15:34:17 2015 +0000
@@ -28,6 +28,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.module.Configuration;
+import java.lang.module.Configuration.ReadDependence;
 import java.lang.module.ModuleReference;
 import java.lang.module.ModuleDescriptor;
 import java.lang.module.ModuleDescriptor.Exports;
@@ -849,8 +850,28 @@
     }
 
 
+    // -- creating Module objects --
 
-    // -- creating Module objects --
+    /**
+     * Find the runtime Module corresponding to the given ReadDependence
+     * in the given parent Layer (or its parents).
+     */
+    private static Module find(ReadDependence d, Layer layer) {
+        Configuration cf = d.configuration();
+        String dn = d.descriptor().name();
+
+        Module m = null;
+        while (layer != null) {
+            if (layer.configuration() == cf) {
+                Optional<Module> om = layer.findModule(dn);
+                m = om.get();
+                assert m.getLayer() == layer;
+                break;
+            }
+            layer = layer.parent().orElse(null);
+        }
+        return m;
+    }
 
     /**
      * Defines each of the module in the given configuration to the runtime.
@@ -886,42 +907,24 @@
         }
 
         // setup readability and exports
-        for (ModuleDescriptor descriptor: cf.descriptors()) {
-            Module m = modules.get(descriptor.name());
+        for (ModuleDescriptor descriptor : cf.descriptors()) {
+            String mn = descriptor.name();
+            Module m = modules.get(mn);
             assert m != null;
 
             // reads
             Set<Module> reads = new HashSet<>();
-            for (ModuleDescriptor other : cf.reads(descriptor)) {
-                Module m2 = null;
+            for (ReadDependence d : cf.reads(descriptor)) {
 
-                // Search the configuration and parent layers for the module
-                // descriptor. This is temporary until Configuration defines
-                // an API to return the layer + name of the source rather than
-                // the module descriptor.
-                String dn = other.name();
-                Module candidate = modules.get(dn);
-                if (candidate != null && other.equals(candidate.getDescriptor())) {
-                    m2 = candidate;
+                Module m2;
+                if (d.configuration() == cf) {
+                    String dn = d.descriptor().name();
+                    m2 = modules.get(dn);
+                    assert m2 != null;
                 } else {
-                    Layer parent = cf.layer();
-                    while (parent != null) {
-                        Optional<Module> om = parent.findModule(dn);
-                        if (om.isPresent()) {
-                            candidate = om.get();
-                            if (other.equals(candidate.getDescriptor())) {
-                                m2 = candidate;
-                                break;
-                            }
-                        }
-                        parent = parent.parent().orElse(null);
-                    }
+                    m2 = find(d, layer.parent().orElse(null));
                 }
 
-                if (m2 == null) {
-                    throw new InternalError(descriptor.name() +
-                            " reads unknown module: " + other.name());
-                }
                 reads.add(m2);
 
                 // update VM view
--- a/src/java.base/share/classes/java/util/ServiceLoader.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/src/java.base/share/classes/java/util/ServiceLoader.java	Fri Nov 27 15:34:17 2015 +0000
@@ -571,10 +571,8 @@
             currentLayer = layer;
 
             // need to get us started
-            Configuration cf = layer.configuration().orElse(null);
-            if (cf != null) {
-                descriptorIterator = cf.provides(serviceName).iterator();
-            }
+            Configuration cf = layer.configuration();
+            descriptorIterator = cf.provides(serviceName).iterator();
         }
 
         @Override
@@ -593,7 +591,7 @@
                 }
 
                 // next descriptor
-                if (descriptorIterator != null && descriptorIterator.hasNext()) {
+                if (descriptorIterator.hasNext()) {
                     ModuleDescriptor descriptor = descriptorIterator.next();
 
                     nextModule = currentLayer.findModule(descriptor.name()).get();
@@ -609,13 +607,8 @@
                 if (parent == null)
                     return false;
 
-                // if the current layer doesn't have a configuration then it means
-                // we've hit the empty layer
                 currentLayer = parent;
-                Configuration cf = currentLayer.configuration().orElse(null);
-                if (cf == null)
-                    return false;
-
+                Configuration cf = currentLayer.configuration();
                 descriptorIterator = cf.provides(service.getName()).iterator();
             }
         }
--- a/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java	Fri Nov 27 15:34:17 2015 +0000
@@ -176,10 +176,10 @@
 
         // run the resolver to create the configuration
         Configuration cf = (Configuration.resolve(finder,
-                                                  Layer.empty(),
+                                                  Configuration.empty(),
                                                   ModuleFinder.empty(),
-                                                  roots)
-                            .bind());
+                                                  roots))
+                            .bind();
 
         // time to create configuration
         PerfCounters.configTime.addElapsedTimeFrom(t1);
@@ -208,7 +208,7 @@
         long t2 = System.nanoTime();
 
         // define modules to VM/runtime
-        Layer bootLayer = Layer.create(cf, clf);
+        Layer bootLayer = Layer.create(cf, Layer.empty(), clf);
 
         // define the module to its class loader, except java.base
         for (ModuleReference mref : cf.modules()) {
@@ -251,9 +251,9 @@
     {
         // resolve all root modules
         Configuration cf = Configuration.resolve(finder,
-                Layer.empty(),
-                ModuleFinder.empty(),
-                roots);
+                                                 Configuration.empty(),
+                                                 ModuleFinder.empty(),
+                                                 roots);
 
         // module name -> reference
         Map<String, ModuleReference> map = new HashMap<>();
--- a/src/java.base/share/classes/sun/launcher/LauncherHelper.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/src/java.base/share/classes/sun/launcher/LauncherHelper.java	Fri Nov 27 15:34:17 2015 +0000
@@ -910,7 +910,7 @@
         if (layer == null)
             return;
 
-        Configuration cf = layer.configuration().get();
+        Configuration cf = layer.configuration();
         int colon = optionFlag.indexOf(':');
         if (colon == -1) {
             cf.modules().stream()
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/JlinkTask.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/JlinkTask.java	Fri Nov 27 15:34:17 2015 +0000
@@ -425,11 +425,10 @@
         if (addMods.isEmpty()) {
             throw new IllegalArgumentException("empty modules and limitmods");
         }
-        Configuration cf
-                = Configuration.resolve(finder,
-                        Layer.empty(),
-                        ModuleFinder.empty(),
-                        addMods);
+        Configuration cf = Configuration.resolve(finder,
+                Configuration.empty(),
+                ModuleFinder.empty(),
+                addMods);
         Map<String, Path> mods = modulesToPath(finder, cf.descriptors());
         return new ImageHelper(cf, mods, output, bom, order);
     }
@@ -445,7 +444,7 @@
     {
         // resolve all root modules
         Configuration cf = Configuration.resolve(finder,
-                Layer.empty(),
+                Configuration.empty(),
                 ModuleFinder.empty(),
                 roots);
 
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/TaskHelper.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/TaskHelper.java	Fri Nov 27 15:34:17 2015 +0000
@@ -895,14 +895,17 @@
         if (finder instanceof ConfigurableModuleFinder)
             ((ConfigurableModuleFinder)finder).configurePhase(Phase.LINK_TIME);
 
-        Configuration cf
-            = Configuration.resolve(ModuleFinder.empty(), Layer.boot(), finder);
+        Configuration bootConfiguration= Layer.boot().configuration();
+
+        Configuration cf = Configuration.resolve(ModuleFinder.empty(), bootConfiguration, finder);
+
         cf = cf.bind();
+
         // The creation of this classloader is done outside privileged block in purpose
         // If a security manager is set, then permission must be granted to jlink
         // codebase to create a classloader. This is the expected behavior.
         ClassLoader cl = new ModuleClassLoader(cf);
-        return Layer.create(cf, mn -> cl);
+        return Layer.create(cf, Layer.boot(), mn -> cl);
     }
 
     // Display all plugins or resource only.
--- a/test/java/lang/Class/getResource/Main.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/test/java/lang/Class/getResource/Main.java	Fri Nov 27 15:34:17 2015 +0000
@@ -120,7 +120,7 @@
      * Returns the directory for the given module (by name).
      */
     static Path directoryFor(String name) {
-        Configuration cf = Layer.boot().configuration().get();
+        Configuration cf = Layer.boot().configuration();
         ModuleReference mref = cf.findModule(name).orElse(null);
         if (mref == null)
             throw new RuntimeException("not found: " + name);
--- a/test/java/lang/ModuleClassLoader/Basic.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/test/java/lang/ModuleClassLoader/Basic.java	Fri Nov 27 15:34:17 2015 +0000
@@ -30,7 +30,6 @@
  */
 
 import java.io.FilePermission;
-import java.lang.reflect.Layer;
 import java.lang.module.Configuration;
 import java.lang.module.ModuleFinder;
 import java.net.URL;
@@ -72,7 +71,7 @@
     static void test(ClassLoader parent, boolean succeed) throws ClassNotFoundException {
 
         Configuration cf = Configuration.resolve(ModuleFinder.empty(),
-                                                 Layer.empty(),
+                                                 Configuration.empty(),
                                                  ModuleFinder.empty());
         try {
             ModuleClassLoader loader = new ModuleClassLoader(parent, cf);
--- a/test/jdk/jigsaw/module/AutomaticModulesTest.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/test/jdk/jigsaw/module/AutomaticModulesTest.java	Fri Nov 27 15:34:17 2015 +0000
@@ -50,9 +50,6 @@
 import java.util.jar.Manifest;
 import java.util.stream.Collectors;
 
-import static java.lang.reflect.Layer.boot;
-import static java.lang.module.ModuleFinder.empty;
-
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 import static org.testng.Assert.*;
@@ -63,6 +60,7 @@
     private static final Path USER_DIR
          = Paths.get(System.getProperty("user.dir"));
 
+    private final Configuration BOOT_CONFIGURATION = Layer.boot().configuration();
 
     @DataProvider(name = "names")
     public Object[][] createNames() {
@@ -144,7 +142,10 @@
         ModuleFinder finder = ModuleFinder.of(dir);
 
         Configuration cf
-            = Configuration.resolve(finder, boot(), empty(), "m1");
+            = Configuration.resolve(finder,
+                                    BOOT_CONFIGURATION,
+                                    ModuleFinder.empty(),
+                                    "m1");
 
         ModuleDescriptor m1 = cf.findDescriptor("m1").get();
 
@@ -176,7 +177,10 @@
         ModuleFinder finder = ModuleFinder.of(dir);
 
         Configuration cf
-            = Configuration.resolve(finder, boot(), empty(), "m1");
+            = Configuration.resolve(finder,
+                                    BOOT_CONFIGURATION,
+                                    ModuleFinder.empty(),
+                                    "m1");
 
         ModuleDescriptor m1 = cf.findDescriptor("m1").get();
 
@@ -186,6 +190,23 @@
 
 
     /**
+     * Returns {@code true} if the configuration contains module mn1
+     * that reads module mn2.
+     */
+    static boolean reads(Configuration cf, String mn1, String mn2) {
+
+        Optional<ModuleDescriptor> omd1 = cf.findDescriptor(mn1);
+        if (!omd1.isPresent())
+            return false;
+
+        ModuleDescriptor md1 = omd1.get();
+        return cf.reads(md1).stream()
+                .map(Configuration.ReadDependence::descriptor)
+                .map(ModuleDescriptor::name)
+                .anyMatch(mn2::equals);
+    }
+
+    /**
      * Basic test of a configuration created with automatic modules
      */
     public void testInConfiguration() throws IOException {
@@ -207,7 +228,10 @@
                                   ModuleFinder.of(dir));
 
         Configuration cf
-            = Configuration.resolve(finder, boot(), empty(), "m1");
+            = Configuration.resolve(finder,
+                                    BOOT_CONFIGURATION,
+                                    ModuleFinder.empty(),
+                                    "m1");
 
         assertTrue(cf.descriptors().size() == 3);
         assertTrue(cf.findDescriptor("m1").isPresent());
@@ -221,28 +245,22 @@
         assertTrue(m2.requires().size() == 1);
         assertTrue(m3.requires().size() == 1);
 
-        // the descriptors for the modules in the boot Layer
-        Set<ModuleDescriptor> bootModules
-            = Layer.boot().configuration().get().descriptors();
+        // the modules in the boot Layer
+        Set<String> bootModules = Layer.boot().modules().stream()
+                .map(Module::getName)
+                .collect(Collectors.toSet());
 
-        // As m2 and m3 are automatic modules then they read all modules
-        // As m1 requires m2 & m3 then it needs to read all modules that
-        // m2 and m3 read.
+        assertTrue(reads(cf, "m1", "m2"));
+        assertTrue(reads(cf, "m1", "m3"));
+        bootModules.forEach(mn -> assertTrue(reads(cf, "m1", mn)));
 
-        //assertFalse(cf.reads(m1).contains(m1));
-        assertTrue(cf.reads(m1).contains(m2));
-        assertTrue(cf.reads(m1).contains(m3));
-        assertTrue(cf.reads(m1).containsAll(bootModules));
+        assertTrue(reads(cf, "m2", "m1"));
+        assertTrue(reads(cf, "m2", "m3"));
+        bootModules.forEach(mn -> assertTrue(reads(cf, "m2", mn)));
 
-        //assertFalse(cf.reads(m2).contains(m2));
-        assertTrue(cf.reads(m2).contains(m1));
-        assertTrue(cf.reads(m2).contains(m3));
-        assertTrue(cf.reads(m2).containsAll(bootModules));
-
-        //assertFalse(cf.reads(m3).contains(m3));
-        assertTrue(cf.reads(m3).contains(m1));
-        assertTrue(cf.reads(m3).contains(m2));
-        assertTrue(cf.reads(m3).containsAll(bootModules));
+        assertTrue(reads(cf, "m3", "m1"));
+        assertTrue(reads(cf, "m3", "m2"));
+        bootModules.forEach(mn -> assertTrue(reads(cf, "m3", mn)));
     }
 
 
@@ -264,14 +282,17 @@
         // module finder locates m1 and the modules in the directory
         ModuleFinder finder
             = ModuleFinder.concat(ModuleUtils.finderOf(descriptor),
-                                  ModuleFinder.of(dir));
+                ModuleFinder.of(dir));
 
         Configuration cf
-            = Configuration.resolve(finder, Layer.boot(), ModuleFinder.empty(), "m1");
+            = Configuration.resolve(finder,
+                                    BOOT_CONFIGURATION,
+                                    ModuleFinder.empty(),
+                                    "m1");
         assertTrue(cf.descriptors().size() == 3);
 
         // each module gets its own loader
-        Layer layer = Layer.create(cf, mn -> new ClassLoader(){});
+        Layer layer = Layer.create(cf, Layer.boot(), mn -> new ClassLoader(){});
 
         Module m2 = layer.findModule("m2").get();
         assertTrue(m2.isNamed());
@@ -292,7 +313,7 @@
         while (layer != Layer.empty()) {
 
             // check that m reads all module in the layer
-            layer.configuration().get().descriptors().stream()
+            layer.configuration().descriptors().stream()
                 .map(ModuleDescriptor::name)
                 .map(layer::findModule)
                 .map(Optional::get)
--- a/test/jdk/jigsaw/module/ConfigurationTest.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/test/jdk/jigsaw/module/ConfigurationTest.java	Fri Nov 27 15:34:17 2015 +0000
@@ -30,16 +30,19 @@
  */
 
 import java.lang.module.Configuration;
+import java.lang.module.Configuration.ReadDependence;
 import java.lang.module.ModuleDescriptor;
 import java.lang.module.ModuleDescriptor.Requires.Modifier;
 import java.lang.module.ModuleFinder;
 import java.lang.module.ModuleReference;
 import java.lang.module.ResolutionException;
 import java.lang.reflect.Layer;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
 import java.util.stream.Collectors;
 
-import static java.lang.module.ModuleFinder.empty;
-import static java.lang.reflect.Layer.*;
+import static java.lang.module.Configuration.empty;
 
 import org.testng.annotations.Test;
 import static org.testng.Assert.*;
@@ -48,13 +51,32 @@
 public class ConfigurationTest {
 
     /**
+     * Returns {@code true} if the configuration contains module mn1
+     * that reads module mn2.
+     */
+    static boolean reads(Configuration cf, String mn1, String mn2) {
+
+        Optional<ModuleDescriptor> omd1 = cf.findDescriptor(mn1);
+        if (!omd1.isPresent())
+            return false;
+
+        ModuleDescriptor md1 = omd1.get();
+        return cf.reads(md1).stream()
+                .map(ReadDependence::descriptor)
+                .map(ModuleDescriptor::name)
+                .anyMatch(mn2::equals);
+    }
+
+
+    /**
      * Basic test of resolver
+     *     m1 requires m2, m2 requires m3
      */
     public void testBasic() {
         ModuleDescriptor descriptor1
             = new ModuleDescriptor.Builder("m1")
-                    .requires("m2")
-                    .build();
+                .requires("m2")
+                .build();
 
         ModuleDescriptor descriptor2
             = new ModuleDescriptor.Builder("m2")
@@ -68,17 +90,20 @@
         ModuleFinder finder
             = ModuleUtils.finderOf(descriptor1, descriptor2, descriptor3);
 
-        Configuration cf = Configuration.resolve(finder, boot(), empty(), "m1");
+        Configuration cf
+            = Configuration.resolve(finder, empty(), ModuleFinder.empty(), "m1");
 
         assertTrue(cf.descriptors().size() == 3);
         assertTrue(cf.descriptors().contains(descriptor1));
         assertTrue(cf.descriptors().contains(descriptor2));
         assertTrue(cf.descriptors().contains(descriptor3));
 
+        assertTrue(cf.parent().get() == empty());
+
         assertEquals(cf.modules().stream()
                         .map(ModuleReference::descriptor)
                         .collect(Collectors.toSet()),
-                     cf.descriptors());
+                cf.descriptors());
 
         assertTrue(cf.findModule("m1").isPresent());
         assertTrue(cf.findModule("m2").isPresent());
@@ -87,11 +112,11 @@
 
         // m1 reads m2
         assertTrue(cf.reads(descriptor1).size() == 1);
-        assertTrue(cf.reads(descriptor1).contains(descriptor2));
+        assertTrue(reads(cf, "m1", "m2"));
 
         // m2 reads m3
         assertTrue(cf.reads(descriptor2).size() == 1);
-        assertTrue(cf.reads(descriptor2).contains(descriptor3));
+        assertTrue(reads(cf, "m2", "m3"));
 
         // m3 reads nothing
         assertTrue(cf.reads(descriptor3).size() == 0);
@@ -104,9 +129,10 @@
 
 
     /**
-     * Basic test of "requires public"
+     * Basic test of "requires public":
+     *     m1 requires m2, m2 requires public m3
      */
-    public void testRequiresPublic() {
+    public void testRequiresPublic1() {
         // m1 requires m2, m2 requires public m3
         ModuleDescriptor descriptor1
             = new ModuleDescriptor.Builder("m1")
@@ -126,13 +152,15 @@
             = ModuleUtils.finderOf(descriptor1, descriptor2, descriptor3);
 
         Configuration cf
-            = Configuration.resolve(finder, boot(), empty(), "m1");
+            = Configuration.resolve(finder, empty(), ModuleFinder.empty(), "m1");
 
         assertTrue(cf.descriptors().size() == 3);
         assertTrue(cf.descriptors().contains(descriptor1));
         assertTrue(cf.descriptors().contains(descriptor2));
         assertTrue(cf.descriptors().contains(descriptor3));
 
+        assertTrue(cf.parent().get() == empty());
+
         assertEquals(cf.modules().stream()
                         .map(ModuleReference::descriptor)
                         .collect(Collectors.toSet()),
@@ -143,12 +171,12 @@
 
         // m1 reads m2 and m3
         assertTrue(cf.reads(descriptor1).size() == 2);
-        assertTrue(cf.reads(descriptor1).contains(descriptor2));
-        assertTrue(cf.reads(descriptor1).contains(descriptor3));
+        assertTrue(reads(cf, "m1", "m2"));
+        assertTrue(reads(cf, "m1", "m3"));
 
         // m2 reads m3
         assertTrue(cf.reads(descriptor2).size() == 1);
-        assertTrue(cf.reads(descriptor2).contains(descriptor3));
+        assertTrue(reads(cf, "m2", "m3"));
 
         // m3 reads nothing
         assertTrue(cf.reads(descriptor3).size() == 0);
@@ -156,87 +184,223 @@
 
 
     /**
-     * Basic test of "requires public" with layers.
+     * Basic test of "requires public" with configurations.
      *
-     * The test consists of two configurations cf1 and cf2. In configuration
-     * cf1 then m1 requires public m2. In configuration cf2 then m3 requires
-     * m1.
+     * The test consists of three configurations:
+     * - Configuration cf1: m1, m2 requires public m1
+     * - Configuration cf2: m3 requires m1
      */
-    public void testRequiresPublicWithLayers1() {
+    public void testRequiresPublic2() {
 
-        // cf1: m1 and m2, m1 requires public m2
+        // cf1: m1 and m2, m2 requires public m1
 
         ModuleDescriptor descriptor1
             = new ModuleDescriptor.Builder("m1")
-                .requires(Modifier.PUBLIC, "m2")
                 .build();
 
         ModuleDescriptor descriptor2
             = new ModuleDescriptor.Builder("m2")
+                .requires(Modifier.PUBLIC, "m1")
                 .build();
 
         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2);
 
-        Configuration cf1 = Configuration.resolve(finder1, boot(), empty(), "m1");
+        Configuration cf1
+            = Configuration.resolve(finder1, empty(), ModuleFinder.empty(), "m2");
 
-        ClassLoader cl1 = new ClassLoader() { };
-        Layer layer1 = Layer.create(cf1, mn -> cl1);
+        assertTrue(cf1.descriptors().size() == 2);
+        assertTrue(cf1.descriptors().contains(descriptor1));
+        assertTrue(cf1.descriptors().contains(descriptor2));
+        assertTrue(cf1.parent().get() == empty());
 
+        assertTrue(reads(cf1, "m2", "m1"));
+        assertFalse(reads(cf1, "m1", "m2"));
 
-        // cf2: m3, m3 requires m1
+
+        // cf2: m3, m3 requires m2
 
         ModuleDescriptor descriptor3
             = new ModuleDescriptor.Builder("m3")
-                .requires("m1")
+                .requires("m2")
                 .build();
 
         ModuleFinder finder2 = ModuleUtils.finderOf(descriptor3);
 
         Configuration cf2
-            = Configuration.resolve(finder2, layer1, empty(), "m3");
+            = Configuration.resolve(finder2, cf1, ModuleFinder.empty(), "m3");
 
         assertTrue(cf2.descriptors().size() == 1);
         assertTrue(cf2.descriptors().contains(descriptor3));
+        assertTrue(cf2.parent().get() == cf1);
 
         assertTrue(cf2.reads(descriptor3).size() == 2);
-        assertTrue(cf2.reads(descriptor3).contains(descriptor1));
-        assertTrue(cf2.reads(descriptor3).contains(descriptor2));
+        assertTrue(reads(cf2, "m3", "m1"));
+        assertTrue(reads(cf2, "m3", "m2"));
     }
 
 
     /**
-     * Basic test of "requires public" with layers
+     * Basic test of "requires public" with configurations.
      *
-     * The test consists of two configurations cf1 and cf2. In configuration
-     * cf1 then m1 requires public m2. In configuration cf2 then m3 requires
-     * public m1 and m4 requires m3.
+     * The test consists of three configurations:
+     * - Configuration cf1: m1
+     * - Configuration cf2: m2 requires public m3, m3 requires m2
      */
-    public void testRequiresPublicWithLayers2() {
+    public void testRequiresPublic3() {
 
-        // cf1: m1 and m2, m1 requires public m2
+        // cf1: m1
 
         ModuleDescriptor descriptor1
             = new ModuleDescriptor.Builder("m1")
-                .requires(Modifier.PUBLIC, "m2")
+                .build();
+
+        ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1);
+
+        Configuration cf1
+            = Configuration.resolve(finder1, empty(), ModuleFinder.empty(), "m1");
+
+        assertTrue(cf1.descriptors().size() == 1);
+        assertTrue(cf1.descriptors().contains(descriptor1));
+        assertTrue(cf1.parent().get() == empty());
+
+
+        // cf2: m2, m3: m2 requires public m1, m3 requires m2
+
+        ModuleDescriptor descriptor2
+            = new ModuleDescriptor.Builder("m2")
+                .requires(Modifier.PUBLIC, "m1")
+                .build();
+
+        ModuleDescriptor descriptor3
+            = new ModuleDescriptor.Builder("m3")
+                .requires("m2")
+                .build();
+
+        ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2, descriptor3);
+
+        Configuration cf2
+            = Configuration.resolve(finder2, cf1, ModuleFinder.empty(), "m3");
+
+        assertTrue(cf2.descriptors().size() == 2);
+        assertTrue(cf2.descriptors().contains(descriptor2));
+        assertTrue(cf2.descriptors().contains(descriptor3));
+        assertTrue(cf2.parent().get() == cf1);
+
+        assertTrue(cf2.reads(descriptor2).size() == 1);
+        assertTrue(reads(cf2, "m2", "m1"));
+
+        assertTrue(cf2.reads(descriptor3).size() == 2);
+        assertTrue(reads(cf2, "m3", "m1"));
+        assertTrue(reads(cf2, "m3", "m2"));
+    }
+
+
+    /**
+     * Basic test of "requires public" with configurations.
+     *
+     * The test consists of three configurations:
+     * - Configuration cf1: m1
+     * - Configuration cf2: m2 requires public m1
+     * - Configuraiton cf3: m3 requires m3
+     */
+    public void testRequiresPublic4() {
+
+        // cf1: m1
+
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
+                .build();
+
+        ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1);
+
+        Configuration cf1
+            = Configuration.resolve(finder1, empty(), ModuleFinder.empty(), "m1");
+
+        assertTrue(cf1.descriptors().size() == 1);
+        assertTrue(cf1.descriptors().contains(descriptor1));
+        assertTrue(cf1.parent().get() == empty());
+
+        // cf2: m2 requires public m1
+
+        ModuleDescriptor descriptor2
+            = new ModuleDescriptor.Builder("m2")
+                .requires(Modifier.PUBLIC, "m1")
+                .build();
+
+        ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2);
+
+        Configuration cf2
+            = Configuration.resolve(finder2, cf1, ModuleFinder.empty(), "m2");
+
+        assertTrue(cf2.descriptors().size() == 1);
+        assertTrue(cf2.descriptors().contains(descriptor2));
+        assertTrue(cf2.parent().get() == cf1);
+
+        assertTrue(cf2.reads(descriptor2).size() == 1);
+        assertTrue(reads(cf2, "m2", "m1"));
+
+
+        // cf3: m3 requires m2
+
+        ModuleDescriptor descriptor3
+            = new ModuleDescriptor.Builder("m3")
+                .requires("m2")
+                .build();
+
+        ModuleFinder finder3 = ModuleUtils.finderOf(descriptor3);
+
+        Configuration cf3
+            = Configuration.resolve(finder3, cf2, ModuleFinder.empty(), "m3");
+
+        assertTrue(cf3.descriptors().size() == 1);
+        assertTrue(cf3.descriptors().contains(descriptor3));
+        assertTrue(cf3.parent().get() == cf2);
+
+        assertTrue(cf3.reads(descriptor3).size() == 2);
+        assertTrue(reads(cf3, "m3", "m1"));
+        assertTrue(reads(cf3, "m3", "m2"));
+    }
+
+
+    /**
+     * Basic test of "requires public" with configurations.
+     *
+     * The test consists of two configurations:
+     * - Configuration cf1: m1, m2 requires public m1
+     * - Configuration cf2: m3 requires public m2, m4 requires m3
+     */
+    public void testRequiresPublic5() {
+
+        // cf1: m1, m2 requires public m1
+
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
                 .build();
 
         ModuleDescriptor descriptor2
             = new ModuleDescriptor.Builder("m2")
+                .requires(Modifier.PUBLIC, "m1")
                 .build();
 
         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2);
 
-        Configuration cf1 = Configuration.resolve(finder1, boot(), empty(), "m1");
+        Configuration cf1
+            = Configuration.resolve(finder1, empty(), ModuleFinder.empty(), "m2");
 
-        ClassLoader cl1 = new ClassLoader() { };
-        Layer layer1 = Layer.create(cf1, mn -> cl1);
+        assertTrue(cf1.descriptors().size() == 2);
+        assertTrue(cf1.descriptors().contains(descriptor1));
+        assertTrue(cf1.descriptors().contains(descriptor2));
+        assertTrue(cf1.parent().get() == empty());
 
+        assertTrue(cf1.reads(descriptor2).size() == 1);
+        assertTrue(reads(cf1, "m2", "m1"));
 
-        // cf2: m3 and m4, m4 requires m3, m3 requires public m1
+
+        // cf2: m3 requires public m2, m4 requires m3
 
         ModuleDescriptor descriptor3
             = new ModuleDescriptor.Builder("m3")
-                .requires(Modifier.PUBLIC, "m1")
+                .requires(Modifier.PUBLIC, "m2")
                 .build();
 
         ModuleDescriptor descriptor4
@@ -248,176 +412,508 @@
         ModuleFinder finder2 = ModuleUtils.finderOf(descriptor3, descriptor4);
 
         Configuration cf2
-            = Configuration.resolve(finder2, layer1, empty(),  "m3", "m4");
+            = Configuration.resolve(finder2, cf1, ModuleFinder.empty(),  "m3", "m4");
 
         assertTrue(cf2.descriptors().size() == 2);
         assertTrue(cf2.descriptors().contains(descriptor3));
         assertTrue(cf2.descriptors().contains(descriptor4));
 
+        assertTrue(cf2.parent().get() == cf1);
+
         assertTrue(cf2.reads(descriptor3).size() == 2);
-        assertTrue(cf2.reads(descriptor3).contains(descriptor1));
-        assertTrue(cf2.reads(descriptor3).contains(descriptor2));
+        assertTrue(reads(cf2, "m3", "m1"));
+        assertTrue(reads(cf2, "m3", "m2"));
 
         assertTrue(cf2.reads(descriptor4).size() == 3);
-        assertTrue(cf2.reads(descriptor4).contains(descriptor1));
-        assertTrue(cf2.reads(descriptor4).contains(descriptor2));
-        assertTrue(cf2.reads(descriptor4).contains(descriptor3));
+        assertTrue(reads(cf2, "m4", "m1"));
+        assertTrue(reads(cf2, "m4", "m2"));
+        assertTrue(reads(cf2, "m4", "m3"));
     }
 
 
     /**
-     * Basic test of "requires public" with layers
-     *
-     * The test consists of two configurations cf1 and cf2. In configuration
-     * cf1 then m1 requires public m2. Configuration cf2 has m2@2 and m3 where
-     * m3 requires m1. The presence of a m2 in the second configuration is to
-     * ensure that m3 reads the correct version of m2.
+     * Basic test of binding services
      */
-    public void testRequiresPublicWithLayers3() {
-
-        // cf1: m1 and m2@1, m1 requires public m2
+    public void testServiceBinding1() {
 
         ModuleDescriptor descriptor1
             = new ModuleDescriptor.Builder("m1")
-                .requires(Modifier.PUBLIC, "m2")
+                .uses("p.Service")
                 .build();
 
         ModuleDescriptor descriptor2
             = new ModuleDescriptor.Builder("m2")
-                .version("1")
+                .provides("p.Service", "q.ServiceImpl")
                 .build();
 
-        ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2);
-
-        Configuration cf1 = Configuration.resolve(finder1, boot(), empty(), "m1");
-
-        ClassLoader cl1 = new ClassLoader() { };
-        Layer layer1 = Layer.create(cf1, mn -> cl1);
-
-
-        // cf2: m3 and m2@2, m3 requires m1
-
-        ModuleDescriptor descriptor2_v2
-            = new ModuleDescriptor.Builder("m2")
-                .version("2")
-                .build();
-
-        ModuleDescriptor descriptor3
-            = new ModuleDescriptor.Builder("m3")
-                .requires("m1")
-                .build();
-
-        ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2_v2, descriptor3);
-
-        Configuration cf2 = Configuration.resolve(finder2, layer1, empty(), "m2", "m3");
-
-        assertTrue(cf2.descriptors().size() == 2);
-        assertTrue(cf2.descriptors().contains(descriptor2_v2));
-        assertTrue(cf2.descriptors().contains(descriptor3));
-
-        assertTrue(cf2.reads(descriptor2_v2).isEmpty());
-
-        assertTrue(cf2.reads(descriptor3).size() == 2);
-        assertTrue(cf2.reads(descriptor3).contains(descriptor1));
-        assertTrue(cf2.reads(descriptor3).contains(descriptor2));
-    }
-
-
-    /**
-     * Basic test of binding services
-     */
-    public void testBasicBinding() {
-
-        ModuleDescriptor descriptor1
-            = new ModuleDescriptor.Builder("m1")
-                .requires("m2")
-                .uses("S")
-                .build();
-
-        ModuleDescriptor descriptor2
-            = new ModuleDescriptor.Builder("m2")
-                .provides("S", "p.S2")
-                .build();
-
-        // service provider
-        ModuleDescriptor descriptor3
-            = new ModuleDescriptor.Builder("m3")
-                .requires("m1")
-                .provides("S", "q.S3").build();
-
-        // unused module
-        ModuleDescriptor descriptor4
-            = new ModuleDescriptor.Builder("m4").build();
-
-        ModuleFinder finder
-            = ModuleUtils.finderOf(descriptor1,
-                                   descriptor2,
-                                   descriptor3,
-                                   descriptor4);
+        ModuleFinder finder = ModuleUtils.finderOf(descriptor1, descriptor2);
 
         Configuration cf
-            = Configuration.resolve(finder, boot(), empty(), "m1");
+            = Configuration.resolve(finder, empty(), ModuleFinder.empty(), "m1");
 
-        // only m1 and m2 in the configuration
-        assertTrue(cf.descriptors().size() == 2);
-        assertTrue(cf.descriptors().contains(descriptor1));
-        assertTrue(cf.descriptors().contains(descriptor2));
+        // only m1 in configuration
+        assertTrue(cf.descriptors().size() == 1);
+        assertTrue(cf.findModule("m1").isPresent());
+
+        assertTrue(cf.parent().get() == empty());
 
         assertEquals(cf.modules().stream()
                         .map(ModuleReference::descriptor)
                         .collect(Collectors.toSet()),
                 cf.descriptors());
 
+        assertTrue(cf.provides("p.Service").isEmpty());
+
+        // bind services, should augment graph with m2
+        cf = cf.bind();
+
+        assertTrue(cf.parent().get() == empty());
+
+        assertTrue(cf.descriptors().size() == 2);
         assertTrue(cf.findModule("m1").isPresent());
         assertTrue(cf.findModule("m2").isPresent());
 
-        assertTrue(cf.reads(descriptor1).size() == 1);
-        assertTrue(cf.reads(descriptor1).contains(descriptor2));
-
-        assertTrue(cf.reads(descriptor2).size() == 0);
-
-        assertTrue(cf.provides("S").isEmpty());
-
-        // bind services, should augment graph with m3
-        cf = cf.bind();
-
-        assertTrue(cf.descriptors().size() == 3);
-        assertTrue(cf.descriptors().contains(descriptor1));
-        assertTrue(cf.descriptors().contains(descriptor2));
-        assertTrue(cf.descriptors().contains(descriptor3));
-
         assertEquals(cf.modules().stream()
                         .map(ModuleReference::descriptor)
                         .collect(Collectors.toSet()),
                 cf.descriptors());
 
-        assertTrue(cf.findModule("m1").isPresent());
-        assertTrue(cf.findModule("m2").isPresent());
-        assertTrue(cf.findModule("m3").isPresent());
+        assertTrue(cf.provides("p.Service").size() == 1);
+        assertTrue(cf.provides("p.Service").contains(descriptor2));
+    }
 
-        assertTrue(cf.reads(descriptor1).size() == 1);
-        assertTrue(cf.reads(descriptor1).contains(descriptor2));
 
-        assertTrue(cf.reads(descriptor2).size() == 0);
+    /**
+     * Basic test of binding services with configurations.
+     *
+     * The test consists of two configurations:
+     * - Configuration cf1: m1 uses p.Service
+     * - Configuration cf2: m2 provides p.Service
+     */
+    public void testServiceBinding2() {
 
-        assertTrue(cf.reads(descriptor3).size() == 1);
-        assertTrue(cf.reads(descriptor3).contains(descriptor1));
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
+                .uses("p.Service")
+                .build();
 
-        assertTrue(cf.provides("S").size() == 2);
-        assertTrue(cf.provides("S").contains(descriptor2));
-        assertTrue(cf.provides("S").contains(descriptor3));
+        ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1);
 
-        assertTrue(cf.toString().contains("m1"));
-        assertTrue(cf.toString().contains("m2"));
-        assertTrue(cf.toString().contains("m3"));
+        Configuration cf1
+            = Configuration.resolve(finder1, empty(), ModuleFinder.empty(), "m1");
+
+        assertTrue(cf1.descriptors().size() == 1);
+        assertTrue(cf1.findModule("m1").isPresent());
+
+        ModuleDescriptor descriptor2
+            = new ModuleDescriptor.Builder("m2")
+                .provides("p.Service", "q.ServiceImpl")
+                .build();
+
+        ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2);
+
+        Configuration cf2 = Configuration.resolve(finder2, cf1, ModuleFinder.empty());
+
+        assertTrue(cf2.descriptors().size() == 0);
+
+        cf2 = cf2.bind();
+
+        assertTrue(cf2.parent().get() == cf1);
+
+        assertTrue(cf2.descriptors().size() == 1);
+        assertTrue(cf2.findModule("m2").isPresent());
+
+        assertTrue(cf2.provides("p.Service").size() == 1);
+        assertTrue(cf2.provides("p.Service").contains(descriptor2));
     }
 
+
+    /**
+     * Basic test of binding services with configurations.
+     *
+     * The test consists of two configurations:
+     * - Configuration cf1: m1 uses p.Service && provides p.Service,
+     *                      m2 provides p.Service
+     * - Configuration cf2: m3 provides p.Service
+     *                      m4 provides p.Service
+     */
+    public void testServiceBinding3() {
+
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
+                .uses("p.Service")
+                .provides("p.Service", "m1.ServiceImpl")
+                .build();
+
+        ModuleDescriptor descriptor2
+            = new ModuleDescriptor.Builder("m2")
+                .provides("p.Service", "m2.ServiceImpl")
+                .build();
+
+        ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2);
+
+        Configuration cf1
+            = Configuration.resolve(finder1, empty(), ModuleFinder.empty(), "m1");
+
+        assertTrue(cf1.descriptors().size() == 1);
+        assertTrue(cf1.findModule("m1").isPresent());
+        assertTrue(cf1.provides("p.Service").size() == 1);
+
+        cf1 = cf1.bind();
+
+        assertTrue(cf1.parent().get() == empty());
+
+        assertTrue(cf1.descriptors().size() == 2);
+        assertTrue(cf1.findModule("m1").isPresent());
+        assertTrue(cf1.findModule("m2").isPresent());
+
+        assertTrue(cf1.provides("p.Service").size() == 2);
+        assertTrue(cf1.provides("p.Service").contains(descriptor1));
+        assertTrue(cf1.provides("p.Service").contains(descriptor2));
+
+
+        ModuleDescriptor descriptor3
+            = new ModuleDescriptor.Builder("m3")
+                .provides("p.Service", "m3.ServiceImpl")
+                .build();
+
+        ModuleDescriptor descriptor4
+            = new ModuleDescriptor.Builder("m4")
+                .provides("p.Service", "m4.ServiceImpl")
+                .build();
+
+        ModuleFinder finder2 = ModuleUtils.finderOf(descriptor3, descriptor4);
+
+        Configuration cf2 = Configuration.resolve(finder2, cf1, ModuleFinder.empty());
+
+        assertTrue(cf2.descriptors().size() == 0);
+
+        cf2 = cf2.bind();
+
+        assertTrue(cf2.parent().get() == cf1);
+
+        assertTrue(cf2.descriptors().size() == 2);
+        assertTrue(cf2.findModule("m3").isPresent());
+        assertTrue(cf2.findModule("m4").isPresent());
+
+        assertTrue(cf2.provides("p.Service").size() == 2);
+        assertTrue(cf2.provides("p.Service").contains(descriptor3));
+        assertTrue(cf2.provides("p.Service").contains(descriptor4));
+    }
+
+
+    /**
+     * Basic test of binding services with configurations.
+     *
+     * Configuration cf1: p@1.0 provides p.Service
+     * Test configuration cf2: m1 uses p.Service
+     * Test configuration cf2: m1 uses p.Service, p@2.0 uses p.Service
+     */
+    public void testServiceBinding4() {
+
+        ModuleDescriptor provider_v1
+            = new ModuleDescriptor.Builder("p")
+                .version("1.0")
+                .provides("p.Service", "q.ServiceImpl")
+                .build();
+
+        ModuleFinder finder1 = ModuleUtils.finderOf(provider_v1);
+
+        Configuration cf1
+            = Configuration.resolve(finder1,
+                                    empty(),
+                                    ModuleFinder.empty(),
+                                    "p");
+
+        assertTrue(cf1.descriptors().size() == 1);
+        assertTrue(cf1.descriptors().contains(provider_v1));
+        assertTrue(cf1.provides("p.Service").contains(provider_v1));
+
+
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
+                .uses("p.Service")
+                .build();
+
+        ModuleDescriptor provider_v2
+            = new ModuleDescriptor.Builder("p")
+                .version("2.0")
+                .provides("p.Service", "q.ServiceImpl")
+                .build();
+
+        ModuleFinder finder2 = ModuleUtils.finderOf(descriptor1, provider_v2);
+
+        Configuration cf2;
+
+
+        // finder2 is the before ModuleFinder and so p@2.0 should be located
+
+        cf2 = Configuration.resolve(finder2, cf1, ModuleFinder.empty(), "m1");
+
+        assertTrue(cf2.descriptors().size() == 1);
+        assertTrue(cf2.descriptors().contains(descriptor1));
+
+        cf2 = cf2.bind();
+
+        assertTrue(cf2.descriptors().size() == 2);
+        assertTrue(cf2.descriptors().contains(descriptor1));
+        assertTrue(cf2.descriptors().contains(provider_v2));
+        assertTrue(cf2.provides("p.Service").contains(provider_v2));
+
+
+        // finder2 is the after ModuleFinder and so p@2.0 should not be located
+        // as module p is in parent configuration.
+
+        cf2 = Configuration.resolve(ModuleFinder.empty(), cf1, finder2, "m1");
+
+        assertTrue(cf2.descriptors().size() == 1);
+        assertTrue(cf2.descriptors().contains(descriptor1));
+
+        cf2 = cf2.bind();
+
+        assertTrue(cf2.descriptors().size() == 1);
+        assertTrue(cf2.descriptors().contains(descriptor1));
+        assertTrue(cf2.provides("p.Service").isEmpty());
+    }
+
+
+    /**
+     * Basic test with two module finders.
+     *
+     * Module m2 can be found by both the before and after finders.
+     */
+    public void testWithTwoFinders1() {
+
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
+                .requires("m2")
+                .build();
+
+        ModuleDescriptor descriptor2_v1
+            = new ModuleDescriptor.Builder("m2")
+                .version("1.0")
+                .build();
+
+        ModuleDescriptor descriptor2_v2
+            = new ModuleDescriptor.Builder("m2")
+                .version("2.0")
+                .build();
+
+        ModuleFinder finder1 = ModuleUtils.finderOf(descriptor2_v1);
+        ModuleFinder finder2 = ModuleUtils.finderOf(descriptor1, descriptor2_v2);
+
+        Configuration cf = Configuration.resolve(finder1, empty(), finder2, "m1");
+
+        assertTrue(cf.descriptors().size() == 2);
+        assertTrue(cf.descriptors().contains(descriptor1));
+        assertTrue(cf.descriptors().contains(descriptor2_v1));
+    }
+
+
+    /**
+     * Basic test with two modules finders and service binding.
+     *
+     * The before and after ModuleFinders both locate a service provider module
+     * named "m2" that provide implementations of the same service type.
+     */
+    public void testWithTwoFinders2() {
+
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
+                .uses("p.Service")
+                .build();
+
+        ModuleDescriptor descriptor2_v1
+            = new ModuleDescriptor.Builder("m2")
+                .provides("p.Service", "q.ServiceImpl")
+                .build();
+
+        ModuleDescriptor descriptor2_v2
+            = new ModuleDescriptor.Builder("m2")
+                .provides("p.Service", "q.ServiceImpl")
+                .build();
+
+        ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2_v1);
+        ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2_v2);
+
+        Configuration cf = Configuration.resolve(finder1, empty(), finder2, "m1");
+
+        assertTrue(cf.descriptors().size() == 1);
+        assertTrue(cf.descriptors().contains(descriptor1));
+        assertTrue(cf.provides("p.Service").isEmpty());
+
+        cf = cf.bind();
+
+        assertTrue(cf.descriptors().size() == 2);
+        assertTrue(cf.descriptors().contains(descriptor1));
+        assertTrue(cf.descriptors().contains(descriptor2_v1));
+        assertTrue(cf.provides("p.Service").contains(descriptor2_v1));
+
+    }
+
+
+    /**
+     * Basic test for resolving a module that is located in the parent
+     * configuration.
+     */
+    public void testResolvedInParent1() {
+
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
+                .build();
+
+        ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1);
+
+        Configuration cf1
+            = Configuration.resolve(finder1, empty(), ModuleFinder.empty(), "m1");
+
+        assertTrue(cf1.descriptors().size() == 1);
+        assertTrue(cf1.descriptors().contains(descriptor1));
+
+
+        Configuration cf2
+            = Configuration.resolve(ModuleFinder.empty(), cf1, finder1, "m1");
+
+        assertTrue(cf2.descriptors().size() == 0);
+    }
+
+
+    /**
+     * Basic test for resolving a module that has a dependency on a module
+     * in the parent configuration.
+     */
+    public void testResolvedInParent2() {
+
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
+                .build();
+
+        ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1);
+
+        Configuration cf1
+            = Configuration.resolve(finder1, empty(), ModuleFinder.empty(), "m1");
+
+        assertTrue(cf1.descriptors().size() == 1);
+        assertTrue(cf1.descriptors().contains(descriptor1));
+
+
+        ModuleDescriptor descriptor2
+            = new ModuleDescriptor.Builder("m2")
+                .requires("m1")
+                .build();
+
+        ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2);
+
+        Configuration cf2
+            = Configuration.resolve(ModuleFinder.empty(), cf1, finder2, "m2");
+
+        assertTrue(cf2.descriptors().size() == 1);
+        assertTrue(cf2.descriptors().contains(descriptor2));
+
+        Set<ReadDependence> reads = cf2.reads(descriptor2);
+        assertTrue(reads.size() == 1);
+        ReadDependence rd = reads.iterator().next();
+        assertEquals(rd.configuration(), cf1);
+        assertEquals(rd.descriptor(), descriptor1);
+    }
+
+
+    /**
+     * Basic test of using the beforeFinder to override a module in the parent
+     * configuration.
+     */
+    public void testOverriding1() {
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
+                .build();
+
+        ModuleFinder finder = ModuleUtils.finderOf(descriptor1);
+
+        Configuration cf1
+            = Configuration.resolve(finder, empty(), ModuleFinder.empty(), "m1");
+        assertTrue(cf1.descriptors().size() == 1);
+        assertTrue(cf1.descriptors().contains(descriptor1));
+
+        Configuration cf2
+                = Configuration.resolve(finder, cf1, ModuleFinder.empty(), "m1");
+        assertTrue(cf2.parent().get() == cf1);
+        assertTrue(cf2.descriptors().size() == 1);
+        assertTrue(cf2.descriptors().contains(descriptor1));
+    }
+
+
+    /**
+     * Basic test of using the beforeFinder to override a module in the parent
+     * configuration but where implied readability in the picture so that the
+     * module in the parent is read.
+     *
+     * The test consists of two configurations:
+     * - Configuration cf1: m1, m2 requires public m1
+     * - Configuration cf2: m1, m3 requires m2
+     */
+    public void testOverriding2() {
+
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
+                .build();
+
+        ModuleDescriptor descriptor2
+            = new ModuleDescriptor.Builder("m2")
+                .requires(Modifier.PUBLIC, "m1")
+                .build();
+
+        ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2);
+
+        Configuration cf1
+            = Configuration.resolve(finder1, empty(), ModuleFinder.empty(), "m2");
+
+        assertTrue(cf1.descriptors().size() == 2);
+        assertTrue(cf1.descriptors().contains(descriptor1));
+        assertTrue(cf1.descriptors().contains(descriptor2));
+        assertTrue(cf1.parent().get() == empty());
+
+        assertTrue(cf1.reads(descriptor2).size() == 1);
+        assertTrue(reads(cf1, "m2", "m1"));
+
+        // cf2: m3 requires m2, m1
+
+        ModuleDescriptor descriptor3
+            = new ModuleDescriptor.Builder("m3")
+                .requires("m2")
+                .build();
+
+        ModuleFinder finder2 = ModuleUtils.finderOf(descriptor1, descriptor3);
+
+        Configuration cf2
+            = Configuration.resolve(finder2, cf1, ModuleFinder.empty(), "m1", "m3");
+
+        assertTrue(cf2.descriptors().size() == 2);
+        assertTrue(cf2.descriptors().contains(descriptor1));
+        assertTrue(cf2.descriptors().contains(descriptor3));
+
+        assertTrue(cf2.parent().get() == cf1);
+
+        Set<ReadDependence> reads = cf2.reads(descriptor3);
+        assertTrue(reads.size() == 2);
+        assertTrue(reads(cf2, "m3", "m1"));
+        assertTrue(reads(cf2, "m3", "m2"));
+
+        // check that m3 reads cf1/m1
+        ReadDependence rd = reads.stream()
+                .filter(m -> m.descriptor().name().equals("m1"))
+                .findFirst()
+                .get();
+
+        assertTrue(rd.configuration() == cf1);
+    }
+
+
     /**
      * Root module not found
      */
     @Test(expectedExceptions = { ResolutionException.class })
     public void testRootNotFound() {
-        Configuration.resolve(empty(), boot(), empty(), "m1");
+        Configuration.resolve(ModuleFinder.empty(), empty(), ModuleFinder.empty(), "m1");
     }
 
 
@@ -430,7 +926,7 @@
             = new ModuleDescriptor.Builder("m1").requires("m2").build();
         ModuleFinder finder = ModuleUtils.finderOf(descriptor1);
 
-        Configuration.resolve(finder, boot(), empty(), "m1");
+        Configuration.resolve(finder, empty(), ModuleFinder.empty(), "m1");
     }
 
 
@@ -445,7 +941,7 @@
             = new ModuleDescriptor.Builder("m2").requires("m3").build();
         ModuleFinder finder = ModuleUtils.finderOf(descriptor1, descriptor2);
 
-        Configuration.resolve(finder, boot(), empty(), "m1");
+        Configuration.resolve(finder, empty(), ModuleFinder.empty(), "m1");
     }
 
 
@@ -456,20 +952,24 @@
     public void testServiceProviderDependencyNotFound() {
 
         // service provider dependency (on m3) not found
+
         ModuleDescriptor descriptor1
-            = new ModuleDescriptor.Builder("m1").build();
+            = new ModuleDescriptor.Builder("m1")
+                .uses("p.Service")
+                .build();
+
         ModuleDescriptor descriptor2
             = new ModuleDescriptor.Builder("m2")
                 .requires("m3")
-                .provides("java.security.Provider", "p.CryptoProvder")
+                .provides("p.Service", "q.ServiceImpl")
                 .build();
+
         ModuleFinder finder = ModuleUtils.finderOf(descriptor1, descriptor2);
 
         Configuration cf;
         try {
-            cf = Configuration.resolve(finder, boot(), empty(), "m1");
+            cf = Configuration.resolve(finder, empty(), ModuleFinder.empty(), "m1");
             assertTrue(cf.descriptors().size() == 1);
-            assertTrue(cf.provides("java.security.Provider").isEmpty());
         } catch (ResolutionException e) {
             throw new RuntimeException(e);
         }
@@ -493,7 +993,7 @@
         ModuleFinder finder
             = ModuleUtils.finderOf(descriptor1, descriptor2, descriptor3);
 
-        Configuration.resolve(finder, boot(), empty(), "m1");
+        Configuration.resolve(finder, empty(), ModuleFinder.empty(), "m1");
     }
 
 
@@ -502,6 +1002,7 @@
      */
     @Test(expectedExceptions = { ResolutionException.class })
     public void testCycleInProvider() {
+
         ModuleDescriptor descriptor1
             = new ModuleDescriptor.Builder("m1")
                 .uses("p.Service")
@@ -509,7 +1010,8 @@
         ModuleDescriptor descriptor2
             = new ModuleDescriptor.Builder("m2")
                 .requires("m3")
-                .provides("p.Service", "q.ServiceImpl").build();
+                .provides("p.Service", "q.ServiceImpl")
+                .build();
         ModuleDescriptor descriptor3
             = new ModuleDescriptor.Builder("m3")
                 .requires("m2")
@@ -520,7 +1022,7 @@
 
         Configuration cf;
         try {
-            cf = Configuration.resolve(finder, boot(), empty(), "m1");
+            cf = Configuration.resolve(finder, empty(), ModuleFinder.empty(), "m1");
             assertTrue(cf.findDescriptor("m1").get() == descriptor1);
             assertTrue(cf.descriptors().size() == 1);
         } catch (ResolutionException e) {
@@ -557,7 +1059,7 @@
         ModuleFinder finder
             = ModuleUtils.finderOf(descriptor1, descriptor2, descriptor3);
 
-        Configuration.resolve(finder, Layer.boot(), empty(), "m1");
+        Configuration.resolve(finder, empty(), ModuleFinder.empty(), "m1");
     }
 
 
@@ -581,7 +1083,7 @@
 
         ModuleFinder finder = ModuleUtils.finderOf(descriptor1, descriptor2);
 
-        Configuration.resolve(finder, Layer.boot(), empty(), "m1");
+        Configuration.resolve(finder, empty(), ModuleFinder.empty(), "m1");
     }
 
 
@@ -590,6 +1092,7 @@
      * a module that also has a concealed package p.
      */
     public void testPackagePrivateToSelfAndOther() {
+
         ModuleDescriptor descriptor1
             =  new ModuleDescriptor.Builder("m1")
                 .requires("m2")
@@ -604,14 +1107,14 @@
         ModuleFinder finder = ModuleUtils.finderOf(descriptor1, descriptor2);
 
         Configuration cf
-            = Configuration.resolve(finder, Layer.boot(), empty(), "m1");
+            = Configuration.resolve(finder, empty(), ModuleFinder.empty(), "m1");
 
         assertTrue(cf.descriptors().size() == 2);
         assertTrue(cf.descriptors().contains(descriptor1));
         assertTrue(cf.descriptors().contains(descriptor2));
 
         // m1 reads m2
-        assertTrue(cf.reads(descriptor1).contains(descriptor2));
+        assertTrue(reads(cf, "m1", "m2"));
 
         // m2 reads nothing
         assertTrue(cf.reads(descriptor2).isEmpty());
@@ -632,7 +1135,106 @@
 
         ModuleFinder finder = ModuleUtils.finderOf(descriptor);
 
-        Configuration.resolve(finder, Layer.boot(), empty(), "m1");
+        Configuration bootConfiguration = Layer.boot().configuration();
+
+        Configuration.resolve(finder, bootConfiguration, ModuleFinder.empty(), "m1");
+    }
+
+
+    /**
+     * Test the empty configuration.
+     */
+    public void testEmptyConfiguration() {
+        Configuration cf = empty();
+
+        assertFalse(cf.parent().isPresent());
+
+        assertTrue(cf.descriptors().isEmpty());
+        assertFalse(cf.findDescriptor("java.base").isPresent());
+
+        assertTrue(cf.modules().isEmpty());
+        assertFalse(cf.findModule("java.base").isPresent());
+
+        assertTrue(cf.bind() == cf);
+        assertTrue(cf.provides("java.security.Provider").isEmpty());
+    }
+
+
+    // null handling
+
+
+    @Test(expectedExceptions = { NullPointerException.class })
+    public void testResolveWithNull1() {
+        ModuleFinder finder = ModuleFinder.empty();
+        Configuration.resolve(null, empty(), finder);
+    }
+
+    @Test(expectedExceptions = { NullPointerException.class })
+    public void testResolveWithNull2() {
+        ModuleFinder finder = ModuleFinder.empty();
+        Configuration.resolve(finder, null, finder);
+    }
+
+    @Test(expectedExceptions = { NullPointerException.class })
+    public void testResolveWithNull3() {
+        ModuleFinder finder = ModuleFinder.empty();
+        Configuration.resolve(finder, empty(), null);
+    }
+
+    @Test(expectedExceptions = { NullPointerException.class })
+    public void testResolveWithNull4() {
+        ModuleFinder finder = ModuleFinder.empty();
+        Configuration.resolve(finder, empty(), finder, (Collection<String>)null);
+    }
+
+    @Test(expectedExceptions = { NullPointerException.class })
+    public void testResolveWithNull5() {
+        ModuleFinder finder = ModuleFinder.empty();
+        Configuration.resolve(finder, empty(), finder, (String[])null);
+    }
+
+    @Test(expectedExceptions = { NullPointerException.class })
+    public void testFindDescriptorWithNull() {
+        Configuration.empty().findDescriptor(null);
+    }
+
+    @Test(expectedExceptions = { NullPointerException.class })
+    public void testFindModuleWithNull() {
+        Configuration.empty().findModule(null);
+    }
+
+    @Test(expectedExceptions = { NullPointerException.class })
+    public void testReadsWithNull() {
+        Configuration.empty().reads(null);
+    }
+
+    @Test(expectedExceptions = { NullPointerException.class })
+    public void testProvidesWithNull() {
+        Configuration.empty().provides(null);
+    }
+
+
+    // immutable sets
+
+    @Test(expectedExceptions = { UnsupportedOperationException.class })
+    public void testImmutableSet1() {
+        Configuration cf = Layer.boot().configuration();
+        ModuleDescriptor descriptor = cf.findDescriptor("java.base").get();
+        Layer.boot().configuration().descriptors().add(descriptor);
+    }
+
+    @Test(expectedExceptions = { UnsupportedOperationException.class })
+    public void testImmutableSet2() {
+        Configuration cf = Layer.boot().configuration();
+        ModuleReference mref = cf.findModule("java.base").get();
+        cf.modules().add(mref);
+    }
+
+    @Test(expectedExceptions = { UnsupportedOperationException.class })
+    public void testImmutableSet3() {
+        Configuration cf = Layer.boot().configuration();
+        ModuleDescriptor descriptor = cf.findDescriptor("java.base").get();
+        cf.provides("java.security.Provider").add(descriptor);
     }
 
 }
--- a/test/jdk/jigsaw/reflect/Layer/LayerTest.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/test/jdk/jigsaw/reflect/Layer/LayerTest.java	Fri Nov 27 15:34:17 2015 +0000
@@ -40,8 +40,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
-import static java.lang.reflect.Layer.boot;
-
 
 import org.testng.annotations.Test;
 import static org.testng.Assert.*;
@@ -50,13 +48,34 @@
 public class LayerTest {
 
     /**
+     * Exercise Layer.empty()
+     */
+    public void testEmpty() {
+        Layer emptyLayer = Layer.empty();
+
+        assertFalse(emptyLayer.parent().isPresent());
+
+        assertTrue(emptyLayer.configuration() == Configuration.empty());
+
+        assertTrue(emptyLayer.modules().isEmpty());
+
+        assertFalse(emptyLayer.findModule("java.base").isPresent());
+
+        try {
+            emptyLayer.findLoader("java.base");
+            assertTrue(false);
+        } catch (IllegalArgumentException expected) { }
+    }
+
+
+    /**
      * Exercise Layer.boot()
      */
     public void testBoot() {
         Layer bootLayer = Layer.boot();
 
         // configuration
-        Configuration cf = bootLayer.configuration().get();
+        Configuration cf = bootLayer.configuration();
         assertTrue(cf.findDescriptor("java.base").get().exports()
                    .stream().anyMatch(e -> (e.source().equals("java.lang")
                                             && !e.targets().isPresent())));
@@ -81,32 +100,6 @@
 
 
     /**
-     * Exercise Layer.empty()
-     */
-    public void testEmpty() {
-        Layer emptyLayer = Layer.empty();
-
-        // configuration
-        assertFalse(emptyLayer.configuration().isPresent());
-
-        // modules
-        assertTrue(emptyLayer.modules().isEmpty());
-
-        // findModule
-        assertFalse(emptyLayer.findModule("java.base").isPresent());
-
-        // findLoader
-        try {
-            ClassLoader loader = emptyLayer.findLoader("java.base");
-            assertTrue(false);
-        } catch (IllegalArgumentException ignore) { }
-
-        // parent
-        assertTrue(!emptyLayer.parent().isPresent());
-    }
-
-
-    /**
      * Exercise Layer.create, created on an empty layer
      */
     public void testLayerOnEmpty() {
@@ -128,10 +121,11 @@
         ModuleFinder finder
             = ModuleUtils.finderOf(descriptor1, descriptor2, descriptor3);
 
-        Configuration cf = Configuration.resolve(finder,
-                                                 Layer.empty(),
-                                                 ModuleFinder.empty(),
-                                                 "m1");
+        Configuration cf
+            = Configuration.resolve(finder,
+                                    Configuration.empty(),
+                                    ModuleFinder.empty(),
+                                    "m1");
 
         // map each module to its own class loader for this test
         ClassLoader loader1 = new ClassLoader() { };
@@ -142,11 +136,11 @@
         map.put("m2", loader2);
         map.put("m3", loader3);
 
-        Layer layer = Layer.create(cf, map::get);
+        Layer layer = Layer.create(cf, Layer.empty(), map::get);
 
         // configuration
-        assertTrue(layer.configuration().get() == cf);
-        assertTrue(layer.configuration().get().descriptors().size() == 3);
+        assertTrue(layer.configuration() == cf);
+        assertTrue(layer.configuration().descriptors().size() == 3);
 
         // modules
         Set<Module> modules = layer.modules();
@@ -209,18 +203,19 @@
         ModuleFinder finder
             = ModuleUtils.finderOf(descriptor1, descriptor2);
 
-        Configuration cf = Configuration.resolve(finder,
-                Layer.boot(),
-                ModuleFinder.empty(),
-                "m1");
+        Configuration cf
+            = Configuration.resolve(finder,
+                                    Layer.boot().configuration(),
+                                    ModuleFinder.empty(),
+                                    "m1");
 
         ClassLoader loader = new ClassLoader() { };
 
-        Layer layer = Layer.create(cf, mn -> loader);
+        Layer layer = Layer.create(cf, Layer.boot(), mn -> loader);
 
         // configuration
-        assertTrue(layer.configuration().get() == cf);
-        assertTrue(layer.configuration().get().descriptors().size() == 2);
+        assertTrue(layer.configuration() == cf);
+        assertTrue(layer.configuration().descriptors().size() == 2);
 
         // modules
         Set<Module> modules = layer.modules();
@@ -275,16 +270,19 @@
             = ModuleUtils.finderOf(descriptor1, descriptor2);
 
         Configuration cf
-            = Configuration.resolve(finder, Layer.empty(), ModuleFinder.empty(), "m1");
+            = Configuration.resolve(finder,
+                                    Configuration.empty(),
+                                    ModuleFinder.empty(),
+                                    "m1");
         assertTrue(cf.descriptors().size() == 2);
 
         // one loader per module, should be okay
-        Layer.create(cf, mn -> new ClassLoader() { });
+        Layer.create(cf, Layer.empty(), mn -> new ClassLoader() { });
 
         // same class loader
         try {
             ClassLoader loader = new ClassLoader() { };
-            Layer.create(cf, mn -> loader);
+            Layer.create(cf, Layer.empty(), mn -> loader);
             assertTrue(false);
         } catch (LayerInstantiationException expected) { }
     }
@@ -323,12 +321,14 @@
                                    descriptor4);
 
         Configuration cf
-            = Configuration.resolve(finder, Layer.empty(), ModuleFinder.empty(),
+            = Configuration.resolve(finder,
+                                    Configuration.empty(),
+                                    ModuleFinder.empty(),
                                     "m1", "m3");
         assertTrue(cf.descriptors().size() == 4);
 
         // one loader per module
-        Layer.create(cf, mn -> new ClassLoader() {} );
+        Layer.create(cf, Layer.empty(), mn -> new ClassLoader() {} );
 
         // m1 & m2 in one loader, m3 & m4 in another loader
         ClassLoader loader1 = new ClassLoader() { };
@@ -338,12 +338,12 @@
         map.put("m2", loader1);
         map.put("m3", loader2);
         map.put("m3", loader2);
-        Layer.create(cf, map::get);
+        Layer.create(cf, Layer.empty(), map::get);
 
         // same loader
         try {
             ClassLoader loader = new ClassLoader() { };
-            Layer.create(cf, mn -> loader);
+            Layer.create(cf, Layer.empty(), mn -> loader);
             assertTrue(false);
         } catch (LayerInstantiationException expected) { }
     }
@@ -355,6 +355,12 @@
      * in a parent layer.
      */
     public void testConcealSamePackageAsBootLayer() {
+
+        // check assumption that java.base contains sun.launcher
+        ModuleDescriptor base = Object.class.getModule().getDescriptor();
+        assertTrue(base.conceals().contains("sun.launcher"));
+
+
         ModuleDescriptor descriptor
             = new ModuleDescriptor.Builder("m1")
                .requires("java.base")
@@ -363,12 +369,14 @@
 
         ModuleFinder finder = ModuleUtils.finderOf(descriptor);
 
+        Configuration parent = Layer.boot().configuration();
+
         Configuration cf
-            = Configuration.resolve(finder, Layer.boot(), ModuleFinder.empty(), "m1");
+            = Configuration.resolve(finder, parent, ModuleFinder.empty(), "m1");
         assertTrue(cf.descriptors().size() == 1);
 
         ClassLoader loader = new ClassLoader() { };
-        Layer layer = Layer.create(cf, mn -> loader);
+        Layer layer = Layer.create(cf, Layer.boot(), mn -> loader);
         assertTrue(layer.modules().size() == 1);
    }
 
@@ -376,51 +384,55 @@
     /**
      * Test layers with implied readability.
      *
-     * The test consists of two configurations/layers. In configuration cf1
-     * then m1 requires public m2, this becomes layer1. In configuration
-     * cf2 then m3 requires m1, this becomes layer2.
+     * The test consists of three configurations:
+     * - Configuration/layer1: m1, m2 requires public m1
+     * - Configuration/layer2: m3 requires m1
      */
-    public void testLayerWithRequiresPublic1() {
+    public void testImpliedReadabilityWithLayers1() {
 
-        // cf1: m1 and m2, m1 requires public m2
+        // cf1: m1 and m2, m2 requires public m1
 
         ModuleDescriptor descriptor1
             = new ModuleDescriptor.Builder("m1")
-                .requires(ModuleDescriptor.Requires.Modifier.PUBLIC, "m2")
                 .build();
 
         ModuleDescriptor descriptor2
             = new ModuleDescriptor.Builder("m2")
+                .requires(ModuleDescriptor.Requires.Modifier.PUBLIC, "m1")
                 .build();
 
         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2);
 
         Configuration cf1
-            = Configuration.resolve(finder1, boot(), ModuleFinder.empty(), "m1");
+            = Configuration.resolve(finder1,
+                                    Configuration.empty(),
+                                    ModuleFinder.empty(),
+                                    "m2");
 
         ClassLoader cl1 = new ClassLoader() { };
-        Layer layer1 = Layer.create(cf1, mn -> cl1);
+        Layer layer1 = Layer.create(cf1, Layer.empty(), mn -> cl1);
 
-        // cf2: m3, m3 requires m1
+
+        // cf2: m3, m3 requires m2
 
         ModuleDescriptor descriptor3
             = new ModuleDescriptor.Builder("m3")
-                .requires("m1")
+                .requires("m2")
                 .build();
 
         ModuleFinder finder2 = ModuleUtils.finderOf(descriptor3);
 
         Configuration cf2
-            = Configuration.resolve(finder2, layer1, ModuleFinder.empty(), "m3");
+            = Configuration.resolve(finder2, cf1, ModuleFinder.empty(), "m3");
 
         ClassLoader cl2 = new ClassLoader() { };
-        Layer layer2 = Layer.create(cf2, mn -> cl2);
+        Layer layer2 = Layer.create(cf2, layer1, mn -> cl2);
 
-        assertTrue(layer1.parent().get() == Layer.boot());
+        assertTrue(layer1.parent().get() == Layer.empty());
         assertTrue(layer2.parent().get() == layer1);
 
-        Module m1 = layer1.findModule("m1").get();
-        Module m2 = layer1.findModule("m2").get();
+        Module m1 = layer2.findModule("m1").get();
+        Module m2 = layer2.findModule("m2").get();
         Module m3 = layer2.findModule("m3").get();
 
         assertTrue(m1.getLayer() == layer1);
@@ -432,10 +444,10 @@
         assertTrue(m3.getClassLoader() == cl2);
 
         assertTrue(m1.canRead(m1));
-        assertTrue(m1.canRead(m2));
+        assertFalse(m1.canRead(m2));
         assertFalse(m1.canRead(m3));
 
-        assertFalse(m2.canRead(m1));
+        assertTrue(m2.canRead(m1));
         assertTrue(m2.canRead(m2));
         assertFalse(m2.canRead(m3));
 
@@ -448,37 +460,197 @@
     /**
      * Test layers with implied readability.
      *
-     * The test consists of two configurations cf1 and cf2. In configuration
-     * cf1 then m1 requires public m2, this becomes layer1. In configuration
-     * cf2 then m3 requires public m1 and m4 requires m3, this becomes layer2.
+     * The test consists of three configurations:
+     * - Configuration/layer1: m1
+     * - Configuration/layer2: m2 requires public m3, m3 requires m2
      */
-    public void testLayerWithRequiresPublic2() {
+    public void testImpliedReadabilityWithLayers2() {
 
-        // cf1: m1 and m2@1, m1 requires public m2
+        // cf1: m1
 
         ModuleDescriptor descriptor1
             = new ModuleDescriptor.Builder("m1")
-                .requires(ModuleDescriptor.Requires.Modifier.PUBLIC, "m2")
+                .build();
+
+        ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1);
+
+        Configuration cf1
+            = Configuration.resolve(finder1,
+                                    Configuration.empty(),
+                                    ModuleFinder.empty(),
+                                    "m1");
+
+        ClassLoader cl1 = new ClassLoader() { };
+        Layer layer1 = Layer.create(cf1, Layer.empty(), mn -> cl1);
+
+
+        // cf2: m2, m3: m2 requires public m1, m3 requires m2
+
+        ModuleDescriptor descriptor2
+            = new ModuleDescriptor.Builder("m2")
+                .requires(ModuleDescriptor.Requires.Modifier.PUBLIC, "m1")
+                .build();
+
+        ModuleDescriptor descriptor3
+            = new ModuleDescriptor.Builder("m3")
+                .requires("m2")
+                .build();
+
+        ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2, descriptor3);
+
+        Configuration cf2
+            = Configuration.resolve(finder2, cf1, ModuleFinder.empty(), "m3");
+
+        ClassLoader cl2 = new ClassLoader() { };
+        Layer layer2 = Layer.create(cf2, layer1, mn -> cl2);
+
+        assertTrue(layer1.parent().get() == Layer.empty());
+        assertTrue(layer2.parent().get() == layer1);
+
+        Module m1 = layer2.findModule("m1").get();
+        Module m2 = layer2.findModule("m2").get();
+        Module m3 = layer2.findModule("m3").get();
+
+        assertTrue(m1.getLayer() == layer1);
+        assertTrue(m2.getLayer() == layer2);
+        assertTrue(m3.getLayer() == layer2);
+
+        assertTrue(m1.canRead(m1));
+        assertFalse(m1.canRead(m2));
+        assertFalse(m1.canRead(m3));
+
+        assertTrue(m2.canRead(m1));
+        assertTrue(m2.canRead(m2));
+        assertFalse(m2.canRead(m3));
+
+        assertTrue(m3.canRead(m1));
+        assertTrue(m3.canRead(m2));
+        assertTrue(m3.canRead(m3));
+    }
+
+
+    /**
+     * Test layers with implied readability.
+     *
+     * The test consists of three configurations:
+     * - Configuration/layer1: m1
+     * - Configuration/layer2: m2 requires public m1
+     * - Configuration/layer3: m3 requires m1
+     */
+    public void testImpliedReadabilityWithLayers3() {
+
+        // cf1: m1
+
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
+                .build();
+
+        ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1);
+
+        Configuration cf1
+            = Configuration.resolve(finder1,
+                                    Configuration.empty(),
+                                    ModuleFinder.empty(),
+                                    "m1");
+
+        ClassLoader cl1 = new ClassLoader() { };
+        Layer layer1 = Layer.create(cf1, Layer.empty(), mn -> cl1);
+
+
+        // cf2: m2 requires public m1
+
+        ModuleDescriptor descriptor2
+            = new ModuleDescriptor.Builder("m2")
+                .requires(ModuleDescriptor.Requires.Modifier.PUBLIC, "m1")
+                .build();
+
+        ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2);
+
+        Configuration cf2
+            = Configuration.resolve(finder2, cf1, ModuleFinder.empty(), "m2");
+
+        ClassLoader cl2 = new ClassLoader() { };
+        Layer layer2 = Layer.create(cf2, layer1, mn -> cl2);
+
+
+        // cf3: m3 requires m2
+
+        ModuleDescriptor descriptor3
+            = new ModuleDescriptor.Builder("m3")
+                .requires("m2")
+                .build();
+
+        ModuleFinder finder3 = ModuleUtils.finderOf(descriptor3);
+
+        Configuration cf3
+            = Configuration.resolve(finder3, cf2, ModuleFinder.empty(), "m3");
+
+        ClassLoader cl3 = new ClassLoader() { };
+        Layer layer3 = Layer.create(cf3, layer2, mn -> cl3);
+
+        assertTrue(layer1.parent().get() == Layer.empty());
+        assertTrue(layer2.parent().get() == layer1);
+        assertTrue(layer3.parent().get() == layer2);
+
+        Module m1 = layer3.findModule("m1").get();
+        Module m2 = layer3.findModule("m2").get();
+        Module m3 = layer3.findModule("m3").get();
+
+        assertTrue(m1.getLayer() == layer1);
+        assertTrue(m2.getLayer() == layer2);
+        assertTrue(m3.getLayer() == layer3);
+
+        assertTrue(m1.canRead(m1));
+        assertFalse(m1.canRead(m2));
+        assertFalse(m1.canRead(m3));
+
+        assertTrue(m2.canRead(m1));
+        assertTrue(m2.canRead(m2));
+        assertFalse(m2.canRead(m3));
+
+        assertTrue(m3.canRead(m1));
+        assertTrue(m3.canRead(m2));
+        assertTrue(m3.canRead(m3));
+    }
+
+
+    /**
+     * Test layers with implied readability.
+     *
+     * The test consists of two configurations:
+     * - Configuration/layer1: m1, m2 requires public m1
+     * - Configuration/layer2: m3 requires public m2, m4 requires m3
+     */
+    public void testImpliedReadabilityWithLayers4() {
+
+        // cf1: m1, m2 requires public m1
+
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
                 .build();
 
         ModuleDescriptor descriptor2
             = new ModuleDescriptor.Builder("m2")
+                .requires(ModuleDescriptor.Requires.Modifier.PUBLIC, "m1")
                 .build();
 
         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2);
 
         Configuration cf1
-            = Configuration.resolve(finder1, boot(), ModuleFinder.empty(), "m1");
+            = Configuration.resolve(finder1,
+                                    Configuration.empty(),
+                                    ModuleFinder.empty(),
+                                    "m2");
 
         ClassLoader cl1 = new ClassLoader() { };
-        Layer layer1 = Layer.create(cf1, mn -> cl1);
+        Layer layer1 = Layer.create(cf1, Layer.empty(), mn -> cl1);
 
 
-        // cf2: m3 and m4, m4 requires m3, m3 requires public m1
+        // cf2: m3 requires public m2, m4 requires m3
 
         ModuleDescriptor descriptor3
             = new ModuleDescriptor.Builder("m3")
-                .requires(ModuleDescriptor.Requires.Modifier.PUBLIC, "m1")
+                .requires(ModuleDescriptor.Requires.Modifier.PUBLIC, "m2")
                 .build();
 
         ModuleDescriptor descriptor4
@@ -490,16 +662,16 @@
         ModuleFinder finder2 = ModuleUtils.finderOf(descriptor3, descriptor4);
 
         Configuration cf2
-            = Configuration.resolve(finder2, layer1, ModuleFinder.empty(),  "m3", "m4");
+            = Configuration.resolve(finder2, cf1, ModuleFinder.empty(),  "m3", "m4");
 
         ClassLoader cl2 = new ClassLoader() { };
-        Layer layer2 = Layer.create(cf2, mn -> cl2);
+        Layer layer2 = Layer.create(cf2, layer1, mn -> cl2);
 
-        assertTrue(layer1.parent().get() == Layer.boot());
+        assertTrue(layer1.parent().get() == Layer.empty());
         assertTrue(layer2.parent().get() == layer1);
 
-        Module m1 = layer1.findModule("m1").get();
-        Module m2 = layer1.findModule("m2").get();
+        Module m1 = layer2.findModule("m1").get();
+        Module m2 = layer2.findModule("m2").get();
         Module m3 = layer2.findModule("m3").get();
         Module m4 = layer2.findModule("m4").get();
 
@@ -509,14 +681,14 @@
         assertTrue(m4.getLayer() == layer2);
 
         assertTrue(m1.canRead(m1));
-        assertTrue(m1.canRead(m2));
+        assertFalse(m1.canRead(m2));
         assertFalse(m1.canRead(m3));
         assertFalse(m1.canRead(m4));
 
-        assertFalse(m2.canRead(m1));
+        assertTrue(m2.canRead(m1));
         assertTrue(m2.canRead(m2));
-        assertFalse(m2.canRead(m3));
-        assertFalse(m2.canRead(m4));
+        assertFalse(m1.canRead(m3));
+        assertFalse(m1.canRead(m4));
 
         assertTrue(m3.canRead(m1));
         assertTrue(m3.canRead(m2));
@@ -531,92 +703,6 @@
 
 
     /**
-     * Test layers with implied readability.
-     *
-     * The test consists of two configurations cf1 and cf2. In configuration
-     * cf1 then m1 requires public m2@1, this becomes layer1. Configuration cf2
-     * has m2@2 and m3 where m3 requires m1, this becomes layer2.
-     */
-    public void testLayerWithRequiresPublic3() {
-
-        // cf1: m1 and m2@1, m1 requires public m2
-
-        ModuleDescriptor descriptor1
-            = new ModuleDescriptor.Builder("m1")
-                .requires(ModuleDescriptor.Requires.Modifier.PUBLIC, "m2")
-                .build();
-
-        ModuleDescriptor descriptor2_v1
-            = new ModuleDescriptor.Builder("m2")
-                .version("1")
-                .build();
-
-        ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2_v1);
-
-        Configuration cf1
-            = Configuration.resolve(finder1, boot(), ModuleFinder.empty(), "m1");
-
-        ClassLoader cl1 = new ClassLoader() { };
-        Layer layer1 = Layer.create(cf1, mn -> cl1);
-
-
-        // cf2: m3 and m2@2, m3 requires m1
-
-        ModuleDescriptor descriptor2_v2
-            = new ModuleDescriptor.Builder("m2")
-                .version("2")
-                .build();
-
-        ModuleDescriptor descriptor3
-            = new ModuleDescriptor.Builder("m3")
-                .requires("m1")
-                .build();
-
-        ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2_v2, descriptor3);
-
-        Configuration cf2
-            = Configuration.resolve(finder2, layer1, ModuleFinder.empty(), "m2", "m3");
-
-        ClassLoader cl2 = new ClassLoader() { };
-        Layer layer2 = Layer.create(cf2, mn -> cl2);
-
-        assertTrue(layer1.parent().get() == Layer.boot());
-        assertTrue(layer2.parent().get() == layer1);
-
-        Module m1 = layer1.findModule("m1").get();
-        Module m2_v1 = layer1.findModule("m2").get();
-        Module m2_v2 = layer2.findModule("m2").get();
-        Module m3 = layer2.findModule("m3").get();
-
-        assertTrue(m1.getLayer() == layer1);
-        assertTrue(m2_v1.getLayer() == layer1);
-
-        assertTrue(m2_v2.getLayer() == layer2);
-        assertTrue(m3.getLayer() == layer2);
-
-        assertTrue(m1.canRead(m1));
-        assertTrue(m1.canRead(m2_v1));
-        assertFalse(m1.canRead(m2_v2));
-        assertFalse(m1.canRead(m3));
-
-        assertFalse(m2_v1.canRead(m1));
-        assertTrue(m2_v1.canRead(m2_v1));
-        assertFalse(m2_v1.canRead(m2_v2));
-        assertFalse(m2_v1.canRead(m3));
-
-        assertFalse(m2_v2.canRead(m1));
-        assertFalse(m2_v2.canRead(m2_v1));
-        assertTrue(m2_v2.canRead(m2_v2));
-        assertFalse(m2_v2.canRead(m3));
-
-        assertTrue(m3.canRead(m1));
-        assertTrue(m3.canRead(m2_v1));
-        assertFalse(m3.canRead(m2_v2));
-        assertTrue(m3.canRead(m3));
-    }
-
-
-    /**
      * Attempt to use Layer.create to create a layer with a module defined to a
      * class loader that already has a module of the same name defined to the
      * class loader.
@@ -631,18 +717,17 @@
 
         ModuleFinder finder = ModuleUtils.finderOf(md);
 
+        Configuration parent = Layer.boot().configuration();
+
         Configuration cf
-            = Configuration.resolve(finder,
-                Layer.boot(),
-                ModuleFinder.empty(),
-                "m");
+            = Configuration.resolve(finder, parent, ModuleFinder.empty(), "m");
 
         ClassLoader loader = new ClassLoader() { };
 
-        Layer.create(cf, mn -> loader);
+        Layer.create(cf, Layer.boot(), mn -> loader);
 
         // should throw LayerInstantiationException as m1 already defined to loader
-        Layer.create(cf, mn -> loader);
+        Layer.create(cf, Layer.boot(), mn -> loader);
 
     }
 
@@ -673,18 +758,20 @@
 
         // define m1 containing package p to class loader
 
+        Configuration parent = Layer.boot().configuration();
+
         Configuration cf1
-            = Configuration.resolve(finder, Layer.boot(), ModuleFinder.empty(), "m1");
+            = Configuration.resolve(finder, parent, ModuleFinder.empty(), "m1");
 
-        Layer layer1 = Layer.create(cf1, mn -> loader);
+        Layer layer1 = Layer.create(cf1, Layer.boot(), mn -> loader);
 
         // attempt to define m2 containing package p to class loader
 
         Configuration cf2
-            = Configuration.resolve(finder, Layer.boot(), ModuleFinder.empty(), "m2");
+            = Configuration.resolve(finder, parent, ModuleFinder.empty(), "m2");
 
         // should throw exception because p already in m1
-        Layer layer2 = Layer.create(cf2, mn -> loader);
+        Layer layer2 = Layer.create(cf2, Layer.boot(), mn -> loader);
 
     }
 
@@ -701,7 +788,7 @@
 
         ModuleDescriptor md
             = new ModuleDescriptor.Builder("m")
-                .conceals(packageName(c))
+                .conceals(c.getPackageName())
                 .requires("java.base")
                 .build();
 
@@ -709,17 +796,99 @@
 
         Configuration cf
             = Configuration.resolve(finder,
-                Layer.boot(),
-                ModuleFinder.empty(),
-                "m");
+                                    Layer.boot().configuration(),
+                                    ModuleFinder.empty(),
+                                    "m");
 
-        Layer.create(cf, mn -> c.getClassLoader());
+        Layer.create(cf, Layer.boot(), mn -> c.getClassLoader());
     }
 
-    private static String packageName(Class<?> c) {
-        String cn = c.getName();
-        int dot = cn.lastIndexOf('.');
-        return cn.substring(0, dot);
+
+    /**
+     * Parent of configuration != configuration of parent Layer
+     */
+    @Test(expectedExceptions = { IllegalArgumentException.class })
+    public void testIncorrectParent1() {
+
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
+                .requires("java.base")
+                .build();
+
+        ModuleFinder finder = ModuleUtils.finderOf(descriptor1);
+
+        Configuration cf
+            = Configuration.resolve(finder,
+                                    Layer.boot().configuration(),
+                                    ModuleFinder.empty(),
+                                    "m1");
+
+        ClassLoader loader = new ClassLoader() { };
+        Layer.create(cf, Layer.empty(), mn -> loader);
+    }
+
+
+    /**
+     * Parent of configuration != configuration of parent Layer
+     */
+    @Test(expectedExceptions = { IllegalArgumentException.class })
+    public void testIncorrectParent2() {
+
+        ModuleDescriptor descriptor1
+            = new ModuleDescriptor.Builder("m1")
+                .build();
+
+        ModuleFinder finder = ModuleUtils.finderOf(descriptor1);
+
+        Configuration cf
+            = Configuration.resolve(finder,
+                                    Configuration.empty(),
+                                    ModuleFinder.empty(),
+                                    "m1");
+
+        ClassLoader loader = new ClassLoader() { };
+        Layer.create(cf, Layer.boot(), mn -> loader);
+    }
+
+
+    // null handling
+
+    @Test(expectedExceptions = { NullPointerException.class })
+    public void testCreateWithNull1() {
+        ClassLoader loader = new ClassLoader() { };
+        Layer.create(null, Layer.empty(), mn -> loader);
+
+    }
+
+    @Test(expectedExceptions = { NullPointerException.class })
+    public void testCreateWithNull2() {
+        ClassLoader loader = new ClassLoader() { };
+        Layer.create(Configuration.empty(), null, mn -> loader);
+
+    }
+
+    @Test(expectedExceptions = { NullPointerException.class })
+    public void testCreateWithNull3() {
+        Layer.create(Configuration.empty(), Layer.empty(), null);
+    }
+
+    @Test(expectedExceptions = { NullPointerException.class })
+    public void testFindModuleWithNull() {
+        Layer.boot().findModule(null);
+    }
+
+    @Test(expectedExceptions = { NullPointerException.class })
+    public void testFindLoaderWithNull() {
+        Layer.boot().findLoader(null);
+    }
+
+
+    // immutable sets
+
+    @Test(expectedExceptions = { UnsupportedOperationException.class })
+    public void testImmutableSet() {
+        Module base = Object.class.getModule();
+        Layer.boot().modules().add(base);
     }
 
 }
--- a/test/jdk/jigsaw/reflect/Module/BasicModuleTest.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/test/jdk/jigsaw/reflect/Module/BasicModuleTest.java	Fri Nov 27 15:34:17 2015 +0000
@@ -45,7 +45,7 @@
      */
     private void testReadsAllBootModules(Module m) {
         Layer bootLayer = Layer.boot();
-        bootLayer.configuration().get()
+        bootLayer.configuration()
             .descriptors()
             .stream()
             .map(ModuleDescriptor::name)
--- a/test/jdk/jigsaw/reflect/Proxy/ProxyClassAccessTest.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/test/jdk/jigsaw/reflect/Proxy/ProxyClassAccessTest.java	Fri Nov 27 15:34:17 2015 +0000
@@ -91,11 +91,11 @@
     public void testNoReadAccess() throws Exception {
         ModuleFinder finder = ModuleFinder.of(MODS_DIR);
         Configuration cf = Configuration
-                .resolve(ModuleFinder.empty(), Layer.boot(), finder, modules).bind();
+                .resolve(ModuleFinder.empty(), Layer.boot().configuration(), finder, modules).bind();
 
         ClassLoader parent = this.getClass().getClassLoader();
         ClassLoader loader = new ModuleClassLoader(parent, cf);
-        Layer layer = Layer.create(cf, mn -> loader);
+        Layer layer = Layer.create(cf, Layer.boot(), mn -> loader);
         Class<?>[] interfaces = new Class<?>[] {
                 Class.forName("p.one.I", false, loader),
                 Class.forName("q.NP", false, loader)     // non-public interface in unnamed module
--- a/test/jdk/jigsaw/reflect/Proxy/ProxyLayerTest.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/test/jdk/jigsaw/reflect/Proxy/ProxyLayerTest.java	Fri Nov 27 15:34:17 2015 +0000
@@ -50,6 +50,8 @@
 
 public class ProxyLayerTest {
 
+    private final Configuration BOOT_CONFIGURATION = Layer.boot().configuration();
+
     private static final String TEST_SRC = System.getProperty("test.src");
     private static final String TEST_CLASSES = System.getProperty("test.classes");
 
@@ -80,11 +82,11 @@
     public void testProxyInUnnamed() throws Exception {
         ModuleFinder finder = ModuleFinder.of(MODS_DIR);
         Configuration cf = Configuration
-                .resolve(ModuleFinder.empty(), Layer.boot(), finder, modules)
+                .resolve(ModuleFinder.empty(), BOOT_CONFIGURATION, finder, modules)
                 .bind();
 
         ClassLoader loader = new ModuleClassLoader(cf);
-        Layer layer = Layer.create(cf, mn -> loader);
+        Layer layer = Layer.create(cf, Layer.boot(), mn -> loader);
 
         assertTrue(layer.findModule("m1").isPresent());
         assertTrue(layer.findModule("m2").isPresent());
@@ -112,10 +114,10 @@
     public void testProxyInDynamicModule() throws Exception {
         ModuleFinder finder = ModuleFinder.of(MODS_DIR);
         Configuration cf = Configuration
-                .resolve(ModuleFinder.empty(), Layer.boot(), finder, modules).bind();
+                .resolve(ModuleFinder.empty(), BOOT_CONFIGURATION, finder, modules).bind();
 
         ClassLoader loader = new ModuleClassLoader(cf);
-        Layer layer = Layer.create(cf, mn -> loader);
+        Layer layer = Layer.create(cf, Layer.boot(), mn -> loader);
 
         assertTrue(layer.findModule("m1").isPresent());
         assertTrue(layer.findModule("m2").isPresent());
@@ -139,10 +141,10 @@
     public void testNoReadAccess() throws Exception {
         ModuleFinder finder = ModuleFinder.of(MODS_DIR);
         Configuration cf = Configuration
-                .resolve(ModuleFinder.empty(), Layer.boot(), finder, modules).bind();
+                .resolve(ModuleFinder.empty(), BOOT_CONFIGURATION, finder, modules).bind();
 
         ClassLoader loader = new ModuleClassLoader(cf);
-        Layer layer = Layer.create(cf, mn -> loader);
+        Layer layer = Layer.create(cf, Layer.boot(), mn -> loader);
 
         assertTrue(layer.findModule("m1").isPresent());
         assertTrue(layer.findModule("m2").isPresent());
--- a/test/jdk/jigsaw/scenarios/automaticmodules/src/basictest/test/Main.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/test/jdk/jigsaw/scenarios/automaticmodules/src/basictest/test/Main.java	Fri Nov 27 15:34:17 2015 +0000
@@ -46,7 +46,7 @@
 
         // and read all modules in the boot Layer
         Layer layer = Layer.boot();
-        layer.configuration().get().descriptors().stream()
+        layer.configuration().descriptors().stream()
                 .map(ModuleDescriptor::name)
                 .map(layer::findModule)
                 .forEach(om -> assertTrue(httpModule.canRead(om.get())));
--- a/test/jdk/jigsaw/scenarios/container/src/container/container/Main.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/test/jdk/jigsaw/scenarios/container/src/container/container/Main.java	Fri Nov 27 15:34:17 2015 +0000
@@ -42,7 +42,7 @@
 
         System.out.println("Boot layer");
         Layer.boot()
-             .configuration().get()
+             .configuration()
              .descriptors()
              .stream()
              .map(ModuleDescriptor::name)
@@ -66,12 +66,10 @@
             paths[i++] = Paths.get(dir);
         }
 
-        Layer bootLayer = Layer.boot();
-
         ModuleFinder finder = ModuleFinder.of(paths);
 
         Configuration cf = Configuration.resolve(finder,
-                bootLayer,
+                Layer.boot().configuration(),
                 ModuleFinder.empty(),
                 appModuleName);
         cf = cf.bind();
@@ -84,7 +82,7 @@
         ModuleClassLoader loader = new ModuleClassLoader(cf);
 
         // reify the configuration as a Layer
-        Layer layer = Layer.create(cf, mn -> loader);
+        Layer layer = Layer.create(cf, Layer.boot(), mn -> loader);
 
         // invoke application main method
         Class<?> c = layer.findLoader(appModuleName).loadClass(appMainClass);
--- a/test/jdk/jigsaw/util/ServiceLoader/ServicesTest.java	Thu Nov 26 19:33:24 2015 +0000
+++ b/test/jdk/jigsaw/util/ServiceLoader/ServicesTest.java	Fri Nov 27 15:34:17 2015 +0000
@@ -154,9 +154,9 @@
         // create a custom Layer
         ModuleFinder finder = ModuleFinder.of(MODS_DIR);
         Configuration cf = Configuration
-            .resolve(finder, Layer.boot(), ModuleFinder.empty()).bind();
+            .resolve(finder, Layer.boot().configuration(), ModuleFinder.empty()).bind();
         ClassLoader loader = new ModuleClassLoader(cf);
-        Layer layer = Layer.create(cf, mn -> loader);
+        Layer layer = Layer.create(cf, Layer.boot(), mn -> loader);
         assertTrue(layer.findModule("bananascript").isPresent());
 
         sl = ServiceLoader.load(layer, ScriptEngineFactory.class);