8187443: Forest Consolidation: Move files to unified layout

Reviewed-by: darcy, ihse
This commit is contained in:
Erik Joelsson 2017-09-12 19:03:39 +02:00
parent 270fe13182
commit 3789983e89
56923 changed files with 3 additions and 15727 deletions

View file

@ -0,0 +1,619 @@
/*
* Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.module;
import java.io.PrintStream;
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.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.internal.module.ModuleReferenceImpl;
import jdk.internal.module.ModuleTarget;
/**
* A configuration that is the result of <a href="package-summary.html#resolution">
* resolution</a> or resolution with
* <a href="{@docRoot}/java/lang/module/Configuration.html#service-binding">service binding</a>.
*
* <p> A configuration encapsulates the <em>readability graph</em> that is the
* output of resolution. A readability graph is a directed graph whose vertices
* are of type {@link ResolvedModule} and the edges represent the readability
* amongst the modules. {@code Configuration} defines the {@link #modules()
* modules()} method to get the set of resolved modules in the graph. {@code
* ResolvedModule} defines the {@link ResolvedModule#reads() reads()} method to
* get the set of modules that a resolved module reads. The modules that are
* read may be in the same configuration or may be in {@link #parents() parent}
* configurations. </p>
*
* <p> Configuration defines the {@link #resolve(ModuleFinder,List,ModuleFinder,Collection)
* resolve} method to resolve a collection of root modules, and the {@link
* #resolveAndBind(ModuleFinder,List,ModuleFinder,Collection) resolveAndBind}
* method to do resolution with service binding. There are instance and
* static variants of both methods. The instance methods create a configuration
* with the receiver as the parent configuration. The static methods are for
* more advanced cases where there can be more than one parent configuration. </p>
*
* <p> Each {@link java.lang.ModuleLayer layer} of modules in the Java virtual
* machine is created from a configuration. The configuration for the {@link
* java.lang.ModuleLayer#boot() boot} layer is obtained by invoking {@code
* ModuleLayer.boot().configuration()}. The configuration for the boot layer
* will often be the parent when creating new configurations. </p>
*
* <h3> Example </h3>
*
* <p> The following example uses the {@link
* #resolve(ModuleFinder,ModuleFinder,Collection) resolve} method to resolve a
* module named <em>myapp</em> with the configuration for the boot layer as the
* parent configuration. It prints the name of each resolved module and the
* names of the modules that each module reads. </p>
*
* <pre>{@code
* ModuleFinder finder = ModuleFinder.of(dir1, dir2, dir3);
*
* Configuration parent = ModuleLayer.boot().configuration();
*
* Configuration cf = parent.resolve(finder, ModuleFinder.of(), Set.of("myapp"));
* cf.modules().forEach(m -> {
* System.out.format("%s -> %s%n",
* m.name(),
* m.reads().stream()
* .map(ResolvedModule::name)
* .collect(Collectors.joining(", ")));
* });
* }</pre>
*
* @since 9
* @spec JPMS
* @see java.lang.ModuleLayer
*/
public final class Configuration {
// @see Configuration#empty()
private static final Configuration EMPTY_CONFIGURATION = new Configuration();
// parent configurations, in search order
private final List<Configuration> parents;
private final Map<ResolvedModule, Set<ResolvedModule>> graph;
private final Set<ResolvedModule> modules;
private final Map<String, ResolvedModule> nameToModule;
// constraint on target platform
private final String targetPlatform;
String targetPlatform() { return targetPlatform; }
private Configuration() {
this.parents = Collections.emptyList();
this.graph = Collections.emptyMap();
this.modules = Collections.emptySet();
this.nameToModule = Collections.emptyMap();
this.targetPlatform = null;
}
private Configuration(List<Configuration> parents, Resolver resolver) {
Map<ResolvedModule, Set<ResolvedModule>> g = resolver.finish(this);
@SuppressWarnings(value = {"rawtypes", "unchecked"})
Entry<String, ResolvedModule>[] nameEntries
= (Entry<String, ResolvedModule>[])new Entry[g.size()];
ResolvedModule[] moduleArray = new ResolvedModule[g.size()];
int i = 0;
for (ResolvedModule resolvedModule : g.keySet()) {
moduleArray[i] = resolvedModule;
nameEntries[i] = Map.entry(resolvedModule.name(), resolvedModule);
i++;
}
this.parents = Collections.unmodifiableList(parents);
this.graph = g;
this.modules = Set.of(moduleArray);
this.nameToModule = Map.ofEntries(nameEntries);
this.targetPlatform = resolver.targetPlatform();
}
/**
* Creates the Configuration for the boot layer from a pre-generated
* readability graph.
*
* @apiNote This method is coded for startup performance.
*/
Configuration(ModuleFinder finder, Map<String, Set<String>> map) {
int moduleCount = map.size();
// create map of name -> ResolvedModule
@SuppressWarnings(value = {"rawtypes", "unchecked"})
Entry<String, ResolvedModule>[] nameEntries
= (Entry<String, ResolvedModule>[])new Entry[moduleCount];
ResolvedModule[] moduleArray = new ResolvedModule[moduleCount];
String targetPlatform = null;
int i = 0;
for (String name : map.keySet()) {
ModuleReference mref = finder.find(name).orElse(null);
assert mref != null;
if (targetPlatform == null && mref instanceof ModuleReferenceImpl) {
ModuleTarget target = ((ModuleReferenceImpl)mref).moduleTarget();
if (target != null) {
targetPlatform = target.targetPlatform();
}
}
ResolvedModule resolvedModule = new ResolvedModule(this, mref);
moduleArray[i] = resolvedModule;
nameEntries[i] = Map.entry(name, resolvedModule);
i++;
}
Map<String, ResolvedModule> nameToModule = Map.ofEntries(nameEntries);
// create entries for readability graph
@SuppressWarnings(value = {"rawtypes", "unchecked"})
Entry<ResolvedModule, Set<ResolvedModule>>[] moduleEntries
= (Entry<ResolvedModule, Set<ResolvedModule>>[])new Entry[moduleCount];
i = 0;
for (ResolvedModule resolvedModule : moduleArray) {
Set<String> names = map.get(resolvedModule.name());
ResolvedModule[] readsArray = new ResolvedModule[names.size()];
int j = 0;
for (String name : names) {
readsArray[j++] = nameToModule.get(name);
}
moduleEntries[i++] = Map.entry(resolvedModule, Set.of(readsArray));
}
this.parents = List.of(empty());
this.graph = Map.ofEntries(moduleEntries);
this.modules = Set.of(moduleArray);
this.nameToModule = nameToModule;
this.targetPlatform = targetPlatform;
}
/**
* Resolves a collection of root modules, with this configuration as its
* parent, to create a new configuration. This method works exactly as
* specified by the static {@link
* #resolve(ModuleFinder,List,ModuleFinder,Collection) resolve}
* method when invoked with this configuration as the parent. In other words,
* if this configuration is {@code cf} then this method is equivalent to
* invoking:
* <pre> {@code
* Configuration.resolve(before, List.of(cf), after, roots);
* }</pre>
*
* @param before
* The <em>before</em> module finder to find modules
* @param after
* The <em>after</em> module finder to locate modules when not
* located by the {@code before} module finder or in parent
* configurations
* @param roots
* The possibly-empty collection of module names of the modules
* to resolve
*
* @return The configuration that is the result of resolving the given
* root modules
*
* @throws FindException
* If resolution fails for any of the observability-related reasons
* specified by the static {@code resolve} method
* @throws ResolutionException
* If resolution fails any of the consistency checks specified by
* the static {@code resolve} method
* @throws SecurityException
* If locating a module is denied by the security manager
*/
public Configuration resolve(ModuleFinder before,
ModuleFinder after,
Collection<String> roots)
{
return resolve(before, List.of(this), after, roots);
}
/**
* Resolves a collection of root modules, with service binding, and with
* this configuration as its parent, to create a new configuration.
* This method works exactly as specified by the static {@link
* #resolveAndBind(ModuleFinder,List,ModuleFinder,Collection)
* resolveAndBind} method when invoked with this configuration
* as the parent. In other words, if this configuration is {@code cf} then
* this method is equivalent to invoking:
* <pre> {@code
* Configuration.resolveAndBind(before, List.of(cf), after, roots);
* }</pre>
*
*
* @param before
* The <em>before</em> module finder to find modules
* @param after
* The <em>after</em> module finder to locate modules when not
* located by the {@code before} module finder or in parent
* configurations
* @param roots
* The possibly-empty collection of module names of the modules
* to resolve
*
* @return The configuration that is the result of resolving, with service
* binding, the given root modules
*
* @throws FindException
* If resolution fails for any of the observability-related reasons
* specified by the static {@code resolve} method
* @throws ResolutionException
* If resolution fails any of the consistency checks specified by
* the static {@code resolve} method
* @throws SecurityException
* If locating a module is denied by the security manager
*/
public Configuration resolveAndBind(ModuleFinder before,
ModuleFinder after,
Collection<String> roots)
{
return resolveAndBind(before, List.of(this), after, roots);
}
/**
* Resolves a collection of root modules, with service binding, and with
* the empty configuration as its parent.
*
* This method is used to create the configuration for the boot layer.
*/
static Configuration resolveAndBind(ModuleFinder finder,
Collection<String> roots,
PrintStream traceOutput)
{
List<Configuration> parents = List.of(empty());
Resolver resolver = new Resolver(finder, parents, ModuleFinder.of(), traceOutput);
resolver.resolve(roots).bind();
return new Configuration(parents, resolver);
}
/**
* Resolves a collection of root modules to create a configuration.
*
* <p> Each root module is located using the given {@code before} module
* finder. If a module is not found then it is located in the parent
* configuration as if by invoking the {@link #findModule(String)
* findModule} method on each parent in iteration order. If not found then
* the module is located using the given {@code after} module finder. The
* same search order is used to locate transitive dependences. Root modules
* or dependences that are located in a parent configuration are resolved
* no further and are not included in the resulting configuration. </p>
*
* <p> When all modules have been enumerated then a readability graph
* is computed, and in conjunction with the module exports and service use,
* checked for consistency. </p>
*
* <p> Resolution may fail with {@code FindException} for the following
* <em>observability-related</em> reasons: </p>
*
* <ul>
*
* <li><p> A root module, or a direct or transitive dependency, is not
* found. </p></li>
*
* <li><p> An error occurs when attempting to find a module.
* Possible errors include I/O errors, errors detected parsing a module
* descriptor ({@code module-info.class}) or two versions of the same
* module are found in the same directory. </p></li>
*
* </ul>
*
* <p> Resolution may fail with {@code ResolutionException} if any of the
* following consistency checks fail: </p>
*
* <ul>
*
* <li><p> A cycle is detected, say where module {@code m1} requires
* module {@code m2} and {@code m2} requires {@code m1}. </p></li>
*
* <li><p> A module reads two or more modules with the same name. This
* includes the case where a module reads another with the same name as
* itself. </p></li>
*
* <li><p> Two or more modules in the configuration export the same
* package to a module that reads both. This includes the case where a
* module {@code M} containing package {@code p} reads another module
* that exports {@code p} to {@code M}. </p></li>
*
* <li><p> A module {@code M} declares that it "{@code uses p.S}" or
* "{@code provides p.S with ...}" but package {@code p} is neither in
* module {@code M} nor exported to {@code M} by any module that
* {@code M} reads. </p></li>
*
* </ul>
*
* @implNote In the implementation then observability of modules may depend
* on referential integrity or other checks that ensure different builds of
* tightly coupled modules or modules for specific operating systems or
* architectures are not combined in the same configuration.
*
* @param before
* The <em>before</em> module finder to find modules
* @param parents
* The list parent configurations in search order
* @param after
* The <em>after</em> module finder to locate modules when not
* located by the {@code before} module finder or in parent
* configurations
* @param roots
* The possibly-empty collection of module names of the modules
* to resolve
*
* @return The configuration that is the result of resolving the given
* root modules
*
* @throws FindException
* If resolution fails for any of observability-related reasons
* specified above
* @throws ResolutionException
* If resolution fails for any of the consistency checks specified
* above
* @throws IllegalArgumentException
* If the list of parents is empty, or the list has two or more
* parents with modules for different target operating systems,
* architectures, or versions
*
* @throws SecurityException
* If locating a module is denied by the security manager
*/
public static Configuration resolve(ModuleFinder before,
List<Configuration> parents,
ModuleFinder after,
Collection<String> roots)
{
Objects.requireNonNull(before);
Objects.requireNonNull(after);
Objects.requireNonNull(roots);
List<Configuration> parentList = new ArrayList<>(parents);
if (parentList.isEmpty())
throw new IllegalArgumentException("'parents' is empty");
Resolver resolver = new Resolver(before, parentList, after, null);
resolver.resolve(roots);
return new Configuration(parentList, resolver);
}
/**
* Resolves a collection of root modules, with service binding, to create
* configuration.
*
* <p> This method works exactly as specified by {@link
* #resolve(ModuleFinder,List,ModuleFinder,Collection)
* resolve} except that the graph of resolved modules is augmented
* with modules induced by the service-use dependence relation. </p>
*
* <p><a id="service-binding"></a>More specifically, the root modules are
* resolved as if by calling {@code resolve}. The resolved modules, and
* all modules in the parent configurations, with {@link ModuleDescriptor#uses()
* service dependences} are then examined. All modules found by the given
* module finders that {@link ModuleDescriptor#provides() provide} an
* implementation of one or more of the service types are added to the
* module graph and then resolved as if by calling the {@code
* resolve} method. Adding modules to the module graph may introduce new
* service-use dependences and so the process works iteratively until no
* more modules are added. </p>
*
* <p> As service binding involves resolution then it may fail with {@code
* FindException} or {@code ResolutionException} for exactly the same
* reasons specified in {@code resolve}. </p>
*
* @param before
* The <em>before</em> module finder to find modules
* @param parents
* The list parent configurations in search order
* @param after
* The <em>after</em> module finder to locate modules when not
* located by the {@code before} module finder or in parent
* configurations
* @param roots
* The possibly-empty collection of module names of the modules
* to resolve
*
* @return The configuration that is the result of resolving, with service
* binding, the given root modules
*
* @throws FindException
* If resolution fails for any of the observability-related reasons
* specified by the static {@code resolve} method
* @throws ResolutionException
* If resolution fails any of the consistency checks specified by
* the static {@code resolve} method
* @throws IllegalArgumentException
* If the list of parents is empty, or the list has two or more
* parents with modules for different target operating systems,
* architectures, or versions
* @throws SecurityException
* If locating a module is denied by the security manager
*/
public static Configuration resolveAndBind(ModuleFinder before,
List<Configuration> parents,
ModuleFinder after,
Collection<String> roots)
{
Objects.requireNonNull(before);
Objects.requireNonNull(after);
Objects.requireNonNull(roots);
List<Configuration> parentList = new ArrayList<>(parents);
if (parentList.isEmpty())
throw new IllegalArgumentException("'parents' is empty");
Resolver resolver = new Resolver(before, parentList, after, null);
resolver.resolve(roots).bind();
return new Configuration(parentList, resolver);
}
/**
* Returns the <em>empty</em> configuration. There are no modules in the
* empty configuration. It has no parents.
*
* @return The empty configuration
*/
public static Configuration empty() {
return EMPTY_CONFIGURATION;
}
/**
* Returns an unmodifiable list of this configuration's parents, in search
* order. If this is the {@linkplain #empty empty configuration} then an
* empty list is returned.
*
* @return A possibly-empty unmodifiable list of this parent configurations
*/
public List<Configuration> parents() {
return parents;
}
/**
* Returns an immutable set of the resolved modules in this configuration.
*
* @return A possibly-empty unmodifiable set of the resolved modules
* in this configuration
*/
public Set<ResolvedModule> modules() {
return modules;
}
/**
* Finds a resolved module in this configuration, or if not in this
* configuration, the {@linkplain #parents() parent} configurations.
* Finding a module in parent configurations is equivalent to invoking
* {@code findModule} on each parent, in search order, until the module
* is found or all parents have been searched. In a <em>tree of
* configurations</em> then this is equivalent to a depth-first search.
*
* @param name
* The module name of the resolved module to find
*
* @return The resolved 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 configurations
*/
public Optional<ResolvedModule> findModule(String name) {
Objects.requireNonNull(name);
ResolvedModule m = nameToModule.get(name);
if (m != null)
return Optional.of(m);
if (!parents.isEmpty()) {
return configurations()
.skip(1) // skip this configuration
.map(cf -> cf.nameToModule)
.filter(map -> map.containsKey(name))
.map(map -> map.get(name))
.findFirst();
}
return Optional.empty();
}
Set<ModuleDescriptor> descriptors() {
if (modules.isEmpty()) {
return Collections.emptySet();
} else {
return modules.stream()
.map(ResolvedModule::reference)
.map(ModuleReference::descriptor)
.collect(Collectors.toSet());
}
}
Set<ResolvedModule> reads(ResolvedModule m) {
return Collections.unmodifiableSet(graph.get(m));
}
/**
* Returns an ordered stream of configurations. The first element is this
* configuration, the remaining elements are the parent configurations
* in DFS order.
*
* @implNote For now, the assumption is that the number of elements will
* be very low and so this method does not use a specialized spliterator.
*/
Stream<Configuration> configurations() {
List<Configuration> allConfigurations = this.allConfigurations;
if (allConfigurations == null) {
allConfigurations = new ArrayList<>();
Set<Configuration> visited = new HashSet<>();
Deque<Configuration> stack = new ArrayDeque<>();
visited.add(this);
stack.push(this);
while (!stack.isEmpty()) {
Configuration layer = stack.pop();
allConfigurations.add(layer);
// push in reverse order
for (int i = layer.parents.size() - 1; i >= 0; i--) {
Configuration parent = layer.parents.get(i);
if (!visited.contains(parent)) {
visited.add(parent);
stack.push(parent);
}
}
}
this.allConfigurations = Collections.unmodifiableList(allConfigurations);
}
return allConfigurations.stream();
}
private volatile List<Configuration> allConfigurations;
/**
* Returns a string describing this configuration.
*
* @return A possibly empty string describing this configuration
*/
@Override
public String toString() {
return modules().stream()
.map(ResolvedModule::name)
.collect(Collectors.joining(", "));
}
}

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.module;
/**
* Thrown by a {@link ModuleFinder ModuleFinder} when an error occurs finding
* a module. Also thrown by {@link
* Configuration#resolve(ModuleFinder,java.util.List,ModuleFinder,java.util.Collection)
* Configuration.resolve} when resolution fails for observability-related
* reasons.
*
* @since 9
* @spec JPMS
*/
public class FindException extends RuntimeException {
private static final long serialVersionUID = -5817081036963388391L;
/**
* Constructs a {@code FindException} with no detail message.
*/
public FindException() {
}
/**
* Constructs a {@code FindException} with the given detail
* message.
*
* @param msg
* The detail message; can be {@code null}
*/
public FindException(String msg) {
super(msg);
}
/**
* Constructs a {@code FindException} with the given cause.
*
* @param cause
* The cause; can be {@code null}
*/
public FindException(Throwable cause) {
super(cause);
}
/**
* Constructs a {@code FindException} with the given detail message
* and cause.
*
* @param msg
* The detail message; can be {@code null}
* @param cause
* The cause; can be {@code null}
*/
public FindException(String msg, Throwable cause) {
super(msg, cause);
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.module;
/**
* Thrown when reading a module descriptor and the module descriptor is found
* to be malformed or otherwise cannot be interpreted as a module descriptor.
*
* @see ModuleDescriptor#read
* @since 9
* @spec JPMS
*/
public class InvalidModuleDescriptorException extends RuntimeException {
private static final long serialVersionUID = 4863390386809347380L;
/**
* Constructs an {@code InvalidModuleDescriptorException} with no detail
* message.
*/
public InvalidModuleDescriptorException() {
}
/**
* Constructs an {@code InvalidModuleDescriptorException} with the
* specified detail message.
*
* @param msg
* The detail message; can be {@code null}
*/
public InvalidModuleDescriptorException(String msg) {
super(msg);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,381 @@
/*
* Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.module;
import java.nio.file.Path;
import java.security.AccessController;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import jdk.internal.module.ModulePath;
import jdk.internal.module.SystemModuleFinders;
/**
* A finder of modules. A {@code ModuleFinder} is used to find modules during
* <a href="package-summary.html#resolution">resolution</a> or
* <a href="Configuration.html#service-binding">service binding</a>.
*
* <p> A {@code ModuleFinder} can only find one module with a given name. A
* {@code ModuleFinder} that finds modules in a sequence of directories, for
* example, will locate the first occurrence of a module of a given name and
* will ignore other modules of that name that appear in directories later in
* the sequence. </p>
*
* <p> Example usage: </p>
*
* <pre>{@code
* Path dir1, dir2, dir3;
*
* ModuleFinder finder = ModuleFinder.of(dir1, dir2, dir3);
*
* Optional<ModuleReference> omref = finder.find("jdk.foo");
* omref.ifPresent(mref -> ... );
*
* }</pre>
*
* <p> The {@link #find(String) find} and {@link #findAll() findAll} methods
* defined here can fail for several reasons. These include I/O errors, errors
* detected parsing a module descriptor ({@code module-info.class}), or in the
* case of {@code ModuleFinder} returned by {@link #of ModuleFinder.of}, that
* two or more modules with the same name are found in a directory.
* When an error is detected then these methods throw {@link FindException
* FindException} with an appropriate {@link Throwable#getCause cause}.
* The behavior of a {@code ModuleFinder} after a {@code FindException} is
* thrown is undefined. For example, invoking {@code find} after an exception
* is thrown may or may not scan the same modules that lead to the exception.
* It is recommended that a module finder be discarded after an exception is
* thrown. </p>
*
* <p> A {@code ModuleFinder} is not required to be thread safe. </p>
*
* @since 9
* @spec JPMS
*/
public interface ModuleFinder {
/**
* Finds a reference to a module of a given name.
*
* <p> A {@code ModuleFinder} provides a consistent view of the
* modules that it locates. If {@code find} is invoked several times to
* locate the same module (by name) then it will return the same result
* each time. If a module is located then it is guaranteed to be a member
* of the set of modules returned by the {@link #findAll() findAll}
* method. </p>
*
* @param name
* The name of the module to find
*
* @return A reference to a module with the given name or an empty
* {@code Optional} if not found
*
* @throws FindException
* If an error occurs finding the module
*
* @throws SecurityException
* If denied by the security manager
*/
Optional<ModuleReference> find(String name);
/**
* Returns the set of all module references that this finder can locate.
*
* <p> A {@code ModuleFinder} provides a consistent view of the modules
* that it locates. If {@link #findAll() findAll} is invoked several times
* then it will return the same (equals) result each time. For each {@code
* ModuleReference} element in the returned set then it is guaranteed that
* {@link #find find} will locate the {@code ModuleReference} if invoked
* to find that module. </p>
*
* @apiNote This is important to have for methods such as {@link
* Configuration#resolveAndBind resolveAndBind} that need to scan the
* module path to find modules that provide a specific service.
*
* @return The set of all module references that this finder locates
*
* @throws FindException
* If an error occurs finding all modules
*
* @throws SecurityException
* If denied by the security manager
*/
Set<ModuleReference> findAll();
/**
* Returns a module finder that locates the <em>system modules</em>. The
* system modules are the modules in the Java run-time image.
* The module finder will always find {@code java.base}.
*
* <p> If there is a security manager set then its {@link
* SecurityManager#checkPermission(Permission) checkPermission} method is
* invoked to check that the caller has been granted
* {@link RuntimePermission RuntimePermission("accessSystemModules")}
* to access the system modules. </p>
*
* @return A {@code ModuleFinder} that locates the system modules
*
* @throws SecurityException
* If denied by the security manager
*/
static ModuleFinder ofSystem() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("accessSystemModules"));
PrivilegedAction<ModuleFinder> pa = SystemModuleFinders::ofSystem;
return AccessController.doPrivileged(pa);
} else {
return SystemModuleFinders.ofSystem();
}
}
/**
* Returns a module finder that locates modules on the file system by
* searching a sequence of directories and/or packaged modules.
*
* Each element in the given array is one of:
* <ol>
* <li><p> A path to a directory of modules.</p></li>
* <li><p> A path to the <em>top-level</em> directory of an
* <em>exploded module</em>. </p></li>
* <li><p> A path to a <em>packaged module</em>. </p></li>
* </ol>
*
* The module finder locates modules by searching each directory, exploded
* module, or packaged module in array index order. It finds the first
* occurrence of a module with a given name and ignores other modules of
* that name that appear later in the sequence.
*
* <p> If an element is a path to a directory of modules then each entry in
* the directory is a packaged module or the top-level directory of an
* exploded module. It is an error if a directory contains more than one
* module with the same name. If an element is a path to a directory, and
* that directory contains a file named {@code module-info.class}, then the
* directory is treated as an exploded module rather than a directory of
* modules. </p>
*
* <p id="automatic-modules"> The module finder returned by this method
* supports modules packaged as JAR files. A JAR file with a {@code
* module-info.class} in its top-level directory, or in a versioned entry
* in a {@linkplain java.util.jar.JarFile#isMultiRelease() multi-release}
* JAR file, is a modular JAR file and thus defines an <em>explicit</em>
* module. A JAR file that does not have a {@code module-info.class} in its
* top-level directory defines an <em>automatic module</em>, as follows:
* </p>
*
* <ul>
*
* <li><p> If the JAR file has the attribute "{@code Automatic-Module-Name}"
* in its main manifest then its value is the {@linkplain
* ModuleDescriptor#name() module name}. The module name is otherwise
* derived from the name of the JAR file. </p></li>
*
* <li><p> The {@link ModuleDescriptor#version() version}, and the
* module name when the attribute "{@code Automatic-Module-Name}" is not
* present, are derived from the file name of the JAR file as follows: </p>
*
* <ul>
*
* <li><p> The "{@code .jar}" suffix is removed. </p></li>
*
* <li><p> If the name matches the regular expression {@code
* "-(\\d+(\\.|$))"} then the module name will be derived from the
* subsequence preceding the hyphen of the first occurrence. The
* subsequence after the hyphen is parsed as a {@link
* ModuleDescriptor.Version Version} and ignored if it cannot be
* parsed as a {@code Version}. </p></li>
*
* <li><p> All non-alphanumeric characters ({@code [^A-Za-z0-9]})
* in the module name are replaced with a dot ({@code "."}), all
* repeating dots are replaced with one dot, and all leading and
* trailing dots are removed. </p></li>
*
* <li><p> As an example, a JAR file named "{@code foo-bar.jar}" will
* derive a module name "{@code foo.bar}" and no version. A JAR file
* named "{@code foo-bar-1.2.3-SNAPSHOT.jar}" will derive a module
* name "{@code foo.bar}" and "{@code 1.2.3-SNAPSHOT}" as the version.
* </p></li>
*
* </ul></li>
*
* <li><p> The set of packages in the module is derived from the
* non-directory entries in the JAR file that have names ending in
* "{@code .class}". A candidate package name is derived from the name
* using the characters up to, but not including, the last forward slash.
* All remaining forward slashes are replaced with dot ({@code "."}). If
* the resulting string is a legal package name then it is assumed to be
* a package name. For example, if the JAR file contains the entry
* "{@code p/q/Foo.class}" then the package name derived is
* "{@code p.q}".</p></li>
*
* <li><p> The contents of entries starting with {@code
* META-INF/services/} are assumed to be service configuration files
* (see {@link java.util.ServiceLoader}). If the name of a file
* (that follows {@code META-INF/services/}) is a legal class name
* then it is assumed to be the fully-qualified class name of a service
* type. The entries in the file are assumed to be the fully-qualified
* class names of provider classes. </p></li>
*
* <li><p> If the JAR file has a {@code Main-Class} attribute in its
* main manifest, its value is a legal class name, and its package is
* in the set of packages derived for the module, then the value is the
* module {@linkplain ModuleDescriptor#mainClass() main class}. </p></li>
*
* </ul>
*
* <p> If a {@code ModuleDescriptor} cannot be created (by means of the
* {@link ModuleDescriptor.Builder ModuleDescriptor.Builder} API) for an
* automatic module then {@code FindException} is thrown. This can arise
* when the value of the "{@code Automatic-Module-Name}" attribute is not a
* legal module name, a legal module name cannot be derived from the file
* name of the JAR file, where the JAR file contains a {@code .class} in
* the top-level directory of the JAR file, where an entry in a service
* configuration file is not a legal class name or its package name is not
* in the set of packages derived for the module. </p>
*
* <p> In addition to JAR files, an implementation may also support modules
* that are packaged in other implementation specific module formats. If
* an element in the array specified to this method is a path to a directory
* of modules then entries in the directory that not recognized as modules
* are ignored. If an element in the array is a path to a packaged module
* that is not recognized then a {@code FindException} is thrown when the
* file is encountered. Paths to files that do not exist are always ignored.
* </p>
*
* <p> As with automatic modules, the contents of a packaged or exploded
* module may need to be <em>scanned</em> in order to determine the packages
* in the module. Whether {@linkplain java.nio.file.Files#isHidden(Path)
* hidden files} are ignored or not is implementation specific and therefore
* not specified. If a {@code .class} file (other than {@code
* module-info.class}) is found in the top-level directory then it is
* assumed to be a class in the unnamed package and so {@code FindException}
* is thrown. </p>
*
* <p> Finders created by this method are lazy and do not eagerly check
* that the given file paths are directories or packaged modules.
* Consequently, the {@code find} or {@code findAll} methods will only
* fail if invoking these methods results in searching a directory or
* packaged module and an error is encountered. </p>
*
* @param entries
* A possibly-empty array of paths to directories of modules
* or paths to packaged or exploded modules
*
* @return A {@code ModuleFinder} that locates modules on the file system
*/
static ModuleFinder of(Path... entries) {
// special case zero entries
if (entries.length == 0) {
return new ModuleFinder() {
@Override
public Optional<ModuleReference> find(String name) {
Objects.requireNonNull(name);
return Optional.empty();
}
@Override
public Set<ModuleReference> findAll() {
return Collections.emptySet();
}
};
}
return ModulePath.of(entries);
}
/**
* Returns a module finder that is composed from a sequence of zero or more
* module finders. The {@link #find(String) find} method of the resulting
* module finder will locate a module by invoking the {@code find} method
* of each module finder, in array index order, until either the module is
* found or all module finders have been searched. The {@link #findAll()
* findAll} method of the resulting module finder will return a set of
* modules that includes all modules located by the first module finder.
* The set of modules will include all modules located by the second or
* subsequent module finder that are not located by previous module finders
* in the sequence.
*
* <p> When locating modules then any exceptions or errors thrown by the
* {@code find} or {@code findAll} methods of the underlying module finders
* will be propagated to the caller of the resulting module finder's
* {@code find} or {@code findAll} methods. </p>
*
* @param finders
* The array of module finders
*
* @return A {@code ModuleFinder} that composes a sequence of module finders
*/
static ModuleFinder compose(ModuleFinder... finders) {
// copy the list and check for nulls
final List<ModuleFinder> finderList = List.of(finders);
return new ModuleFinder() {
private final Map<String, ModuleReference> nameToModule = new HashMap<>();
private Set<ModuleReference> allModules;
@Override
public Optional<ModuleReference> find(String name) {
// cached?
ModuleReference mref = nameToModule.get(name);
if (mref != null)
return Optional.of(mref);
Optional<ModuleReference> omref = finderList.stream()
.map(f -> f.find(name))
.flatMap(Optional::stream)
.findFirst();
omref.ifPresent(m -> nameToModule.put(name, m));
return omref;
}
@Override
public Set<ModuleReference> findAll() {
if (allModules != null)
return allModules;
// seed with modules already found
Set<ModuleReference> result = new HashSet<>(nameToModule.values());
finderList.stream()
.flatMap(f -> f.findAll().stream())
.forEach(mref -> {
String name = mref.descriptor().name();
if (nameToModule.putIfAbsent(name, mref) == null) {
result.add(mref);
}
});
allModules = Collections.unmodifiableSet(result);
return allModules;
}
};
}
}

View file

@ -0,0 +1,241 @@
/*
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.module;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
/**
* Provides access to the content of a module.
*
* <p> A module reader is intended for cases where access to the resources in a
* module is required, regardless of whether the module has been loaded.
* A framework that scans a collection of packaged modules on the file system,
* for example, may use a module reader to access a specific resource in each
* module. A module reader is also intended to be used by {@code ClassLoader}
* implementations that load classes and resources from modules. </p>
*
* <p> A resource in a module is identified by an abstract name that is a
* '{@code /}'-separated path string. For example, module {@code java.base} may
* have a resource "{@code java/lang/Object.class}" that, by convention, is the
* class file for {@code java.lang.Object}. A module reader may treat
* directories in the module content as resources (whether it does or not is
* module reader specific). Where the module content contains a directory
* that can be located as a resource then its name ends with a slash ('/'). The
* directory can also be located with a name that drops the trailing slash. </p>
*
* <p> A {@code ModuleReader} is {@linkplain ModuleReference#open open} upon
* creation and is closed by invoking the {@link #close close} method. Failure
* to close a module reader may result in a resource leak. The {@code
* try-with-resources} statement provides a useful construct to ensure that
* module readers are closed. </p>
*
* <p> A {@code ModuleReader} implementation may require permissions to access
* resources in the module. Consequently the {@link #find find}, {@link #open
* open}, {@link #read read}, and {@link #list list} methods may throw {@code
* SecurityException} if access is denied by the security manager. </p>
*
* @implSpec Implementations of {@code ModuleReader} should take great care
* when translating an abstract resource name to the location of a resource in
* a packaged module or on the file system. Implementations are advised to
* treat resource names with elements such as '{@code .}, '{@code ..}',
* elements containing file separators, or empty elements as "not found". More
* generally, if the resource name is not in the stream of elements that the
* {@code list} method returns then the resource should be treated as "not
* found" to avoid inconsistencies.
*
* @see ModuleReference
* @since 9
* @spec JPMS
*/
public interface ModuleReader extends Closeable {
/**
* Finds a resource, returning a URI to the resource in the module.
*
* <p> If the module reader can determine that the name locates a directory
* then the resulting URI will end with a slash ('/'). </p>
*
* @param name
* The name of the resource to open for reading
*
* @return A URI to the resource; an empty {@code Optional} if the resource
* is not found or a URI cannot be constructed to locate the
* resource
*
* @throws IOException
* If an I/O error occurs or the module reader is closed
* @throws SecurityException
* If denied by the security manager
*
* @see ClassLoader#getResource(String)
*/
Optional<URI> find(String name) throws IOException;
/**
* Opens a resource, returning an input stream to read the resource in
* the module.
*
* <p> The behavior of the input stream when used after the module reader
* is closed is implementation specific and therefore not specified. </p>
*
* @implSpec The default implementation invokes the {@link #find(String)
* find} method to get a URI to the resource. If found, then it attempts
* to construct a {@link java.net.URL URL} and open a connection to the
* resource.
*
* @param name
* The name of the resource to open for reading
*
* @return An input stream to read the resource or an empty
* {@code Optional} if not found
*
* @throws IOException
* If an I/O error occurs or the module reader is closed
* @throws SecurityException
* If denied by the security manager
*/
default Optional<InputStream> open(String name) throws IOException {
Optional<URI> ouri = find(name);
if (ouri.isPresent()) {
return Optional.of(ouri.get().toURL().openStream());
} else {
return Optional.empty();
}
}
/**
* Reads a resource, returning a byte buffer with the contents of the
* resource.
*
* The element at the returned buffer's position is the first byte of the
* resource, the element at the buffer's limit is the last byte of the
* resource. Once consumed, the {@link #release(ByteBuffer) release} method
* must be invoked. Failure to invoke the {@code release} method may result
* in a resource leak.
*
* @apiNote This method is intended for high-performance class loading. It
* is not capable (or intended) to read arbitrary large resources that
* could potentially be 2GB or larger. The rationale for using this method
* in conjunction with the {@code release} method is to allow module reader
* implementations manage buffers in an efficient manner.
*
* @implSpec The default implementation invokes the {@link #open(String)
* open} method and reads all bytes from the input stream into a byte
* buffer.
*
* @param name
* The name of the resource to read
*
* @return A byte buffer containing the contents of the resource or an
* empty {@code Optional} if not found
*
* @throws IOException
* If an I/O error occurs or the module reader is closed
* @throws SecurityException
* If denied by the security manager
* @throws OutOfMemoryError
* If the resource is larger than {@code Integer.MAX_VALUE},
* the maximum capacity of a byte buffer
*
* @see ClassLoader#defineClass(String, ByteBuffer, java.security.ProtectionDomain)
*/
default Optional<ByteBuffer> read(String name) throws IOException {
Optional<InputStream> oin = open(name);
if (oin.isPresent()) {
try (InputStream in = oin.get()) {
return Optional.of(ByteBuffer.wrap(in.readAllBytes()));
}
} else {
return Optional.empty();
}
}
/**
* Release a byte buffer. This method should be invoked after consuming
* the contents of the buffer returned by the {@code read} method.
* The behavior of this method when invoked to release a buffer that has
* already been released, or the behavior when invoked to release a buffer
* after a {@code ModuleReader} is closed is implementation specific and
* therefore not specified.
*
* @param bb
* The byte buffer to release
*
* @implSpec The default implementation doesn't do anything except check
* if the byte buffer is null.
*/
default void release(ByteBuffer bb) {
Objects.requireNonNull(bb);
}
/**
* Lists the contents of the module, returning a stream of elements that
* are the names of all resources in the module. Whether the stream of
* elements includes names corresponding to directories in the module is
* module reader specific.
*
* <p> In lazy implementations then an {@code IOException} may be thrown
* when using the stream to list the module contents. If this occurs then
* the {@code IOException} will be wrapped in an {@link
* java.io.UncheckedIOException} and thrown from the method that caused the
* access to be attempted. {@code SecurityException} may also be thrown
* when using the stream to list the module contents and access is denied
* by the security manager. </p>
*
* <p> The behavior of the stream when used after the module reader is
* closed is implementation specific and therefore not specified. </p>
*
* @return A stream of elements that are the names of all resources
* in the module
*
* @throws IOException
* If an I/O error occurs or the module reader is closed
* @throws SecurityException
* If denied by the security manager
*/
Stream<String> list() throws IOException;
/**
* Closes the module reader. Once closed then subsequent calls to locate or
* read a resource will fail by throwing {@code IOException}.
*
* <p> A module reader is not required to be asynchronously closeable. If a
* thread is reading a resource and another thread invokes the close method,
* then the second thread may block until the read operation is complete. </p>
*/
@Override
void close() throws IOException;
}

View file

@ -0,0 +1,103 @@
/*
* Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.module;
import java.io.IOException;
import java.net.URI;
import java.util.Objects;
import java.util.Optional;
/**
* A reference to a module's content.
*
* <p> A module reference is a concrete implementation of this class that
* implements the abstract methods defined by this class. It contains the
* module's descriptor and its location, if known. It also has the ability to
* create a {@link ModuleReader} in order to access the module's content, which
* may be inside the Java run-time system itself or in an artifact such as a
* modular JAR file.
*
* @see ModuleFinder
* @see ModuleReader
* @since 9
* @spec JPMS
*/
public abstract class ModuleReference {
private final ModuleDescriptor descriptor;
private final URI location;
/**
* Constructs a new instance of this class.
*
* @param descriptor
* The module descriptor
* @param location
* The module location or {@code null} if not known
*/
protected ModuleReference(ModuleDescriptor descriptor, URI location) {
this.descriptor = Objects.requireNonNull(descriptor);
this.location = location;
}
/**
* Returns the module descriptor.
*
* @return The module descriptor
*/
public final ModuleDescriptor descriptor() {
return descriptor;
}
/**
* Returns the location of this module's content, if known.
*
* <p> This URI, when present, can be used as the {@linkplain
* java.security.CodeSource#getLocation location} value of a {@link
* java.security.CodeSource CodeSource} so that a module's classes can be
* granted specific permissions when loaded by a {@link
* java.security.SecureClassLoader SecureClassLoader}.
*
* @return The location or an empty {@code Optional} if not known
*/
public final Optional<URI> location() {
return Optional.ofNullable(location);
}
/**
* Opens the module content for reading.
*
* @return A {@code ModuleReader} to read the module
*
* @throws IOException
* If an I/O error occurs
* @throws SecurityException
* If denied by the security manager
*/
public abstract ModuleReader open() throws IOException;
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.module;
/**
* Thrown when resolving a set of modules, or resolving a set of modules with
* service binding, fails.
*
* @see Configuration
* @since 9
* @spec JPMS
*/
public class ResolutionException extends RuntimeException {
private static final long serialVersionUID = -1031186845316729450L;
/**
* Constructs a {@code ResolutionException} with no detail message.
*/
public ResolutionException() { }
/**
* Constructs a {@code ResolutionException} with the given detail
* message.
*
* @param msg
* The detail message; can be {@code null}
*/
public ResolutionException(String msg) {
super(msg);
}
/**
* Constructs an instance of this exception with the given cause.
*
* @param cause
* The cause; can be {@code null}
*/
public ResolutionException(Throwable cause) {
super(cause);
}
/**
* Constructs a {@code ResolutionException} with the given detail message
* and cause.
*
* @param msg
* The detail message; can be {@code null}
* @param cause
* The cause; can be {@code null}
*/
public ResolutionException(String msg, Throwable cause) {
super(msg, cause);
}
}

View file

@ -0,0 +1,160 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.module;
import java.util.Objects;
import java.util.Set;
/**
* A module in a graph of <em>resolved modules</em>.
*
* <p> {@code ResolvedModule} defines the {@link #configuration configuration}
* method to get the configuration that the resolved module is in. It defines
* the {@link #reference() reference} method to get the reference to the
* module's content.
*
* @since 9
* @spec JPMS
* @see Configuration#modules()
*/
public final class ResolvedModule {
private final Configuration cf;
private final ModuleReference mref;
ResolvedModule(Configuration cf, ModuleReference mref) {
this.cf = Objects.requireNonNull(cf);
this.mref = Objects.requireNonNull(mref);
}
/**
* Returns the configuration that this resolved module is in.
*
* @return The configuration that this resolved module is in
*/
public Configuration configuration() {
return cf;
}
/**
* Returns the reference to the module's content.
*
* @return The reference to the module's content
*/
public ModuleReference reference() {
return mref;
}
/**
* Returns the module descriptor.
*
* This convenience method is the equivalent to invoking:
* <pre> {@code
* reference().descriptor()
* }</pre>
*
* @return The module descriptor
*/
ModuleDescriptor descriptor() {
return reference().descriptor();
}
/**
* Returns the module name.
*
* This convenience method is the equivalent to invoking:
* <pre> {@code
* reference().descriptor().name()
* }</pre>
*
* @return The module name
*/
public String name() {
return reference().descriptor().name();
}
/**
* Returns the set of resolved modules that this resolved module reads.
*
* @return A possibly-empty unmodifiable set of resolved modules that
* this resolved module reads
*/
public Set<ResolvedModule> reads() {
return cf.reads(this);
}
/**
* Computes a hash code for this resolved module.
*
* <p> The hash code is based upon the components of the resolved module
* and satisfies the general contract of the {@link Object#hashCode
* Object.hashCode} method. </p>
*
* @return The hash-code value for this resolved module
*/
@Override
public int hashCode() {
return cf.hashCode() ^ mref.hashCode();
}
/**
* Tests this resolved module for equality with the given object.
*
* <p> If the given object is not a {@code ResolvedModule} then this
* method returns {@code false}. Two {@code ResolvedModule} objects are
* equal if they are in the same configuration and have equal references
* to the module content. </p>
*
* <p> This method satisfies the general contract of the {@link
* java.lang.Object#equals(Object) Object.equals} method. </p>
*
* @param ob
* the object to which this object is to be compared
*
* @return {@code true} if, and only if, the given object is a module
* reference that is equal to this module reference
*/
@Override
public boolean equals(Object ob) {
if (!(ob instanceof ResolvedModule))
return false;
ResolvedModule that = (ResolvedModule) ob;
return Objects.equals(this.cf, that.cf)
&& Objects.equals(this.mref, that.mref);
}
/**
* Returns a string describing this resolved module.
*
* @return A string describing this resolved module
*/
@Override
public String toString() {
return System.identityHashCode(cf) + "/" + name();
}
}

View file

@ -0,0 +1,911 @@
/*
* Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.module;
import java.io.PrintStream;
import java.lang.module.ModuleDescriptor.Provides;
import java.lang.module.ModuleDescriptor.Requires.Modifier;
import java.net.URI;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
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.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import jdk.internal.module.ModuleHashes;
import jdk.internal.module.ModuleReferenceImpl;
import jdk.internal.module.ModuleTarget;
/**
* The resolver used by {@link Configuration#resolve} and {@link
* Configuration#resolveAndBind}.
*
* @implNote The resolver is used at VM startup and so deliberately avoids
* using lambda and stream usages in code paths used during startup.
*/
final class Resolver {
private final ModuleFinder beforeFinder;
private final List<Configuration> parents;
private final ModuleFinder afterFinder;
private final PrintStream traceOutput;
// maps module name to module reference
private final Map<String, ModuleReference> nameToReference = new HashMap<>();
// true if all automatic modules have been found
private boolean haveAllAutomaticModules;
// constraint on target platform
private String targetPlatform;
String targetPlatform() { return targetPlatform; }
/**
* @throws IllegalArgumentException if there are more than one parent and
* the constraints on the target platform conflict
*/
Resolver(ModuleFinder beforeFinder,
List<Configuration> parents,
ModuleFinder afterFinder,
PrintStream traceOutput) {
this.beforeFinder = beforeFinder;
this.parents = parents;
this.afterFinder = afterFinder;
this.traceOutput = traceOutput;
// record constraint on target platform, checking for conflicts
for (Configuration parent : parents) {
String value = parent.targetPlatform();
if (value != null) {
if (targetPlatform == null) {
targetPlatform = value;
} else {
if (!value.equals(targetPlatform)) {
String msg = "Parents have conflicting constraints on target" +
" platform: " + targetPlatform + ", " + value;
throw new IllegalArgumentException(msg);
}
}
}
}
}
/**
* Resolves the given named modules.
*
* @throws ResolutionException
*/
Resolver resolve(Collection<String> roots) {
// create the visit stack to get us started
Deque<ModuleDescriptor> q = new ArrayDeque<>();
for (String root : roots) {
// find root module
ModuleReference mref = findWithBeforeFinder(root);
if (mref == null) {
if (findInParent(root) != null) {
// in parent, nothing to do
continue;
}
mref = findWithAfterFinder(root);
if (mref == null) {
findFail("Module %s not found", root);
}
}
if (isTracing()) {
trace("root %s", nameAndInfo(mref));
}
addFoundModule(mref);
q.push(mref.descriptor());
}
resolve(q);
return this;
}
/**
* Resolve all modules in the given queue. On completion the queue will be
* empty and any resolved modules will be added to {@code nameToReference}.
*
* @return The set of module resolved by this invocation of resolve
*/
private Set<ModuleDescriptor> resolve(Deque<ModuleDescriptor> q) {
Set<ModuleDescriptor> resolved = new HashSet<>();
while (!q.isEmpty()) {
ModuleDescriptor descriptor = q.poll();
assert nameToReference.containsKey(descriptor.name());
// if the module is an automatic module then all automatic
// modules need to be resolved
if (descriptor.isAutomatic() && !haveAllAutomaticModules) {
addFoundAutomaticModules().forEach(mref -> {
ModuleDescriptor other = mref.descriptor();
q.offer(other);
if (isTracing()) {
trace("%s requires %s", descriptor.name(), nameAndInfo(mref));
}
});
haveAllAutomaticModules = true;
}
// process dependences
for (ModuleDescriptor.Requires requires : descriptor.requires()) {
// only required at compile-time
if (requires.modifiers().contains(Modifier.STATIC))
continue;
String dn = requires.name();
// find dependence
ModuleReference mref = findWithBeforeFinder(dn);
if (mref == null) {
if (findInParent(dn) != null) {
// dependence is in parent
continue;
}
mref = findWithAfterFinder(dn);
if (mref == null) {
findFail("Module %s not found, required by %s",
dn, descriptor.name());
}
}
if (isTracing() && !dn.equals("java.base")) {
trace("%s requires %s", descriptor.name(), nameAndInfo(mref));
}
if (!nameToReference.containsKey(dn)) {
addFoundModule(mref);
q.offer(mref.descriptor());
}
}
resolved.add(descriptor);
}
return resolved;
}
/**
* Augments the set of resolved modules with modules induced by the
* service-use relation.
*/
Resolver bind() {
// 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 (Provides provides : descriptor.provides()) {
String sn = provides.service();
// computeIfAbsent
Set<ModuleReference> providers = availableProviders.get(sn);
if (providers == null) {
providers = new HashSet<>();
availableProviders.put(sn, providers);
}
providers.add(mref);
}
}
}
// create the visit stack
Deque<ModuleDescriptor> q = new ArrayDeque<>();
// the initial set of modules that may use services
Set<ModuleDescriptor> initialConsumers;
if (ModuleLayer.boot() == null) {
initialConsumers = new HashSet<>();
} else {
initialConsumers = parents.stream()
.flatMap(Configuration::configurations)
.distinct()
.flatMap(c -> c.descriptors().stream())
.collect(Collectors.toSet());
}
for (ModuleReference mref : nameToReference.values()) {
initialConsumers.add(mref.descriptor());
}
// Where there is a consumer of a service then resolve all modules
// that provide an implementation of that service
Set<ModuleDescriptor> candidateConsumers = initialConsumers;
do {
for (ModuleDescriptor descriptor : candidateConsumers) {
if (!descriptor.uses().isEmpty()) {
// the modules that provide at least one service
Set<ModuleDescriptor> modulesToBind = null;
if (isTracing()) {
modulesToBind = new HashSet<>();
}
for (String service : descriptor.uses()) {
Set<ModuleReference> mrefs = availableProviders.get(service);
if (mrefs != null) {
for (ModuleReference mref : mrefs) {
ModuleDescriptor provider = mref.descriptor();
if (!provider.equals(descriptor)) {
if (isTracing() && modulesToBind.add(provider)) {
trace("%s binds %s", descriptor.name(),
nameAndInfo(mref));
}
String pn = provider.name();
if (!nameToReference.containsKey(pn)) {
addFoundModule(mref);
q.push(provider);
}
}
}
}
}
}
}
candidateConsumers = resolve(q);
} while (!candidateConsumers.isEmpty());
return this;
}
/**
* Add all automatic modules that have not already been found to the
* nameToReference map.
*/
private Set<ModuleReference> addFoundAutomaticModules() {
Set<ModuleReference> result = new HashSet<>();
findAll().forEach(mref -> {
String mn = mref.descriptor().name();
if (mref.descriptor().isAutomatic() && !nameToReference.containsKey(mn)) {
addFoundModule(mref);
result.add(mref);
}
});
return result;
}
/**
* Add the module to the nameToReference map. Also check any constraints on
* the target platform with the constraints of other modules.
*/
private void addFoundModule(ModuleReference mref) {
String mn = mref.descriptor().name();
if (mref instanceof ModuleReferenceImpl) {
ModuleTarget target = ((ModuleReferenceImpl)mref).moduleTarget();
if (target != null)
checkTargetPlatform(mn, target);
}
nameToReference.put(mn, mref);
}
/**
* Check that the module's constraints on the target platform does
* conflict with the constraint of other modules resolved so far.
*/
private void checkTargetPlatform(String mn, ModuleTarget target) {
String value = target.targetPlatform();
if (value != null) {
if (targetPlatform == null) {
targetPlatform = value;
} else {
if (!value.equals(targetPlatform)) {
findFail("Module %s has constraints on target platform (%s)"
+ " that conflict with other modules: %s", mn,
value, targetPlatform);
}
}
}
}
/**
* Execute post-resolution checks and returns the module graph of resolved
* modules as a map.
*/
Map<ResolvedModule, Set<ResolvedModule>> finish(Configuration cf) {
detectCycles();
checkHashes();
Map<ResolvedModule, Set<ResolvedModule>> graph = makeGraph(cf);
checkExportSuppliers(graph);
return graph;
}
/**
* 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.
*/
private void detectCycles() {
visited = new HashSet<>();
visitPath = new LinkedHashSet<>(); // preserve insertion order
for (ModuleReference mref : nameToReference.values()) {
visit(mref.descriptor());
}
visited.clear();
}
// the modules that were visited
private Set<ModuleDescriptor> visited;
// the modules in the current visit path
private Set<ModuleDescriptor> visitPath;
private void visit(ModuleDescriptor descriptor) {
if (!visited.contains(descriptor)) {
boolean added = visitPath.add(descriptor);
if (!added) {
resolveFail("Cycle detected: %s", cycleAsString(descriptor));
}
for (ModuleDescriptor.Requires requires : descriptor.requires()) {
String dn = requires.name();
ModuleReference mref = nameToReference.get(dn);
if (mref != null) {
ModuleDescriptor other = mref.descriptor();
if (other != descriptor) {
// dependency is in this configuration
visit(other);
}
}
}
visitPath.remove(descriptor);
visited.add(descriptor);
}
}
/**
* Returns a String with a list of the modules in a detected cycle.
*/
private String cycleAsString(ModuleDescriptor descriptor) {
List<ModuleDescriptor> list = new ArrayList<>(visitPath);
list.add(descriptor);
int index = list.indexOf(descriptor);
return list.stream()
.skip(index)
.map(ModuleDescriptor::name)
.collect(Collectors.joining(" -> "));
}
/**
* Checks the hashes in the module descriptor to ensure that they match
* any recorded hashes.
*/
private void checkHashes() {
for (ModuleReference mref : nameToReference.values()) {
// get the recorded hashes, if any
if (!(mref instanceof ModuleReferenceImpl))
continue;
ModuleHashes hashes = ((ModuleReferenceImpl)mref).recordedHashes();
if (hashes == null)
continue;
ModuleDescriptor descriptor = mref.descriptor();
String algorithm = hashes.algorithm();
for (String dn : hashes.names()) {
ModuleReference mref2 = nameToReference.get(dn);
if (mref2 == null) {
ResolvedModule resolvedModule = findInParent(dn);
if (resolvedModule != null)
mref2 = resolvedModule.reference();
}
if (mref2 == null)
continue;
if (!(mref2 instanceof ModuleReferenceImpl)) {
findFail("Unable to compute the hash of module %s", dn);
}
ModuleReferenceImpl other = (ModuleReferenceImpl)mref2;
if (other != null) {
byte[] recordedHash = hashes.hashFor(dn);
byte[] actualHash = other.computeHash(algorithm);
if (actualHash == null)
findFail("Unable to compute the hash of module %s", dn);
if (!Arrays.equals(recordedHash, actualHash)) {
findFail("Hash of %s (%s) differs to expected hash (%s)" +
" recorded in %s", dn, toHexString(actualHash),
toHexString(recordedHash), descriptor.name());
}
}
}
}
}
private static String toHexString(byte[] ba) {
StringBuilder sb = new StringBuilder(ba.length * 2);
for (byte b: ba) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
}
/**
* Computes the readability graph for the modules in the given Configuration.
*
* The readability graph is created by propagating "requires" through the
* "requires transitive" edges of the module dependence graph. So if the
* module dependence graph has m1 requires m2 && m2 requires transitive m3
* then the resulting readability graph will contain m1 reads m2, m1 reads m3,
* and m2 reads m3.
*/
private Map<ResolvedModule, Set<ResolvedModule>> makeGraph(Configuration cf) {
// initial capacity of maps to avoid resizing
int capacity = 1 + (4 * nameToReference.size())/ 3;
// the "reads" graph starts as a module dependence graph and
// is iteratively updated to be the readability graph
Map<ResolvedModule, Set<ResolvedModule>> g1 = new HashMap<>(capacity);
// the "requires transitive" graph, contains requires transitive edges only
Map<ResolvedModule, Set<ResolvedModule>> g2;
// need "requires transitive" from the modules in parent configurations
// as there may be selected modules that have a dependency on modules in
// the parent configuration.
if (ModuleLayer.boot() == null) {
g2 = new HashMap<>(capacity);
} else {
g2 = parents.stream()
.flatMap(Configuration::configurations)
.distinct()
.flatMap(c ->
c.modules().stream().flatMap(m1 ->
m1.descriptor().requires().stream()
.filter(r -> r.modifiers().contains(Modifier.TRANSITIVE))
.flatMap(r -> {
Optional<ResolvedModule> m2 = c.findModule(r.name());
assert m2.isPresent()
|| r.modifiers().contains(Modifier.STATIC);
return m2.stream();
})
.map(m2 -> Map.entry(m1, m2))
)
)
// stream of m1->m2
.collect(Collectors.groupingBy(Map.Entry::getKey,
HashMap::new,
Collectors.mapping(Map.Entry::getValue, Collectors.toSet())
));
}
// populate g1 and g2 with the dependences from the selected modules
Map<String, ResolvedModule> nameToResolved = new HashMap<>(capacity);
for (ModuleReference mref : nameToReference.values()) {
ModuleDescriptor descriptor = mref.descriptor();
String name = descriptor.name();
ResolvedModule m1 = computeIfAbsent(nameToResolved, name, cf, mref);
Set<ResolvedModule> reads = new HashSet<>();
Set<ResolvedModule> requiresTransitive = new HashSet<>();
for (ModuleDescriptor.Requires requires : descriptor.requires()) {
String dn = requires.name();
ResolvedModule m2 = null;
ModuleReference mref2 = nameToReference.get(dn);
if (mref2 != null) {
// same configuration
m2 = computeIfAbsent(nameToResolved, dn, cf, mref2);
} else {
// parent configuration
m2 = findInParent(dn);
if (m2 == null) {
assert requires.modifiers().contains(Modifier.STATIC);
continue;
}
}
// m1 requires m2 => m1 reads m2
reads.add(m2);
// m1 requires transitive m2
if (requires.modifiers().contains(Modifier.TRANSITIVE)) {
requiresTransitive.add(m2);
}
}
// automatic modules read all selected modules and all modules
// in parent configurations
if (descriptor.isAutomatic()) {
// reads all selected modules
// `requires transitive` all selected automatic modules
for (ModuleReference mref2 : nameToReference.values()) {
ModuleDescriptor descriptor2 = mref2.descriptor();
String name2 = descriptor2.name();
if (!name.equals(name2)) {
ResolvedModule m2
= computeIfAbsent(nameToResolved, name2, cf, mref2);
reads.add(m2);
if (descriptor2.isAutomatic())
requiresTransitive.add(m2);
}
}
// reads all modules in parent configurations
// `requires transitive` all automatic modules in parent
// configurations
for (Configuration parent : parents) {
parent.configurations()
.map(Configuration::modules)
.flatMap(Set::stream)
.forEach(m -> {
reads.add(m);
if (m.reference().descriptor().isAutomatic())
requiresTransitive.add(m);
});
}
}
g1.put(m1, reads);
g2.put(m1, requiresTransitive);
}
// Iteratively update g1 until there are no more requires transitive
// to propagate
boolean changed;
List<ResolvedModule> toAdd = new ArrayList<>();
do {
changed = false;
for (Set<ResolvedModule> m1Reads : g1.values()) {
for (ResolvedModule m2 : m1Reads) {
Set<ResolvedModule> m2RequiresTransitive = g2.get(m2);
if (m2RequiresTransitive != null) {
for (ResolvedModule m3 : m2RequiresTransitive) {
if (!m1Reads.contains(m3)) {
// m1 reads m2, m2 requires transitive m3
// => need to add m1 reads m3
toAdd.add(m3);
}
}
}
}
if (!toAdd.isEmpty()) {
m1Reads.addAll(toAdd);
toAdd.clear();
changed = true;
}
}
} while (changed);
return g1;
}
/**
* Equivalent to
* <pre>{@code
* map.computeIfAbsent(name, k -> new ResolvedModule(cf, mref))
* </pre>}
*/
private ResolvedModule computeIfAbsent(Map<String, ResolvedModule> map,
String name,
Configuration cf,
ModuleReference mref)
{
ResolvedModule m = map.get(name);
if (m == null) {
m = new ResolvedModule(cf, mref);
map.put(name, m);
}
return m;
}
/**
* Checks the readability graph to ensure that
* <ol>
* <li><p> A module does not read two or more modules with the same name.
* This includes the case where a module reads another another with the
* same name as itself. </p></li>
* <li><p> Two or more modules in the configuration don't export the same
* package to a module that reads both. This includes the case where a
* module {@code M} containing package {@code p} reads another module
* that exports {@code p} to {@code M}. </p></li>
* <li><p> A module {@code M} doesn't declare that it "{@code uses p.S}"
* or "{@code provides p.S with ...}" but package {@code p} is neither
* in module {@code M} nor exported to {@code M} by any module that
* {@code M} reads. </p></li>
* </ol>
*/
private void checkExportSuppliers(Map<ResolvedModule, Set<ResolvedModule>> graph) {
for (Map.Entry<ResolvedModule, Set<ResolvedModule>> e : graph.entrySet()) {
ModuleDescriptor descriptor1 = e.getKey().descriptor();
String name1 = descriptor1.name();
// the names of the modules that are read (including self)
Set<String> names = new HashSet<>();
names.add(name1);
// the map of packages that are local or exported to descriptor1
Map<String, ModuleDescriptor> packageToExporter = new HashMap<>();
// local packages
Set<String> packages = descriptor1.packages();
for (String pn : packages) {
packageToExporter.put(pn, descriptor1);
}
// descriptor1 reads descriptor2
Set<ResolvedModule> reads = e.getValue();
for (ResolvedModule endpoint : reads) {
ModuleDescriptor descriptor2 = endpoint.descriptor();
String name2 = descriptor2.name();
if (descriptor2 != descriptor1 && !names.add(name2)) {
if (name2.equals(name1)) {
resolveFail("Module %s reads another module named %s",
name1, name1);
} else{
resolveFail("Module %s reads more than one module named %s",
name1, name2);
}
}
if (descriptor2.isAutomatic()) {
// automatic modules read self and export all packages
if (descriptor2 != descriptor1) {
for (String source : descriptor2.packages()) {
ModuleDescriptor supplier
= packageToExporter.putIfAbsent(source, descriptor2);
// descriptor2 and 'supplier' export source to descriptor1
if (supplier != null) {
failTwoSuppliers(descriptor1, source, descriptor2, supplier);
}
}
}
} else {
for (ModuleDescriptor.Exports export : descriptor2.exports()) {
if (export.isQualified()) {
if (!export.targets().contains(descriptor1.name()))
continue;
}
// source is exported by descriptor2
String source = export.source();
ModuleDescriptor supplier
= packageToExporter.putIfAbsent(source, descriptor2);
// descriptor2 and 'supplier' export source to descriptor1
if (supplier != null) {
failTwoSuppliers(descriptor1, source, descriptor2, supplier);
}
}
}
}
// uses/provides checks not applicable to automatic modules
if (!descriptor1.isAutomatic()) {
// uses S
for (String service : descriptor1.uses()) {
String pn = packageName(service);
if (!packageToExporter.containsKey(pn)) {
resolveFail("Module %s does not read a module that exports %s",
descriptor1.name(), pn);
}
}
// provides S
for (ModuleDescriptor.Provides provides : descriptor1.provides()) {
String pn = packageName(provides.service());
if (!packageToExporter.containsKey(pn)) {
resolveFail("Module %s does not read a module that exports %s",
descriptor1.name(), pn);
}
}
}
}
}
/**
* Fail because a module in the configuration exports the same package to
* a module that reads both. This includes the case where a module M
* containing a package p reads another module that exports p to at least
* module M.
*/
private void failTwoSuppliers(ModuleDescriptor descriptor,
String source,
ModuleDescriptor supplier1,
ModuleDescriptor supplier2) {
if (supplier2 == descriptor) {
ModuleDescriptor tmp = supplier1;
supplier1 = supplier2;
supplier2 = tmp;
}
if (supplier1 == descriptor) {
resolveFail("Module %s contains package %s"
+ ", module %s exports package %s to %s",
descriptor.name(),
source,
supplier2.name(),
source,
descriptor.name());
} else {
resolveFail("Modules %s and %s export package %s to module %s",
supplier1.name(),
supplier2.name(),
source,
descriptor.name());
}
}
/**
* Find a module of the given name in the parent configurations
*/
private ResolvedModule findInParent(String mn) {
for (Configuration parent : parents) {
Optional<ResolvedModule> om = parent.findModule(mn);
if (om.isPresent())
return om.get();
}
return null;
}
/**
* Invokes the beforeFinder to find method to find the given module.
*/
private ModuleReference findWithBeforeFinder(String mn) {
return beforeFinder.find(mn).orElse(null);
}
/**
* Invokes the afterFinder to find method to find the given module.
*/
private ModuleReference findWithAfterFinder(String mn) {
return afterFinder.find(mn).orElse(null);
}
/**
* Returns the set of all modules that are observable with the before
* and after ModuleFinders.
*/
private Set<ModuleReference> findAll() {
Set<ModuleReference> beforeModules = beforeFinder.findAll();
Set<ModuleReference> afterModules = afterFinder.findAll();
if (afterModules.isEmpty())
return beforeModules;
if (beforeModules.isEmpty()
&& parents.size() == 1
&& parents.get(0) == Configuration.empty())
return afterModules;
Set<ModuleReference> result = new HashSet<>(beforeModules);
for (ModuleReference mref : afterModules) {
String name = mref.descriptor().name();
if (!beforeFinder.find(name).isPresent()
&& findInParent(name) == null) {
result.add(mref);
}
}
return result;
}
/**
* Returns the package name
*/
private static String packageName(String cn) {
int index = cn.lastIndexOf(".");
return (index == -1) ? "" : cn.substring(0, index);
}
/**
* Throw FindException with the given format string and arguments
*/
private static void findFail(String fmt, Object ... args) {
String msg = String.format(fmt, args);
throw new FindException(msg);
}
/**
* Throw ResolutionException with the given format string and arguments
*/
private static void resolveFail(String fmt, Object ... args) {
String msg = String.format(fmt, args);
throw new ResolutionException(msg);
}
/**
* Tracing support
*/
private boolean isTracing() {
return traceOutput != null;
}
private void trace(String fmt, Object ... args) {
if (traceOutput != null) {
traceOutput.format(fmt, args);
traceOutput.println();
}
}
private String nameAndInfo(ModuleReference mref) {
ModuleDescriptor descriptor = mref.descriptor();
StringBuilder sb = new StringBuilder(descriptor.name());
mref.location().ifPresent(uri -> sb.append(" " + uri));
if (descriptor.isAutomatic())
sb.append(" automatic");
return sb.toString();
}
}

