mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 06:45:07 +02:00
8187443: Forest Consolidation: Move files to unified layout
Reviewed-by: darcy, ihse
This commit is contained in:
parent
270fe13182
commit
3789983e89
56923 changed files with 3 additions and 15727 deletions
619
src/java.base/share/classes/java/lang/module/Configuration.java
Normal file
619
src/java.base/share/classes/java/lang/module/Configuration.java
Normal 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(", "));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
2744
src/java.base/share/classes/java/lang/module/ModuleDescriptor.java
Normal file
2744
src/java.base/share/classes/java/lang/module/ModuleDescriptor.java
Normal file
File diff suppressed because it is too large
Load diff
381
src/java.base/share/classes/java/lang/module/ModuleFinder.java
Normal file
381
src/java.base/share/classes/java/lang/module/ModuleFinder.java
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
241
src/java.base/share/classes/java/lang/module/ModuleReader.java
Normal file
241
src/java.base/share/classes/java/lang/module/ModuleReader.java
Normal 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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
160
src/java.base/share/classes/java/lang/module/ResolvedModule.java
Normal file
160
src/java.base/share/classes/java/lang/module/ResolvedModule.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
911
src/java.base/share/classes/java/lang/module/Resolver.java
Normal file
911
src/java.base/share/classes/java/lang/module/Resolver.java
Normal 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();
|
||||
}
|
||||
}
|
213
src/java.base/share/classes/java/lang/module/package-info.java
Normal file
213
src/java.base/share/classes/java/lang/module/package-info.java
Normal 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;
|
Loading…
Add table
Add a link
Reference in a new issue