View file

@ -0,0 +1,213 @@
/*
* Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* Classes to support module descriptors and creating configurations of modules
* by means of resolution and service binding.
*
* <p> Unless otherwise noted, passing a {@code null} argument to a constructor
* or method of any class or interface in this package will cause a {@link
* java.lang.NullPointerException NullPointerException} to be thrown. Additionally,
* invoking a method with an array or collection containing a {@code null} element
* will cause a {@code NullPointerException}, unless otherwise specified. </p>
*
*
* <h1><a id="resolution">Resolution</a></h1>
*
* <p> Resolution is the process of computing how modules depend on each other.
* The process occurs at compile time and run time. </p>
*
* <p> Resolution is a two-step process. The first step recursively enumerates
* the 'requires' directives of a set of root modules. If all the enumerated
* modules are observable, then the second step computes their readability graph.
* The readability graph embodies how modules depend on each other, which in
* turn controls access across module boundaries. </p>
*
* <h2> Step 1: Recursive enumeration </h2>
*
* <p> Recursive enumeration takes a set of module names, looks up each of their
* module declarations, and for each module declaration, recursively enumerates:
*
* <ul>
* <li> <p> the module names given by the 'requires' directives with the
* 'transitive' modifier, and </p></li>
* <li> <p> at the discretion of the host system, the module names given by
* the 'requires' directives without the 'transitive' modifier. </p></li>
* </ul>
*
* <p> Module declarations are looked up in a set of observable modules. The set
* of observable modules is determined in an implementation specific manner. The
* set of observable modules may include modules with explicit declarations
* (that is, with a {@code module-info.java} source file or {@code module-info.class}
* file) and modules with implicit declarations (that is,
* <a href="ModuleFinder.html#automatic-modules">automatic modules</a>).
* Because an automatic module has no explicit module declaration, it has no
* 'requires' directives of its own, although its name may be given by a
* 'requires' directive of an explicit module declaration. </p>
* <p> The set of root modules, whose names are the initial input to this
* algorithm, is determined in an implementation specific manner. The set of
* root modules may include automatic modules. </p>
*
* <p> If at least one automatic module is enumerated by this algorithm, then
* every observable automatic module must be enumerated, regardless of whether
* any of their names are given by 'requires' directives of explicit module
* declarations. </p>
*
* <p> If any of the following conditions occur, then resolution fails:
* <ul>
* <li><p> Any root module is not observable. </p></li>
* <li><p> Any module whose name is given by a 'requires' directive with the
* 'transitive' modifier is not observable. </p></li>
* <li><p> At the discretion of the host system, any module whose name is given
* by a 'requires' directive without the 'transitive' modifier is not
* observable. </p></li>
* <li><p> The algorithm in this step enumerates the same module name twice. This
* indicates a cycle in the 'requires' directives, disregarding any 'transitive'
* modifiers. </p></li>
* </ul>
*
* <p> Otherwise, resolution proceeds to step 2. </p>
*
* <h2> Step 2: Computing the readability graph </h2>
*
* <p> A 'requires' directive (irrespective of 'transitive') expresses that
* one module depends on some other module. The effect of the 'transitive'
* modifier is to cause additional modules to also depend on the other module.
* If module M 'requires transitive N', then not only does M depend on N, but
* any module that depends on M also depends on N. This allows M to be
* refactored so that some or all of its content can be moved to a new module N
* without breaking modules that have a 'requires M' directive. </p>
*
* <p> Module dependencies are represented by the readability graph. The
* readability graph is a directed graph whose vertices are the modules
* enumerated in step 1 and whose edges represent readability between pairs of
* modules. The edges are specified as follows:
*
* <p> First, readability is determined by the 'requires' directives of the
* enumerated modules, disregarding any 'transitive' modifiers:
*
* <ul>
* <li><p> For each enumerated module A that 'requires' B: A "reads" B. </p></li>
* <li><p> For each enumerated module X that is automatic: X "reads" every
* other enumerated module (it is "as if" an automatic module has 'requires'
* directives for every other enumerated module). </p></li>
* </ul>
*
* <p> Second, readability is augmented to account for 'transitive' modifiers:
* <ul>
* <li> <p> For each enumerated module A that "reads" B: </p>
* <ul>
* <li><p> If B 'requires transitive' C, then A "reads" C as well as B. This
* augmentation is recursive: since A "reads" C, if C 'requires transitive'
* D, then A "reads" D as well as C and B. </p></li>
* <li><p> If B is an automatic module, then A "reads" every other enumerated
* automatic module. (It is "as if" an automatic module has 'requires transitive'
* directives for every other enumerated automatic module).</p> </li>
* </ul>
* </li>
* </ul>
*
* <p> Finally, every module "reads" itself. </p>
*
* <p> If any of the following conditions occur in the readability graph, then
* resolution fails:
* <ul>
* <li><p> A module "reads" two or more modules with the same name. This includes
* the case where a module "reads" another with the same name as itself. </p></li>
* <li><p> Two or more modules export a package with the same name to a module
* that "reads" both. This includes the case where a module M containing package
* p "reads" another module that exports p to M. </p></li>
* <li><p> A module M declares that it 'uses p.S' or 'provides p.S with ...' but
* package p is neither in module M nor exported to M by any module that M
* "reads". </p></li>
* </ul>
* <p> Otherwise, resolution succeeds, and the result of resolution is the
* readability graph.
*
* <h2> Root modules </h2>
*
* <p> The set of root modules at compile-time is usually the set of modules
* being compiled. At run-time, the set of root modules is usually the
* application module specified to the 'java' launcher. When compiling code in
* the unnamed module, or at run-time when the main application class is loaded
* from the class path, then the default set of root modules is implementation
* specific (In the JDK implementation it is the module "java.se", if observable,
* and every observable module that exports an API). </p>
*
* <h2> Observable modules </h2>
*
* <p> The set of observable modules at both compile-time and run-time is
* determined by searching several different paths, and also by searching
* the compiled modules built in to the environment. The search order is as
* follows: </p>
*
* <ol>
* <li><p> At compile time only, the compilation module path. This path
* contains module definitions in source form. </p></li>
*
* <li><p> The upgrade module path. This path contains compiled definitions of
* modules that will be observed in preference to the compiled definitions of
* any <i>upgradeable modules</i> that are present in (3) and (4). See the Java
* SE Platform for the designation of which standard modules are upgradeable.
* </p></li>
*
* <li><p> The system modules, which are the compiled definitions built in to
* the environment. </p></li>
*
* <li><p> The application module path. This path contains compiled definitions
* of library and application modules. </p></li>
*
* </ol>
*
* <h2> 'requires' directives with 'static' modifier </h2>
*
* <p> 'requires' directives that have the 'static' modifier express an optional
* dependence at run time. If a module declares that it 'requires static M' then
* resolution does not search the observable modules for M to satisfy the dependency.
* However, if M is recursively enumerated at step 1 then all modules that are
* enumerated and `requires static M` will read M. </p>
*
* <h2> Completeness </h2>
*
* <p> Resolution may be partial at compile-time in that the complete transitive
* closure may not be required to compile a set of modules. Minimally, the
* readability graph that is constructed and validated at compile-time includes
* the modules being compiled, their direct dependences, and all implicitly
* declared dependences (requires transitive). </p>
*
* <p> At run-time, resolution is an additive process. The recursive enumeration
* at step 1 may be relative to previous resolutions so that a root module,
* or a module named in a 'requires' directive, is not enumerated when it was
* enumerated by a previous (or parent) resolution. The readability graph that
* is the result of resolution may therefore have a vertex for a module enumerated
* in step 1 but with an edge to represent that the module reads a module that
* was enumerated by previous (or parent) resolution. </p>
*
* @since 9
* @spec JPMS
*/
package java.lang.module;