diff --git a/jdk/make/data/jdwp/jdwp.spec b/jdk/make/data/jdwp/jdwp.spec index a144c896f92..bb1d21f92b9 100644 --- a/jdk/make/data/jdwp/jdwp.spec +++ b/jdk/make/data/jdwp/jdwp.spec @@ -2709,22 +2709,6 @@ JDWP "Java(tm) Debug Wire Protocol" (Error VM_DEAD) ) ) - (Command CanRead=3 - "Returns true if this module can read the source module; false otherwise." - "
Since JDWP version 9."
- (Out
- (moduleID module "This module.")
- (moduleID sourceModule "The source module.")
- )
- (Reply
- (boolean canRead "true if this module can read the source module; false otherwise.")
- )
- (ErrorSet
- (Error INVALID_MODULE "This module or sourceModule is not the ID of a module.")
- (Error NOT_IMPLEMENTED)
- (Error VM_DEAD)
- )
- )
)
(CommandSet Event=64
(Command Composite=100
diff --git a/jdk/make/launcher/Launcher-jdk.jconsole.gmk b/jdk/make/launcher/Launcher-jdk.jconsole.gmk
index aa07823c54a..6205ae63d16 100644
--- a/jdk/make/launcher/Launcher-jdk.jconsole.gmk
+++ b/jdk/make/launcher/Launcher-jdk.jconsole.gmk
@@ -27,7 +27,8 @@ include LauncherCommon.gmk
$(eval $(call SetupBuildLauncher, jconsole, \
MAIN_CLASS := sun.tools.jconsole.JConsole, \
- JAVA_ARGS := -Djconsole.showOutputViewer, \
+ JAVA_ARGS := --add-opens java.base/java.io=jdk.jconsole \
+ -Djconsole.showOutputViewer, \
CFLAGS_windows := -DJAVAW, \
LIBS_windows := user32.lib, \
))
diff --git a/jdk/make/src/classes/build/tools/jigsaw/AddPackagesAttribute.java b/jdk/make/src/classes/build/tools/jigsaw/AddPackagesAttribute.java
index 5d212cd70ce..7aa02f365b9 100644
--- a/jdk/make/src/classes/build/tools/jigsaw/AddPackagesAttribute.java
+++ b/jdk/make/src/classes/build/tools/jigsaw/AddPackagesAttribute.java
@@ -65,7 +65,7 @@ public class AddPackagesAttribute {
String mn = entry.getFileName().toString();
Optional"));
- boolean footnote = requiresPublicNote;
+ boolean footnote = requiresTransitiveNote;
ms.descriptor().requires().stream()
.sorted(Comparator.comparing(Requires::name))
.forEach(r -> {
- boolean requiresPublic = r.modifiers().contains(Requires.Modifier.PUBLIC);
- Selector sel = requiresPublic ? REQUIRES_PUBLIC : REQUIRES;
+ boolean requiresTransitive = r.modifiers().contains(Requires.Modifier.TRANSITIVE);
+ Selector sel = requiresTransitive ? REQUIRES_PUBLIC : REQUIRES;
String req = String.format("%s",
sel, r.name(), r.name());
- if (!requiresPublicNote && requiresPublic) {
- requiresPublicNote = true;
+ if (!requiresTransitiveNote && requiresTransitive) {
+ requiresTransitiveNote = true;
req += "*";
}
sb.append(req).append("\n").append(" ");
return sb.toString();
@@ -558,11 +558,10 @@ public class ModuleSummary {
ms.descriptor().uses().stream()
.sorted()
.forEach(s -> sb.append("uses ").append(s).append("
");
@@ -534,8 +534,8 @@ public class ModuleSummary {
sb.append("
");
sb.append("+").append(indirectDeps).append(" transitive dependencies");
}
- if (footnote != requiresPublicNote) {
- sb.append("
").append("* bold denotes requires public");
+ if (footnote != requiresTransitiveNote) {
+ sb.append("
").append("* bold denotes requires transitive");
}
sb.append("
").append("\n"));
- ms.descriptor().provides().entrySet().stream()
- .sorted(Map.Entry.comparingByKey())
- .flatMap(e -> e.getValue().providers().stream()
- .map(p -> String.format("provides %s
with %s",
- e.getKey(), p)))
+ ms.descriptor().provides().stream()
+ .sorted(Comparator.comparing(Provides::service))
+ .map(p -> String.format("provides %s
with %s",
+ p.service(), p.providers()))
.forEach(p -> sb.append(p).append("
").append("\n"));
sb.append("");
return sb.toString();
diff --git a/jdk/make/src/classes/build/tools/module/GenModuleInfoSource.java b/jdk/make/src/classes/build/tools/module/GenModuleInfoSource.java
index e7d932d1141..368fcc7271a 100644
--- a/jdk/make/src/classes/build/tools/module/GenModuleInfoSource.java
+++ b/jdk/make/src/classes/build/tools/module/GenModuleInfoSource.java
@@ -30,219 +30,593 @@ import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
-import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import static java.util.stream.Collectors.*;
/**
* A build tool to extend the module-info.java in the source tree for
- * platform-specific exports, uses, and provides and write to the specified
- * output file. Injecting platform-specific requires is not supported.
+ * platform-specific exports, opens, uses, and provides and write to
+ * the specified output file.
*
- * The extra exports, uses, provides can be specified in module-info.java.extra
- * files and GenModuleInfoSource will be invoked for each module that has
+ * GenModuleInfoSource will be invoked for each module that has
* module-info.java.extra in the source directory.
+ *
+ * The extra exports, opens, uses, provides can be specified
+ * in module-info.java.extra.
+ * Injecting platform-specific requires is not supported.
+ *
+ * @see build.tools.module.ModuleInfoExtraTest for basic testing
*/
public class GenModuleInfoSource {
private final static String USAGE =
- "Usage: GenModuleInfoSource [option] -o
{@code ModuleDescriptor} objects are immutable and safe for use by * multiple concurrent threads.
@@ -95,7 +99,13 @@ public class ModuleDescriptor * module to have an implicitly declared dependence on the module * named by the {@code Requires}. */ - PUBLIC, + TRANSITIVE, + + /** + * The dependence is mandatory in the static phase, during compilation, + * but is optional in the dynamic phase, during execution. + */ + STATIC, /** * The dependence was not explicitly or implicitly declared in the @@ -115,16 +125,18 @@ public class ModuleDescriptor private final String name; private Requires(SetThe hash code is based upon the package name, and for a - * qualified export, the set of modules names to which the package - * is exported. It satisfies the general contract of the {@link - * Object#hashCode Object.hashCode} method. + *
The hash code is based upon the modifiers, the package name, + * and for a qualified export, the set of modules names to which the + * package is exported. It satisfies the general contract of the + * {@link Object#hashCode Object.hashCode} method. * * @return The hash-code value for this module export */ @Override public int hashCode() { - return hash(source, targets); + int hash = mods.hashCode(); + hash = hash * 43 + source.hashCode(); + return hash * 43 + targets.hashCode(); } /** * Tests this module export for equality with the given object. * *
If the given object is not an {@code Exports} then this method - * returns {@code false}. Two module exports objects are equal if the - * package names are equal and the set of target module names is equal. - *
+ * returns {@code false}. Two module exports objects are equal if their + * set of modifiers is equal, the package names are equal and the set + * of target module names is equal. * *This method satisfies the general contract of the {@link * java.lang.Object#equals(Object) Object.equals} method.
@@ -338,8 +376,9 @@ public class ModuleDescriptor if (!(ob instanceof Exports)) return false; Exports other = (Exports)ob; - return Objects.equals(this.source, other.source) && - Objects.equals(this.targets, other.targets); + return Objects.equals(this.mods, other.mods) + && Objects.equals(this.source, other.source) + && Objects.equals(this.targets, other.targets); } /** @@ -349,15 +388,177 @@ public class ModuleDescriptor */ @Override public String toString() { + String s = ModuleDescriptor.toString(mods, source); if (targets.isEmpty()) - return source; + return s; else - return source + " to " + targets; + return s + " to " + targets; + } + } + + + /** + *Represents a module opens directive, may be qualified or + * unqualified.
+ * + *The opens directive in a module declaration declares a + * package to be open to allow all types in the package, and all their + * members, not just public types and their public members to be reflected + * on by APIs that support private access or a way to bypass or suppress + * default Java language access control checks.
+ * + * @see ModuleDescriptor#opens() + * @since 9 + */ + + public final static class Opens { + + /** + * A modifier on a module opens directive. + * + * @since 9 + */ + public static enum Modifier { + + /** + * The opens was not explicitly or implicitly declared in the + * source of the module declaration. + */ + SYNTHETIC, + + /** + * The opens was implicitly declared in the source of the module + * declaration. + */ + MANDATED; + + } + + private final SetThe hash code is based upon the modifiers, the package name, + * and for a qualified opens, the set of modules names to which the + * package is opened. It satisfies the general contract of the + * {@link Object#hashCode Object.hashCode} method. + * + * @return The hash-code value for this module opens + */ + @Override + public int hashCode() { + int hash = mods.hashCode(); + hash = hash * 43 + source.hashCode(); + return hash * 43 + targets.hashCode(); + } + + /** + * Tests this module opens for equality with the given object. + * + *
If the given object is not an {@code Opens} then this method + * returns {@code false}. Two {@code Opens} objects are equal if their + * set of modifiers is equal, the package names are equal and the set + * of target module names is equal.
+ * + *This method satisfies the general contract of the {@link + * java.lang.Object#equals(Object) Object.equals} method.
+ * + * @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 + * dependence that is equal to this module dependence + */ + @Override + public boolean equals(Object ob) { + if (!(ob instanceof Opens)) + return false; + Opens other = (Opens)ob; + return Objects.equals(this.mods, other.mods) + && Objects.equals(this.source, other.source) + && Objects.equals(this.targets, other.targets); + } + + /** + * Returns a string describing module opens. + * + * @return A string describing module opens + */ + @Override + public String toString() { + String s = ModuleDescriptor.toString(mods, source); + if (targets.isEmpty()) + return s; + else + return s + " to " + targets; } - } - /** *A service that a module provides one or more implementations of.
@@ -369,21 +570,15 @@ public class ModuleDescriptor public final static class Provides { private final String service; - private final SetIf the given object is not a {@code Provides} then this method * returns {@code false}. Two {@code Provides} objects are equal if the - * service type is equal and the set of providers is equal.
+ * service type is equal and the list of providers is equal. * *This method satisfies the general contract of the {@link * java.lang.Object#equals(Object) Object.equals} method.
@@ -774,10 +970,7 @@ public class ModuleDescriptor // From module declarations private final String name; - private final SetReturns {@code true} if this is an open module.
+ * + *An open module does not declare any open packages (the {@link #opens() + * opens} method returns an empty set) but the resulting module is treated + * as if all packages are open.
+ * + * @return {@code true} if this is an open module + */ + public boolean isOpen() { + return open; + } + /** *Returns {@code true} if this is an automatic module.
* @@ -933,8 +1148,6 @@ public class ModuleDescriptor * * @return {@code true} if this module descriptor was not generated by * an explicit or implicit module declaration - * - * @jvms 4.7.8 The {@code Synthetic} Attribute */ public boolean isSynthetic() { return synthetic; @@ -949,6 +1162,33 @@ public class ModuleDescriptor return requires; } + /** + *The module exports.
+ * + * @return A possibly-empty unmodifiable set of exported packages + */ + public SetThe module opens directives.
+ * + *Each {@code Opens} object in the set represents a package (and + * the set of target module names when qualified) where all types in the + * package, and all their members, not just public types and their public + * members, can be reflected on when using APIs that bypass or suppress + * default Java language access control checks.
+ * + *This method returns an empty set when invoked on {@link #isOpen() + * open} module.
+ * + * @return A possibly-empty unmodifiable set of open packages + */ + public SetThe service dependences of this module.
* @@ -962,23 +1202,13 @@ public class ModuleDescriptor /** *The services that this module provides.
* - * @return The possibly-empty unmodifiable map of the services that this - * module provides. The map key is fully qualified class name of - * the service type. + * @return The possibly-empty unmodifiable set of the services that this + * module provides */ - public MapThe module exports.
- * - * @return A possibly-empty unmodifiable set of exported packages - */ - public SetExample usage:
+ *{@code ModuleDescriptor} defines the {@link #module module}, {@link + * #openModule openModule}, and {@link #automaticModule automaticModule} + * methods to create builders for building different kinds of modules.
* - *{@code
- * ModuleDescriptor descriptor = new ModuleDescriptor.Builder("m1")
- * .requires("m2")
+ * Example usage:
+ * {@code ModuleDescriptor descriptor = ModuleDescriptor.module("m1")
* .exports("p")
+ * .requires("m2")
* .build();
* }
*
- * @apiNote A {@code Builder} cannot be used to create an {@link
- * ModuleDescriptor#isAutomatic() automatic} or a {@link
- * ModuleDescriptor#isSynthetic() synthetic} module.
+ * @apiNote A {@code Builder} checks the components and invariants as
+ * components are added to the builder. The rational for this is to detect
+ * errors as early as possible and not defer all validation to the
+ * {@link #build build} method. A {@code Builder} cannot be used to create
+ * a {@link ModuleDescriptor#isSynthetic() synthetic} module.
*
* @since 9
*/
public static final class Builder {
-
final String name;
+ final boolean strict; // true if module names are checked
+ boolean open;
boolean automatic;
boolean synthetic;
final Map requires = new HashMap<>();
- final Set uses = new HashSet<>();
+
final Map exports = new HashMap<>();
+ final Map opens = new HashMap<>();
+ final Set concealedPackages = new HashSet<>();
+
+ final Set uses = new HashSet<>();
final Map provides = new HashMap<>();
- Set conceals = Collections.emptySet();
Version version;
String osName;
String osArch;
@@ -1113,29 +1338,30 @@ public class ModuleDescriptor
/**
* Initializes a new builder with the given module name.
*
- * @param name
- * The module name
- *
- * @throws IllegalArgumentException
- * If the module name is {@code null} or is not a legal Java
- * identifier
+ * @param strict
+ * Indicates whether module names are checked or not
*/
- public Builder(String name) {
- this.name = requireModuleName(name);
+ Builder(String name, boolean strict) {
+ this.strict = strict;
+ this.name = (strict) ? requireModuleName(name) : name;
}
- /**
- * Updates the builder so that it builds an automatic module.
- *
- * @return This builder
- *
- * @see ModuleDescriptor#isAutomatic()
- */
- /* package */ Builder automatic() {
- this.automatic = true;
+ /* package */ Builder open(boolean open) {
+ this.open = open;
return this;
}
+ /* package */ Builder automatic(boolean automatic) {
+ this.automatic = automatic;
+ return this;
+ }
+
+ /* package */ boolean isOpen() { return open; }
+
+ /* package */ boolean isAutomatic() {
+ return automatic;
+ }
+
/**
* Adds a dependence on a module.
*
@@ -1165,7 +1391,7 @@ public class ModuleDescriptor
* Adds a dependence on a module with the given (and possibly empty)
* set of modifiers.
*
- * @param mods
+ * @param ms
* The set of modifiers
* @param mn
* The module name
@@ -1179,14 +1405,10 @@ public class ModuleDescriptor
* @throws IllegalStateException
* If the dependence on the module has already been declared
*/
- public Builder requires(Set mods, String mn) {
- if (name.equals(mn))
- throw new IllegalArgumentException("Dependence on self");
- if (requires.containsKey(mn))
- throw new IllegalStateException("Dependence upon " + mn
- + " already declared");
- requires.put(mn, new Requires(mods, mn)); // checks mn
- return this;
+ public Builder requires(Set ms, String mn) {
+ if (strict)
+ mn = requireModuleName(mn);
+ return requires(new Requires(ms, mn));
}
/**
@@ -1208,62 +1430,6 @@ public class ModuleDescriptor
return requires(EnumSet.noneOf(Requires.Modifier.class), mn);
}
- /**
- * Adds a dependence on a module with the given modifier.
- *
- * @param mod
- * The modifier
- * @param mn
- * The module name
- *
- * @return This builder
- *
- * @throws IllegalArgumentException
- * If the module name is {@code null}, is not a legal Java
- * identifier, or is equal to the module name that this builder
- * was initialized to build
- * @throws IllegalStateException
- * If the dependence on the module has already been declared
- */
- public Builder requires(Requires.Modifier mod, String mn) {
- return requires(EnumSet.of(mod), mn);
- }
-
- /**
- * Adds a service dependence.
- *
- * @param st
- * The service type
- *
- * @return This builder
- *
- * @throws IllegalArgumentException
- * If the service type is {@code null} or is not a legal Java
- * identifier
- * @throws IllegalStateException
- * If a dependency on the service type has already been declared
- */
- public Builder uses(String st) {
- if (uses.contains(requireServiceTypeName(st)))
- throw new IllegalStateException("Dependence upon service "
- + st + " already declared");
- uses.add(st);
- return this;
- }
-
- /**
- * Ensures that the given package name has not been declared as an
- * exported or concealed package.
- */
- private void ensureNotExportedOrConcealed(String pn) {
- if (exports.containsKey(pn))
- throw new IllegalStateException("Export of package "
- + pn + " already declared");
- if (conceals.contains(pn))
- throw new IllegalStateException("Concealed package "
- + pn + " already declared");
- }
-
/**
* Adds an export.
*
@@ -1273,18 +1439,90 @@ public class ModuleDescriptor
* @return This builder
*
* @throws IllegalStateException
- * If the package is already declared as an exported or
- * concealed package
+ * If the package is already declared as a package with the
+ * {@link #contains contains} method or the package is already
+ * declared as exported
*/
public Builder exports(Exports e) {
- String pn = e.source();
- ensureNotExportedOrConcealed(pn);
- exports.put(pn, e);
+ // can't be exported and concealed
+ String source = e.source();
+ if (concealedPackages.contains(source)) {
+ throw new IllegalStateException("Package " + source
+ + " already declared");
+ }
+ if (exports.containsKey(source)) {
+ throw new IllegalStateException("Exported package " + source
+ + " already declared");
+ }
+
+ exports.put(source, e);
return this;
}
/**
- * Adds an export to a set of target modules.
+ * Adds an export, with the given (and possibly empty) set of modifiers,
+ * to export a package to a set of target modules.
+ *
+ * @param ms
+ * The set of modifiers
+ * @param pn
+ * The package name
+ * @param targets
+ * The set of target modules names
+ *
+ * @return This builder
+ *
+ * @throws IllegalArgumentException
+ * If the package name or any of the target modules is {@code
+ * null} or is not a legal Java identifier, or the set of
+ * targets is empty
+ * @throws IllegalStateException
+ * If the package is already declared as a package with the
+ * {@link #contains contains} method or the package is already
+ * declared as exported
+ */
+ public Builder exports(Set ms,
+ String pn,
+ Set targets)
+ {
+ Exports e = new Exports(ms, requirePackageName(pn), targets);
+
+ // check targets
+ targets = e.targets();
+ if (targets.isEmpty())
+ throw new IllegalArgumentException("Empty target set");
+ if (strict)
+ targets.stream().forEach(Checks::requireModuleName);
+
+ return exports(e);
+ }
+
+ /**
+ * Adds an unqualified export with the given (and possibly empty) set
+ * of modifiers.
+ *
+ * @param ms
+ * The set of modifiers
+ * @param pn
+ * The package name
+ *
+ * @return This builder
+ *
+ * @throws IllegalArgumentException
+ * If the package name is {@code null} or is not a legal Java
+ * identifier
+ * @throws IllegalStateException
+ * If the package is already declared as a package with the
+ * {@link #contains contains} method or the package is already
+ * declared as exported
+ */
+ public Builder exports(Set ms, String pn) {
+ Exports e = new Exports(ms, requirePackageName(pn), Collections.emptySet());
+ return exports(e);
+ }
+
+ /**
+ * Adds an export to export a package to a set of target modules.
*
* @param pn
* The package name
@@ -1298,38 +1536,16 @@ public class ModuleDescriptor
* null} or is not a legal Java identifier, or the set of
* targets is empty
* @throws IllegalStateException
- * If the package is already declared as an exported or
- * concealed package
+ * If the package is already declared as a package with the
+ * {@link #contains contains} method or the package is already
+ * declared as exported
*/
public Builder exports(String pn, Set targets) {
- ensureNotExportedOrConcealed(pn);
- exports.put(pn, new Exports(pn, targets)); // checks pn and targets
- return this;
+ return exports(Collections.emptySet(), pn, targets);
}
/**
- * Adds an export to a target module.
- *
- * @param pn
- * The package name
- * @param target
- * The target module name
- *
- * @return This builder
- *
- * @throws IllegalArgumentException
- * If the package name or target module is {@code null} or is
- * not a legal Java identifier
- * @throws IllegalStateException
- * If the package is already declared as an exported or
- * concealed package
- */
- public Builder exports(String pn, String target) {
- return exports(pn, Collections.singleton(target));
- }
-
- /**
- * Adds an export.
+ * Adds an unqualified export.
*
* @param pn
* The package name
@@ -1340,18 +1556,186 @@ public class ModuleDescriptor
* If the package name is {@code null} or is not a legal Java
* identifier
* @throws IllegalStateException
- * If the package is already declared as an exported or
- * concealed package
+ * If the package is already declared as a package with the
+ * {@link #contains contains} method or the package is already
+ * declared as exported
*/
public Builder exports(String pn) {
- ensureNotExportedOrConcealed(pn);
- exports.put(pn, new Exports(pn)); // checks pn
+ return exports(Collections.emptySet(), pn);
+ }
+
+ /**
+ * Adds an opens directive.
+ *
+ * @param obj
+ * The {@code Opens} object
+ *
+ * @return This builder
+ *
+ * @throws IllegalStateException
+ * If the package is already declared as a package with the
+ * {@link #contains contains} method, the package is already
+ * declared as open, or this is a builder for an open module
+ */
+ public Builder opens(Opens obj) {
+ if (open) {
+ throw new IllegalStateException("open modules cannot declare"
+ + " open packages");
+ }
+
+ // can't be open and concealed
+ String source = obj.source();
+ if (concealedPackages.contains(source)) {
+ throw new IllegalStateException("Package " + source
+ + " already declared");
+ }
+ if (opens.containsKey(source)) {
+ throw new IllegalStateException("Open package " + source
+ + " already declared");
+ }
+
+ opens.put(source, obj);
return this;
}
+
+ /**
+ * Adds an opens directive, with the given (and possibly empty)
+ * set of modifiers, to open a package to a set of target modules.
+ *
+ * @param ms
+ * The set of modifiers
+ * @param pn
+ * The package name
+ * @param targets
+ * The set of target modules names
+ *
+ * @return This builder
+ *
+ * @throws IllegalArgumentException
+ * If the package name or any of the target modules is {@code
+ * null} or is not a legal Java identifier, or the set of
+ * targets is empty
+ * @throws IllegalStateException
+ * If the package is already declared as a package with the
+ * {@link #contains contains} method, the package is already
+ * declared as open, or this is a builder for an open module
+ */
+ public Builder opens(Set ms,
+ String pn,
+ Set targets)
+ {
+ Opens e = new Opens(ms, requirePackageName(pn), targets);
+
+ // check targets
+ targets = e.targets();
+ if (targets.isEmpty())
+ throw new IllegalArgumentException("Empty target set");
+ if (strict)
+ targets.stream().forEach(Checks::requireModuleName);
+
+ return opens(e);
+ }
+
+ /**
+ * Adds an opens directive to open a package with the given (and
+ * possibly empty) set of modifiers.
+ *
+ * @param ms
+ * The set of modifiers
+ * @param pn
+ * The package name
+ *
+ * @return This builder
+ *
+ * @throws IllegalArgumentException
+ * If the package name is {@code null} or is not a legal Java
+ * identifier
+ * @throws IllegalStateException
+ * If the package is already declared as a package with the
+ * {@link #contains contains} method, the package is already
+ * declared as open, or this is a builder for an open module
+ */
+ public Builder opens(Set ms, String pn) {
+ Opens e = new Opens(ms, requirePackageName(pn), Collections.emptySet());
+ return opens(e);
+ }
+
+ /**
+ * Adds an opens directive to open a package to a set of target
+ * modules.
+ *
+ * @param pn
+ * The package name
+ * @param targets
+ * The set of target modules names
+ *
+ * @return This builder
+ *
+ * @throws IllegalArgumentException
+ * If the package name or any of the target modules is {@code
+ * null} or is not a legal Java identifier, or the set of
+ * targets is empty
+ * @throws IllegalStateException
+ * If the package is already declared as a package with the
+ * {@link #contains contains} method, the package is already
+ * declared as open, or this is a builder for an open module
+ */
+ public Builder opens(String pn, Set targets) {
+ return opens(Collections.emptySet(), pn, targets);
+ }
+
+ /**
+ * Adds an opens directive to open a package.
+ *
+ * @param pn
+ * The package name
+ *
+ * @return This builder
+ *
+ * @throws IllegalArgumentException
+ * If the package name is {@code null} or is not a legal Java
+ * identifier
+ * @throws IllegalStateException
+ * If the package is already declared as a package with the
+ * {@link #contains contains} method, the package is already
+ * declared as open, or this is a builder for an open module
+ */
+ public Builder opens(String pn) {
+ return opens(Collections.emptySet(), pn);
+ }
+
+
// Used by ModuleInfo, after a packageFinder is invoked
- /* package */ Set exportedPackages() {
- return exports.keySet();
+ /* package */ Set exportedAndOpenPackages() {
+ if (opens.isEmpty())
+ return exports.keySet();
+ Set result = new HashSet<>();
+ result.addAll(exports.keySet());
+ result.addAll(opens.keySet());
+ return result;
+ }
+
+ /**
+ * Adds a service dependence.
+ *
+ * @param service
+ * The service type
+ *
+ * @return This builder
+ *
+ * @throws IllegalArgumentException
+ * If the service type is {@code null} or is not a legal Java
+ * identifier
+ * @throws IllegalStateException
+ * If a dependency on the service type has already been declared
+ */
+ public Builder uses(String service) {
+ if (uses.contains(requireServiceTypeName(service)))
+ throw new IllegalStateException("Dependence upon service "
+ + service + " already declared");
+ uses.add(service);
+ return this;
}
/**
@@ -1376,38 +1760,47 @@ public class ModuleDescriptor
}
/**
- * Provides service {@code st} with implementations {@code pcs}.
+ * Provides implementations of a service.
*
- * @param st
+ * @param service
* The service type
- * @param pcs
- * The set of provider class names
+ * @param providers
+ * The list of provider or provider factory class names
*
* @return This builder
*
* @throws IllegalArgumentException
* If the service type or any of the provider class names is
- * {@code null} or is not a legal Java identifier, or the set
+ * {@code null} or is not a legal Java identifier, or the list
* of provider class names is empty
* @throws IllegalStateException
* If the providers for the service type have already been
* declared
*/
- public Builder provides(String st, Set pcs) {
- if (provides.containsKey(st))
+ public Builder provides(String service, List providers) {
+ if (provides.containsKey(service))
throw new IllegalStateException("Providers of service "
- + st + " already declared");
- provides.put(st, new Provides(st, pcs)); // checks st and pcs
+ + service + " already declared by " + name);
+
+ Provides p = new Provides(requireServiceTypeName(service), providers);
+
+ // check providers after the set has been copied.
+ List providerNames = p.providers();
+ if (providerNames.isEmpty())
+ throw new IllegalArgumentException("Empty providers set");
+ providerNames.forEach(Checks::requireServiceProviderName);
+
+ provides.put(service, p);
return this;
}
/**
- * Provides service {@code st} with implementation {@code pc}.
+ * Provides an implementation of a service.
*
- * @param st
+ * @param service
* The service type
- * @param pc
- * The provider class name
+ * @param provider
+ * The provider or provider factory class name
*
* @return This builder
*
@@ -1418,15 +1811,17 @@ public class ModuleDescriptor
* If the providers for the service type have already been
* declared
*/
- public Builder provides(String st, String pc) {
- return provides(st, Collections.singleton(pc));
+ public Builder provides(String service, String provider) {
+ if (provider == null)
+ throw new IllegalArgumentException("'provider' is null");
+ return provides(service, List.of(provider));
}
/**
- * Adds a set of (possible empty) concealed packages.
+ * Adds a (possible empty) set of packages to the module
*
* @param pns
- * The set of package names of the concealed packages
+ * The set of package names
*
* @return This builder
*
@@ -1434,16 +1829,17 @@ public class ModuleDescriptor
* If any of the package names is {@code null} or is not a
* legal Java identifier
* @throws IllegalStateException
- * If any of packages are already declared as a concealed or
- * exported package
+ * If any of packages are already declared as packages in
+ * the module. This includes packages that are already
+ * declared as exported or open packages.
*/
- public Builder conceals(Set pns) {
- pns.forEach(this::conceals);
+ public Builder contains(Set pns) {
+ pns.forEach(this::contains);
return this;
}
/**
- * Adds a concealed package.
+ * Adds a package to the module.
*
* @param pn
* The package name
@@ -1454,15 +1850,25 @@ public class ModuleDescriptor
* If the package name is {@code null}, or is not a legal Java
* identifier
* @throws IllegalStateException
- * If the package is already declared as a concealed or exported
- * package
+ * If the package is already declared as a package in the
+ * module. This includes the package already declared as an
+ * exported or open package.
*/
- public Builder conceals(String pn) {
+ public Builder contains(String pn) {
Checks.requirePackageName(pn);
- ensureNotExportedOrConcealed(pn);
- if (conceals.isEmpty())
- conceals = new HashSet<>();
- conceals.add(pn);
+ if (concealedPackages.contains(pn)) {
+ throw new IllegalStateException("Package " + pn
+ + " already declared");
+ }
+ if (exports.containsKey(pn)) {
+ throw new IllegalStateException("Exported package "
+ + pn + " already declared");
+ }
+ if (opens.containsKey(pn)) {
+ throw new IllegalStateException("Open package "
+ + pn + " already declared");
+ }
+ concealedPackages.add(pn);
return this;
}
@@ -1473,13 +1879,8 @@ public class ModuleDescriptor
* The version
*
* @return This builder
- *
- * @throws IllegalStateException
- * If the module version is already set
*/
public Builder version(Version v) {
- if (version != null)
- throw new IllegalStateException("module version already set");
version = requireNonNull(v);
return this;
}
@@ -1494,16 +1895,11 @@ public class ModuleDescriptor
*
* @throws IllegalArgumentException
* If {@code v} is null or cannot be parsed as a version string
- * @throws IllegalStateException
- * If the module version is already set
*
* @see Version#parse(String)
*/
public Builder version(String v) {
- if (version != null)
- throw new IllegalStateException("module version already set");
- version = Version.parse(v);
- return this;
+ return version(Version.parse(v));
}
/**
@@ -1516,12 +1912,8 @@ public class ModuleDescriptor
*
* @throws IllegalArgumentException
* If {@code mainClass} is null or is not a legal Java identifier
- * @throws IllegalStateException
- * If the module main class is already set
*/
public Builder mainClass(String mc) {
- if (mainClass != null)
- throw new IllegalStateException("main class already set");
mainClass = requireJavaIdentifier("main class name", mc);
return this;
}
@@ -1536,12 +1928,8 @@ public class ModuleDescriptor
*
* @throws IllegalArgumentException
* If {@code name} is null or the empty String
- * @throws IllegalStateException
- * If the operating system name is already set
*/
public Builder osName(String name) {
- if (osName != null)
- throw new IllegalStateException("OS name already set");
if (name == null || name.isEmpty())
throw new IllegalArgumentException("OS name is null or empty");
osName = name;
@@ -1558,12 +1946,8 @@ public class ModuleDescriptor
*
* @throws IllegalArgumentException
* If {@code name} is null or the empty String
- * @throws IllegalStateException
- * If the operating system architecture is already set
*/
public Builder osArch(String arch) {
- if (osArch != null)
- throw new IllegalStateException("OS arch already set");
if (arch == null || arch.isEmpty())
throw new IllegalArgumentException("OS arch is null or empty");
osArch = arch;
@@ -1580,12 +1964,8 @@ public class ModuleDescriptor
*
* @throws IllegalArgumentException
* If {@code name} is null or the empty String
- * @throws IllegalStateException
- * If the operating system version is already set
*/
public Builder osVersion(String version) {
- if (osVersion != null)
- throw new IllegalStateException("OS version already set");
if (version == null || version.isEmpty())
throw new IllegalArgumentException("OS version is null or empty");
osVersion = version;
@@ -1597,7 +1977,6 @@ public class ModuleDescriptor
return this;
}
-
/* package */ Builder synthetic(boolean v) {
this.synthetic = v;
return this;
@@ -1609,16 +1988,24 @@ public class ModuleDescriptor
* @return The module descriptor
*/
public ModuleDescriptor build() {
- assert name != null;
+ Set requires = new HashSet<>(this.requires.values());
+
+ Set packages = new HashSet<>(exportedAndOpenPackages());
+ packages.addAll(concealedPackages);
+
+ Set exports = new HashSet<>(this.exports.values());
+ Set opens = new HashSet<>(this.opens.values());
+
+ Set provides = new HashSet<>(this.provides.values());
- Set packages = new HashSet<>(conceals);
- packages.addAll(exportedPackages());
return new ModuleDescriptor(name,
+ open,
automatic,
synthetic,
requires,
- uses,
exports,
+ opens,
+ uses,
provides,
version,
mainClass,
@@ -1631,7 +2018,6 @@ public class ModuleDescriptor
}
-
/**
* Compares this module descriptor to another.
*
@@ -1689,11 +2075,13 @@ public class ModuleDescriptor
return false;
ModuleDescriptor that = (ModuleDescriptor)ob;
return (name.equals(that.name)
+ && open == that.open
&& automatic == that.automatic
&& synthetic == that.synthetic
&& requires.equals(that.requires)
- && uses.equals(that.uses)
&& exports.equals(that.exports)
+ && opens.equals(that.opens)
+ && uses.equals(that.uses)
&& provides.equals(that.provides)
&& Objects.equals(version, that.version)
&& Objects.equals(mainClass, that.mainClass)
@@ -1720,11 +2108,13 @@ public class ModuleDescriptor
int hc = hash;
if (hc == 0) {
hc = name.hashCode();
+ hc = hc * 43 + Boolean.hashCode(open);
hc = hc * 43 + Boolean.hashCode(automatic);
hc = hc * 43 + Boolean.hashCode(synthetic);
hc = hc * 43 + requires.hashCode();
- hc = hc * 43 + uses.hashCode();
hc = hc * 43 + exports.hashCode();
+ hc = hc * 43 + opens.hashCode();
+ hc = hc * 43 + uses.hashCode();
hc = hc * 43 + provides.hashCode();
hc = hc * 43 + Objects.hashCode(version);
hc = hc * 43 + Objects.hashCode(mainClass);
@@ -1748,36 +2138,101 @@ public class ModuleDescriptor
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
- sb.append("Module { name: ").append(toNameAndVersion());
+
+ if (isOpen())
+ sb.append("open ");
+ sb.append("module { name: ").append(toNameAndVersion());
if (!requires.isEmpty())
sb.append(", ").append(requires);
if (!uses.isEmpty())
sb.append(", ").append(uses);
if (!exports.isEmpty())
sb.append(", exports: ").append(exports);
+ if (!opens.isEmpty())
+ sb.append(", opens: ").append(opens);
if (!provides.isEmpty()) {
- sb.append(", provides: [");
- for (Map.Entry entry : provides.entrySet()) {
- sb.append(entry.getKey())
- .append(" with ")
- .append(entry.getValue());
- }
- sb.append("]");
+ sb.append(", provides: ").append(provides);
}
sb.append(" }");
return sb.toString();
}
+
+ /**
+ * Instantiates a builder to build a module descriptor.
+ *
+ * @param name
+ * The module name
+ *
+ * @return A new builder
+ *
+ * @throws IllegalArgumentException
+ * If the module name is {@code null} or is not a legal Java
+ * identifier
+ */
+ public static Builder module(String name) {
+ return new Builder(name, true);
+ }
+
+ /**
+ * Instantiates a builder to build a module descriptor for an open module.
+ * An open module does not declare any open packages but the resulting
+ * module is treated as if all packages are open.
+ *
+ * As an example, the following creates a module descriptor for an open
+ * name "{@code m}" containing two packages, one of which is exported.
+ * {@code
+ * ModuleDescriptor descriptor = ModuleDescriptor.openModule("m")
+ * .requires("java.base")
+ * .exports("p")
+ * .contains("q")
+ * .build();
+ * }
+ *
+ * @param name
+ * The module name
+ *
+ * @return A new builder that builds an open module
+ *
+ * @throws IllegalArgumentException
+ * If the module name is {@code null} or is not a legal Java
+ * identifier
+ */
+ public static Builder openModule(String name) {
+ return new Builder(name, true).open(true);
+ }
+
+ /**
+ * Instantiates a builder to build a module descriptor for an automatic
+ * module. Automatic modules receive special treatment during resolution
+ * (see {@link Configuration}) so that they read all other modules. When
+ * Instantiated in the Java virtual machine as a {@link java.lang.reflect.Module}
+ * then the Module reads every unnamed module in the Java virtual machine.
+ *
+ * @param name
+ * The module name
+ *
+ * @return A new builder that builds an automatic module
+ *
+ * @throws IllegalArgumentException
+ * If the module name is {@code null} or is not a legal Java
+ * identifier
+ *
+ * @see ModuleFinder#of(Path[])
+ */
+ public static Builder automaticModule(String name) {
+ return new Builder(name, true).automatic(true);
+ }
+
+
/**
* Reads the binary form of a module declaration from an input stream
* as a module descriptor.
*
* If the descriptor encoded in the input stream does not indicate a
- * set of concealed packages then the {@code packageFinder} will be
- * invoked. The packages it returns, except for those indicated as
- * exported in the encoded descriptor, will be considered to be concealed.
- * If the {@code packageFinder} throws an {@link UncheckedIOException} then
- * {@link IOException} cause will be re-thrown.
+ * set of packages in the module then the {@code packageFinder} will be
+ * invoked. If the {@code packageFinder} throws an {@link UncheckedIOException}
+ * then {@link IOException} cause will be re-thrown.
*
* If there are bytes following the module descriptor then it is
* implementation specific as to whether those bytes are read, ignored,
@@ -1789,12 +2244,12 @@ public class ModuleDescriptor
*
* @apiNote The {@code packageFinder} parameter is for use when reading
* module descriptors from legacy module-artifact formats that do not
- * record the set of concealed packages in the descriptor itself.
+ * record the set of packages in the descriptor itself.
*
* @param in
* The input stream
* @param packageFinder
- * A supplier that can produce a set of package names
+ * A supplier that can produce the set of packages
*
* @return The module descriptor
*
@@ -1834,10 +2289,7 @@ public class ModuleDescriptor
* as a module descriptor.
*
*
If the descriptor encoded in the byte buffer does not indicate a
- * set of concealed packages then the {@code packageFinder} will be
- * invoked. The packages it returns, except for those indicated as
- * exported in the encoded descriptor, will be considered to be
- * concealed.
+ * set of packages then the {@code packageFinder} will be invoked.
*
* The module descriptor is read from the buffer stating at index
* {@code p}, where {@code p} is the buffer's {@link ByteBuffer#position()
@@ -1853,12 +2305,12 @@ public class ModuleDescriptor
*
* @apiNote The {@code packageFinder} parameter is for use when reading
* module descriptors from legacy module-artifact formats that do not
- * record the set of concealed packages in the descriptor itself.
+ * record the set of packages in the descriptor itself.
*
* @param bb
* The byte buffer
* @param packageFinder
- * A supplier that can produce a set of package names
+ * A supplier that can produce the set of packages
*
* @return The module descriptor
*
@@ -1908,6 +2360,15 @@ public class ModuleDescriptor
}
}
+ /**
+ * Returns a string containing the given set of modifiers and label.
+ */
+ private static String toString(Set mods, String what) {
+ return (Stream.concat(mods.stream().map(e -> e.toString().toLowerCase()),
+ Stream.of(what)))
+ .collect(Collectors.joining(" "));
+ }
+
static {
/**
* Setup the shared secret to allow code in other packages access
@@ -1915,25 +2376,48 @@ public class ModuleDescriptor
*/
jdk.internal.misc.SharedSecrets
.setJavaLangModuleAccess(new jdk.internal.misc.JavaLangModuleAccess() {
+ @Override
+ public Builder newModuleBuilder(String mn, boolean strict) {
+ return new Builder(mn, strict);
+ }
+
+ @Override
+ public Builder newOpenModuleBuilder(String mn, boolean strict) {
+ return new Builder(mn, strict).open(true);
+ }
@Override
public Requires newRequires(Set ms, String mn) {
- return new Requires(ms, mn, false);
+ return new Requires(ms, mn, true);
}
@Override
- public Exports newExports(String source, Set targets) {
- return new Exports(source, targets, false);
+ public Exports newExports(Set ms, String source) {
+ return new Exports(ms, source, Collections.emptySet(), true);
}
@Override
- public Exports newExports(String source) {
- return new Exports(source, false);
+ public Exports newExports(Set ms,
+ String source,
+ Set targets) {
+ return new Exports(ms, source, targets, true);
}
@Override
- public Provides newProvides(String service, Set providers) {
- return new Provides(service, providers, false);
+ public Opens newOpens(Set ms,
+ String source,
+ Set targets) {
+ return new Opens(ms, source, targets, true);
+ }
+
+ @Override
+ public Opens newOpens(Set ms, String source) {
+ return new Opens(ms, source, Collections.emptySet(), true);
+ }
+
+ @Override
+ public Provides newProvides(String service, List providers) {
+ return new Provides(service, providers, true);
}
@Override
@@ -1949,25 +2433,30 @@ public class ModuleDescriptor
@Override
public ModuleDescriptor newModuleDescriptor(String name,
+ boolean open,
boolean automatic,
boolean synthetic,
Set requires,
- Set uses,
Set exports,
- Map provides,
+ Set opens,
+ Set uses,
+ Set provides,
Version version,
String mainClass,
String osName,
String osArch,
String osVersion,
Set packages,
- ModuleHashes hashes) {
+ ModuleHashes hashes,
+ int hashCode) {
return new ModuleDescriptor(name,
+ open,
automatic,
synthetic,
requires,
- uses,
exports,
+ opens,
+ uses,
provides,
version,
mainClass,
@@ -1975,7 +2464,14 @@ public class ModuleDescriptor
osArch,
osVersion,
packages,
- hashes);
+ hashes,
+ hashCode,
+ false);
+ }
+
+ @Override
+ public Optional hashes(ModuleDescriptor descriptor) {
+ return descriptor.hashes();
}
@Override
@@ -1994,11 +2490,6 @@ public class ModuleDescriptor
return new ModuleReference(descriptor, location, s, true, null);
}
- @Override
- public Optional hashes(ModuleDescriptor descriptor) {
- return descriptor.hashes();
- }
-
@Override
public ModuleFinder newModulePath(Runtime.Version version,
boolean isLinkPhase,
diff --git a/jdk/src/java.base/share/classes/java/lang/module/ModuleFinder.java b/jdk/src/java.base/share/classes/java/lang/module/ModuleFinder.java
index 4e72a52cf22..4b98bf48416 100644
--- a/jdk/src/java.base/share/classes/java/lang/module/ModuleFinder.java
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleFinder.java
@@ -233,10 +233,11 @@ public interface ModuleFinder {
* ModuleDescriptor.Version} and ignored if it cannot be parsed as
* a {@code Version}.
*
- * For the module name, then all non-alphanumeric
- * characters ({@code [^A-Za-z0-9])} are replaced with a dot
- * ({@code "."}), all repeating dots are replaced with one dot,
- * and all leading and trailing dots are removed.
+ * For the module name, then any trailing digits and dots
+ * are removed, all non-alphanumeric characters ({@code [^A-Za-z0-9]})
+ * are replaced with a dot ({@code "."}), all repeating dots are
+ * replaced with one dot, and all leading and trailing dots are
+ * removed.
*
* 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
diff --git a/jdk/src/java.base/share/classes/java/lang/module/ModuleInfo.java b/jdk/src/java.base/share/classes/java/lang/module/ModuleInfo.java
index 1d0e22fec11..b1c82819aa9 100644
--- a/jdk/src/java.base/share/classes/java/lang/module/ModuleInfo.java
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -31,13 +31,17 @@ import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
-import java.lang.module.ModuleDescriptor.Requires.Modifier;
+import java.lang.module.ModuleDescriptor.Builder;
+import java.lang.module.ModuleDescriptor.Requires;
+import java.lang.module.ModuleDescriptor.Exports;
+import java.lang.module.ModuleDescriptor.Opens;
import java.nio.ByteBuffer;
import java.nio.BufferUnderflowException;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
@@ -56,15 +60,12 @@ import static jdk.internal.module.ClassFileConstants.*;
final class ModuleInfo {
- // supplies the set of packages when ConcealedPackages not present
+ // supplies the set of packages when ModulePackages attribute not present
private final Supplier> packageFinder;
- // indicates if the Hashes attribute should be parsed
+ // indicates if the ModuleHashes attribute should be parsed
private final boolean parseHashes;
- // the builder, created when parsing
- private ModuleDescriptor.Builder builder;
-
private ModuleInfo(Supplier> pf, boolean ph) {
packageFinder = pf;
parseHashes = ph;
@@ -86,9 +87,8 @@ final class ModuleInfo {
{
try {
return new ModuleInfo(pf).doRead(new DataInputStream(in));
- } catch (IllegalArgumentException iae) {
- // IllegalArgumentException means a malformed class
- throw invalidModuleDescriptor(iae.getMessage());
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ throw invalidModuleDescriptor(e.getMessage());
} catch (EOFException x) {
throw truncatedModuleDescriptor();
}
@@ -105,9 +105,8 @@ final class ModuleInfo {
{
try {
return new ModuleInfo(pf).doRead(new DataInputWrapper(bb));
- } catch (IllegalArgumentException iae) {
- // IllegalArgumentException means a malformed class
- throw invalidModuleDescriptor(iae.getMessage());
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ throw invalidModuleDescriptor(e.getMessage());
} catch (EOFException x) {
throw truncatedModuleDescriptor();
} catch (IOException ioe) {
@@ -117,7 +116,7 @@ final class ModuleInfo {
/**
* Reads a {@code module-info.class} from the given byte buffer
- * but ignore the {@code Hashes} attribute.
+ * but ignore the {@code ModuleHashes} attribute.
*
* @throws InvalidModuleDescriptorException
* @throws UncheckedIOException
@@ -127,8 +126,8 @@ final class ModuleInfo {
{
try {
return new ModuleInfo(pf, false).doRead(new DataInputWrapper(bb));
- } catch (IllegalArgumentException iae) {
- throw invalidModuleDescriptor(iae.getMessage());
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ throw invalidModuleDescriptor(e.getMessage());
} catch (EOFException x) {
throw truncatedModuleDescriptor();
} catch (IOException ioe) {
@@ -164,12 +163,8 @@ final class ModuleInfo {
throw invalidModuleDescriptor("access_flags should be ACC_MODULE");
int this_class = in.readUnsignedShort();
- String mn = cpool.getClassName(this_class);
- int suffix = mn.indexOf("/module-info");
- if (suffix < 1)
- throw invalidModuleDescriptor("this_class not of form name/module-info");
- mn = mn.substring(0, suffix).replace('/', '.');
- builder = new ModuleDescriptor.Builder(mn);
+ if (this_class != 0)
+ throw invalidModuleDescriptor("this_class must be 0");
int super_class = in.readUnsignedShort();
if (super_class > 0)
@@ -192,6 +187,13 @@ final class ModuleInfo {
// the names of the attributes found in the class file
Set attributes = new HashSet<>();
+ Builder builder = null;
+ Set packages = null;
+ String version = null;
+ String mainClass = null;
+ String[] osValues = null;
+ ModuleHashes hashes = null;
+
for (int i = 0; i < attributes_count ; i++) {
int name_index = in.readUnsignedShort();
String attribute_name = cpool.getUtf8(name_index);
@@ -206,28 +208,28 @@ final class ModuleInfo {
switch (attribute_name) {
case MODULE :
- readModuleAttribute(mn, in, cpool);
+ builder = readModuleAttribute(in, cpool);
break;
- case CONCEALED_PACKAGES :
- readConcealedPackagesAttribute(in, cpool);
+ case MODULE_PACKAGES :
+ packages = readModulePackagesAttribute(in, cpool);
break;
- case VERSION :
- readVersionAttribute(in, cpool);
+ case MODULE_VERSION :
+ version = readModuleVersionAttribute(in, cpool);
break;
- case MAIN_CLASS :
- readMainClassAttribute(in, cpool);
+ case MODULE_MAIN_CLASS :
+ mainClass = readModuleMainClassAttribute(in, cpool);
break;
- case TARGET_PLATFORM :
- readTargetPlatformAttribute(in, cpool);
+ case MODULE_TARGET :
+ osValues = readModuleTargetAttribute(in, cpool);
break;
- case HASHES :
+ case MODULE_HASHES :
if (parseHashes) {
- readHashesAttribute(in, cpool);
+ hashes = readModuleHashesAttribute(in, cpool);
} else {
in.skipBytes(length);
}
@@ -245,53 +247,91 @@ final class ModuleInfo {
}
// the Module attribute is required
- if (!attributes.contains(MODULE)) {
+ if (builder == null) {
throw invalidModuleDescriptor(MODULE + " attribute not found");
}
- // If the ConcealedPackages attribute is not present then the
- // packageFinder is used to to find any non-exported packages.
- if (!attributes.contains(CONCEALED_PACKAGES) && packageFinder != null) {
- Set pkgs;
+ // If the ModulePackages attribute is not present then the packageFinder
+ // is used to find the set of packages
+ boolean usedPackageFinder = false;
+ if (packages == null && packageFinder != null) {
try {
- pkgs = new HashSet<>(packageFinder.get());
+ packages = new HashSet<>(packageFinder.get());
} catch (UncheckedIOException x) {
throw x.getCause();
}
- pkgs.removeAll(builder.exportedPackages());
- builder.conceals(pkgs);
+ usedPackageFinder = true;
+ }
+ if (packages != null) {
+ for (String pn : builder.exportedAndOpenPackages()) {
+ if (!packages.contains(pn)) {
+ String tail;
+ if (usedPackageFinder) {
+ tail = " not found by package finder";
+ } else {
+ tail = " missing from ModulePackages attribute";
+ }
+ throw invalidModuleDescriptor("Package " + pn + tail);
+ }
+ packages.remove(pn);
+ }
+ builder.contains(packages);
}
- // Was the Synthetic attribute present?
- if (attributes.contains(SYNTHETIC))
- builder.synthetic(true);
+ if (version != null)
+ builder.version(version);
+ if (mainClass != null)
+ builder.mainClass(mainClass);
+ if (osValues != null) {
+ if (osValues[0] != null) builder.osName(osValues[0]);
+ if (osValues[1] != null) builder.osArch(osValues[1]);
+ if (osValues[2] != null) builder.osVersion(osValues[2]);
+ }
+ if (hashes != null)
+ builder.hashes(hashes);
return builder.build();
}
/**
- * Reads the Module attribute.
+ * Reads the Module attribute, returning the ModuleDescriptor.Builder to
+ * build the corresponding ModuleDescriptor.
*/
- private void readModuleAttribute(String mn, DataInput in, ConstantPool cpool)
+ private Builder readModuleAttribute(DataInput in, ConstantPool cpool)
throws IOException
{
+ // module_name
+ int module_name_index = in.readUnsignedShort();
+ String mn = cpool.getUtf8AsBinaryName(module_name_index);
+
+ Builder builder = new ModuleDescriptor.Builder(mn, /*strict*/ false);
+
+ int module_flags = in.readUnsignedShort();
+ boolean open = ((module_flags & ACC_OPEN) != 0);
+ if (open)
+ builder.open(true);
+ if ((module_flags & ACC_SYNTHETIC) != 0)
+ builder.synthetic(true);
+
int requires_count = in.readUnsignedShort();
boolean requiresJavaBase = false;
for (int i=0; i mods;
+ String dn = cpool.getUtf8AsBinaryName(index);
+ Set mods;
if (flags == 0) {
mods = Collections.emptySet();
} else {
mods = new HashSet<>();
- if ((flags & ACC_PUBLIC) != 0)
- mods.add(Modifier.PUBLIC);
+ if ((flags & ACC_TRANSITIVE) != 0)
+ mods.add(Requires.Modifier.TRANSITIVE);
+ if ((flags & ACC_STATIC_PHASE) != 0)
+ mods.add(Requires.Modifier.STATIC);
if ((flags & ACC_SYNTHETIC) != 0)
- mods.add(Modifier.SYNTHETIC);
+ mods.add(Requires.Modifier.SYNTHETIC);
if ((flags & ACC_MANDATED) != 0)
- mods.add(Modifier.MANDATED);
+ mods.add(Requires.Modifier.MANDATED);
}
builder.requires(mods, dn);
if (dn.equals("java.base"))
@@ -311,17 +351,66 @@ final class ModuleInfo {
if (exports_count > 0) {
for (int i=0; i mods;
+ int flags = in.readUnsignedShort();
+ if (flags == 0) {
+ mods = Collections.emptySet();
+ } else {
+ mods = new HashSet<>();
+ if ((flags & ACC_SYNTHETIC) != 0)
+ mods.add(Exports.Modifier.SYNTHETIC);
+ if ((flags & ACC_MANDATED) != 0)
+ mods.add(Exports.Modifier.MANDATED);
+ }
+
int exports_to_count = in.readUnsignedShort();
if (exports_to_count > 0) {
Set targets = new HashSet<>(exports_to_count);
for (int j=0; j 0) {
+ if (open) {
+ throw invalidModuleDescriptor("The opens table for an open"
+ + " module must be 0 length");
+ }
+ for (int i=0; i mods;
+ int flags = in.readUnsignedShort();
+ if (flags == 0) {
+ mods = Collections.emptySet();
+ } else {
+ mods = new HashSet<>();
+ if ((flags & ACC_SYNTHETIC) != 0)
+ mods.add(Opens.Modifier.SYNTHETIC);
+ if ((flags & ACC_MANDATED) != 0)
+ mods.add(Opens.Modifier.MANDATED);
+ }
+
+ int open_to_count = in.readUnsignedShort();
+ if (open_to_count > 0) {
+ Set targets = new HashSet<>(open_to_count);
+ for (int j=0; j 0) {
for (int i=0; i 0) {
- Map> pm = new HashMap<>();
for (int i=0; i providers = pm.get(sn);
- if (providers == null) {
- providers = new LinkedHashSet<>(); // preserve order
- pm.put(sn, providers);
+ String sn = cpool.getClassNameAsBinaryName(index);
+ int with_count = in.readUnsignedShort();
+ List providers = new ArrayList<>(with_count);
+ for (int j=0; j> e : pm.entrySet()) {
- builder.provides(e.getKey(), e.getValue());
+ builder.provides(sn, providers);
}
}
+
+ return builder;
}
/**
- * Reads the ConcealedPackages attribute
+ * Reads the ModulePackages attribute
*/
- private void readConcealedPackagesAttribute(DataInput in, ConstantPool cpool)
+ private Set readModulePackagesAttribute(DataInput in, ConstantPool cpool)
throws IOException
{
int package_count = in.readUnsignedShort();
+ Set packages = new HashSet<>(package_count);
for (int i=0; i map = new HashMap<>(hash_count);
+ Map map = new HashMap<>(hash_count);
for (int i=0; i notAllowed = predefinedNotAllowed;
@@ -477,12 +569,11 @@ final class ModuleInfo {
"LineNumberTable",
"LocalVariableTable",
"LocalVariableTypeTable",
- "RuntimeVisibleAnnotations",
- "RuntimeInvisibleAnnotations",
"RuntimeVisibleParameterAnnotations",
"RuntimeInvisibleParameterAnnotations",
"RuntimeVisibleTypeAnnotations",
"RuntimeInvisibleTypeAnnotations",
+ "Synthetic",
"AnnotationDefault",
"BootstrapMethods",
"MethodParameters");
@@ -495,7 +586,6 @@ final class ModuleInfo {
private static volatile Set predefinedNotAllowed;
-
/**
* The constant pool in a class file.
*/
@@ -628,6 +718,11 @@ final class ModuleInfo {
return getUtf8(((IndexEntry) e).index);
}
+ String getClassNameAsBinaryName(int index) {
+ String value = getClassName(index);
+ return value.replace('/', '.'); // internal form -> binary name
+ }
+
String getUtf8(int index) {
checkIndex(index);
Entry e = pool[index];
@@ -638,6 +733,11 @@ final class ModuleInfo {
return (String) (((ValueEntry) e).value);
}
+ String getUtf8AsBinaryName(int index) {
+ String value = getUtf8(index);
+ return value.replace('/', '.'); // internal -> binary name
+ }
+
void checkIndex(int index) {
if (index < 1 || index >= pool.length)
throw invalidModuleDescriptor("Index into constant pool out of range");
diff --git a/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java b/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java
index fa40f3b7958..15df0307180 100644
--- a/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java
@@ -40,9 +40,10 @@ import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -54,7 +55,6 @@ import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import jdk.internal.jmod.JmodFile;
@@ -399,8 +399,8 @@ class ModulePath implements ModuleFinder {
*
* 1. The module name (and optionally the version) is derived from the file
* name of the JAR file
- * 2. The packages of all .class files in the JAR file are exported
- * 3. It has no module-private/concealed packages
+ * 2. All packages are exported and open
+ * 3. It has no non-exported/non-open packages
* 4. The contents of any META-INF/services configuration files are mapped
* to "provides" declarations
* 5. The Main-Class attribute in the main attributes of the JAR manifest
@@ -440,8 +440,7 @@ class ModulePath implements ModuleFinder {
// Builder throws IAE if module name is empty or invalid
ModuleDescriptor.Builder builder
- = new ModuleDescriptor.Builder(mn)
- .automatic()
+ = ModuleDescriptor.automaticModule(mn)
.requires(Set.of(Requires.Modifier.MANDATED), "java.base");
if (vs != null)
builder.version(vs);
@@ -455,13 +454,12 @@ class ModulePath implements ModuleFinder {
Set resources = map.get(Boolean.FALSE);
Set configFiles = map.get(Boolean.TRUE);
-
- // all packages are exported
+ // all packages are exported and open
resources.stream()
.map(this::toPackageName)
.flatMap(Optional::stream)
.distinct()
- .forEach(builder::exports);
+ .forEach(pn -> builder.exports(pn).opens(pn));
// map names of service configuration files to service names
Set serviceNames = configFiles.stream()
@@ -472,7 +470,7 @@ class ModulePath implements ModuleFinder {
// parse each service configuration file
for (String sn : serviceNames) {
JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn);
- Set providerClasses = new LinkedHashSet<>();
+ List providerClasses = new ArrayList<>();
try (InputStream in = jf.getInputStream(entry)) {
BufferedReader reader
= new BufferedReader(new InputStreamReader(in, "UTF-8"));
@@ -493,7 +491,7 @@ class ModulePath implements ModuleFinder {
Attributes attrs = man.getMainAttributes();
String mainClass = attrs.getValue(Attributes.Name.MAIN_CLASS);
if (mainClass != null)
- builder.mainClass(mainClass);
+ builder.mainClass(mainClass.replace("/", "."));
}
return builder.build();
@@ -504,6 +502,7 @@ class ModulePath implements ModuleFinder {
*/
private static class Patterns {
static final Pattern DASH_VERSION = Pattern.compile("-(\\d+(\\.|$))");
+ static final Pattern TRAILING_VERSION = Pattern.compile("(\\.|\\d)*$");
static final Pattern NON_ALPHANUM = Pattern.compile("[^A-Za-z0-9]");
static final Pattern REPEATING_DOTS = Pattern.compile("(\\.)(\\1)+");
static final Pattern LEADING_DOTS = Pattern.compile("^\\.");
@@ -514,6 +513,9 @@ class ModulePath implements ModuleFinder {
* Clean up candidate module name derived from a JAR file name.
*/
private static String cleanModuleName(String mn) {
+ // drop trailing version from name
+ mn = Patterns.TRAILING_VERSION.matcher(mn).replaceAll("");
+
// replace non-alphanumeric
mn = Patterns.NON_ALPHANUM.matcher(mn).replaceAll(".");
diff --git a/jdk/src/java.base/share/classes/java/lang/module/ModuleReference.java b/jdk/src/java.base/share/classes/java/lang/module/ModuleReference.java
index 34d13639736..cbf84cf938d 100644
--- a/jdk/src/java.base/share/classes/java/lang/module/ModuleReference.java
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleReference.java
@@ -60,8 +60,8 @@ public final class ModuleReference {
// the function that computes the hash of this module reference
private final HashSupplier hasher;
- // cached hash string to avoid needing to compute it many times
- private String cachedHash;
+ // cached hash to avoid needing to compute it many times
+ private byte[] cachedHash;
/**
@@ -183,13 +183,13 @@ public final class ModuleReference {
}
/**
- * Computes the hash of this module, returning it as a hex string.
- * Returns {@code null} if the hash cannot be computed.
+ * Computes the hash of this module. Returns {@code null} if the hash
+ * cannot be computed.
*
* @throws java.io.UncheckedIOException if an I/O error occurs
*/
- String computeHash(String algorithm) {
- String result = cachedHash;
+ byte[] computeHash(String algorithm) {
+ byte[] result = cachedHash;
if (result != null)
return result;
if (hasher == null)
@@ -211,8 +211,11 @@ public final class ModuleReference {
public int hashCode() {
int hc = hash;
if (hc == 0) {
- hc = Objects.hash(descriptor, location, readerSupplier, hasher,
- Boolean.valueOf(patched));
+ hc = descriptor.hashCode();
+ hc = 43 * hc + readerSupplier.hashCode();
+ hc = 43 * hc + Objects.hashCode(location);
+ hc = 43 * hc + Objects.hashCode(hasher);
+ hc = 43 * hc + Boolean.hashCode(patched);
if (hc == 0)
hc = -1;
hash = hc;
diff --git a/jdk/src/java.base/share/classes/java/lang/module/ModuleReferences.java b/jdk/src/java.base/share/classes/java/lang/module/ModuleReferences.java
index 8393bd0f223..e53ba0441d3 100644
--- a/jdk/src/java.base/share/classes/java/lang/module/ModuleReferences.java
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleReferences.java
@@ -51,6 +51,7 @@ import java.util.zip.ZipFile;
import jdk.internal.jmod.JmodFile;
import jdk.internal.misc.JavaLangAccess;
import jdk.internal.misc.SharedSecrets;
+import jdk.internal.module.ModuleBootstrap;
import jdk.internal.module.ModuleHashes;
import jdk.internal.module.ModuleHashes.HashSupplier;
import jdk.internal.module.ModulePatcher;
@@ -80,9 +81,8 @@ class ModuleReferences {
HashSupplier hasher) {
ModuleReference mref = new ModuleReference(md, uri, supplier, hasher);
-
if (JLA.getBootLayer() == null)
- mref = ModulePatcher.interposeIfNeeded(mref);
+ mref = ModuleBootstrap.patcher().patchIfNeeded(mref);
return mref;
}
@@ -93,7 +93,7 @@ class ModuleReferences {
static ModuleReference newJarModule(ModuleDescriptor md, Path file) {
URI uri = file.toUri();
Supplier supplier = () -> new JarModuleReader(file, uri);
- HashSupplier hasher = (a) -> ModuleHashes.computeHashAsString(file, a);
+ HashSupplier hasher = (a) -> ModuleHashes.computeHash(file, a);
return newModule(md, uri, supplier, hasher);
}
@@ -103,7 +103,7 @@ class ModuleReferences {
static ModuleReference newJModModule(ModuleDescriptor md, Path file) {
URI uri = file.toUri();
Supplier supplier = () -> new JModModuleReader(file, uri);
- HashSupplier hasher = (a) -> ModuleHashes.computeHashAsString(file, a);
+ HashSupplier hasher = (a) -> ModuleHashes.computeHash(file, a);
return newModule(md, file.toUri(), supplier, hasher);
}
diff --git a/jdk/src/java.base/share/classes/java/lang/module/Resolver.java b/jdk/src/java.base/share/classes/java/lang/module/Resolver.java
index adc60da8892..5f716b131b7 100644
--- a/jdk/src/java.base/share/classes/java/lang/module/Resolver.java
+++ b/jdk/src/java.base/share/classes/java/lang/module/Resolver.java
@@ -26,9 +26,12 @@
package java.lang.module;
import java.io.PrintStream;
+import java.lang.module.ModuleDescriptor.Provides;
import java.lang.module.ModuleDescriptor.Requires.Modifier;
+import java.lang.reflect.Layer;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
@@ -47,12 +50,15 @@ import jdk.internal.module.ModuleHashes;
/**
* The resolver used by {@link Configuration#resolveRequires} and
* {@link Configuration#resolveRequiresAndUses}.
+ *
+ * @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 Configuration parent;
+ private final List parents;
private final ModuleFinder afterFinder;
private final PrintStream traceOutput;
@@ -61,11 +67,11 @@ final class Resolver {
Resolver(ModuleFinder beforeFinder,
- Configuration parent,
+ List parents,
ModuleFinder afterFinder,
PrintStream traceOutput) {
this.beforeFinder = beforeFinder;
- this.parent = parent;
+ this.parents = parents;
this.afterFinder = afterFinder;
this.traceOutput = traceOutput;
}
@@ -85,10 +91,12 @@ final class Resolver {
// find root module
ModuleReference mref = findWithBeforeFinder(root);
if (mref == null) {
- if (parent.findModule(root).isPresent()) {
+
+ if (findInParent(root) != null) {
// in parent, nothing to do
continue;
}
+
mref = findWithAfterFinder(root);
if (mref == null) {
fail("Module %s not found", root);
@@ -125,13 +133,21 @@ final class Resolver {
// 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 (parent.findModule(dn).isPresent())
+
+ if (findInParent(dn) != null) {
+ // dependence is in parent
continue;
+ }
mref = findWithAfterFinder(dn);
if (mref == null) {
@@ -174,7 +190,9 @@ final class Resolver {
ModuleDescriptor descriptor = mref.descriptor();
if (!descriptor.provides().isEmpty()) {
- for (String sn : descriptor.provides().keySet()) {
+ for (Provides provides : descriptor.provides()) {
+ String sn = provides.service();
+
// computeIfAbsent
Set providers = availableProviders.get(sn);
if (providers == null) {
@@ -191,19 +209,23 @@ final class Resolver {
Deque q = new ArrayDeque<>();
// the initial set of modules that may use services
- Set candidateConsumers = new HashSet<>();
- Configuration p = parent;
- while (p != null) {
- candidateConsumers.addAll(p.descriptors());
- p = p.parent().orElse(null);
+ Set initialConsumers;
+ if (Layer.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()) {
- candidateConsumers.add(mref.descriptor());
+ initialConsumers.add(mref.descriptor());
}
-
// Where there is a consumer of a service then resolve all modules
// that provide an implementation of that service
+ Set candidateConsumers = initialConsumers;
do {
for (ModuleDescriptor descriptor : candidateConsumers) {
if (!descriptor.uses().isEmpty()) {
@@ -234,7 +256,6 @@ final class Resolver {
}
candidateConsumers = resolve(q);
-
} while (!candidateConsumers.isEmpty());
return this;
@@ -429,21 +450,21 @@ final class Resolver {
for (String dn : hashes.names()) {
ModuleReference other = nameToReference.get(dn);
if (other == null) {
- other = parent.findModule(dn)
- .map(ResolvedModule::reference)
- .orElse(null);
+ ResolvedModule resolvedModule = findInParent(dn);
+ if (resolvedModule != null)
+ other = resolvedModule.reference();
}
// skip checking the hash if the module has been patched
if (other != null && !other.isPatched()) {
- String recordedHash = hashes.hashFor(dn);
- String actualHash = other.computeHash(algorithm);
+ byte[] recordedHash = hashes.hashFor(dn);
+ byte[] actualHash = other.computeHash(algorithm);
if (actualHash == null)
fail("Unable to compute the hash of module %s", dn);
- if (!recordedHash.equals(actualHash)) {
+ if (!Arrays.equals(recordedHash, actualHash)) {
fail("Hash of %s (%s) differs to expected hash (%s)" +
- " recorded in %s", dn, actualHash, recordedHash,
- descriptor.name());
+ " recorded in %s", dn, toHexString(actualHash),
+ toHexString(recordedHash), descriptor.name());
}
}
}
@@ -451,52 +472,68 @@ final class Resolver {
}
}
+ 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
- * "public requires" edges of the module dependence graph. So if the module
- * dependence graph has m1 requires m2 && m2 requires public m3 then the
- * resulting readability graph will contain m1 reads m2, m1 reads m3, and
- * m2 reads m3.
+ * "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> 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> g1 = new HashMap<>();
+ Map> g1 = new HashMap<>(capacity);
- // the "requires public" graph, contains requires public edges only
- Map> g2 = new HashMap<>();
+ // the "requires transitive" graph, contains requires transitive edges only
+ Map> g2;
-
- // need "requires public" from the modules in parent configurations as
- // there may be selected modules that have a dependency on modules in
+ // 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.
-
- Configuration p = parent;
- while (p != null) {
- for (ModuleDescriptor descriptor : p.descriptors()) {
- String name = descriptor.name();
- ResolvedModule m1 = p.findModule(name)
- .orElseThrow(() -> new InternalError(name + " not found"));
- for (ModuleDescriptor.Requires requires : descriptor.requires()) {
- if (requires.modifiers().contains(Modifier.PUBLIC)) {
- String dn = requires.name();
- ResolvedModule m2 = p.findModule(dn)
- .orElseThrow(() -> new InternalError(dn + " not found"));
- g2.computeIfAbsent(m1, k -> new HashSet<>()).add(m2);
- }
- }
- }
-
- p = p.parent().orElse(null);
+ if (Layer.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 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 nameToResolved = new HashMap<>();
+ Map nameToResolved = new HashMap<>(capacity);
for (ModuleReference mref : nameToReference.values()) {
ModuleDescriptor descriptor = mref.descriptor();
@@ -505,20 +542,21 @@ final class Resolver {
ResolvedModule m1 = computeIfAbsent(nameToResolved, name, cf, mref);
Set reads = new HashSet<>();
- Set requiresPublic = new HashSet<>();
+ Set requiresTransitive = new HashSet<>();
for (ModuleDescriptor.Requires requires : descriptor.requires()) {
String dn = requires.name();
- ResolvedModule m2;
+ ResolvedModule m2 = null;
ModuleReference mref2 = nameToReference.get(dn);
if (mref2 != null) {
// same configuration
m2 = computeIfAbsent(nameToResolved, dn, cf, mref2);
} else {
// parent configuration
- m2 = parent.findModule(dn).orElse(null);
+ m2 = findInParent(dn);
if (m2 == null) {
+ assert requires.modifiers().contains(Modifier.STATIC);
continue;
}
}
@@ -526,9 +564,9 @@ final class Resolver {
// m1 requires m2 => m1 reads m2
reads.add(m2);
- // m1 requires public m2
- if (requires.modifiers().contains(Modifier.PUBLIC)) {
- requiresPublic.add(m2);
+ // m1 requires transitive m2
+ if (requires.modifiers().contains(Modifier.TRANSITIVE)) {
+ requiresTransitive.add(m2);
}
}
@@ -538,7 +576,7 @@ final class Resolver {
if (descriptor.isAutomatic()) {
// reads all selected modules
- // `requires public` all selected automatic modules
+ // `requires transitive` all selected automatic modules
for (ModuleReference mref2 : nameToReference.values()) {
ModuleDescriptor descriptor2 = mref2.descriptor();
String name2 = descriptor2.name();
@@ -548,40 +586,42 @@ final class Resolver {
= computeIfAbsent(nameToResolved, name2, cf, mref2);
reads.add(m2);
if (descriptor2.isAutomatic())
- requiresPublic.add(m2);
+ requiresTransitive.add(m2);
}
}
// reads all modules in parent configurations
- // `requires public` all automatic modules in parent configurations
- p = parent;
- while (p != null) {
- for (ResolvedModule m : p.modules()) {
- reads.add(m);
- if (m.reference().descriptor().isAutomatic())
- requiresPublic.add(m);
- }
- p = p.parent().orElse(null);
+ // `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, requiresPublic);
+ g2.put(m1, requiresTransitive);
}
- // Iteratively update g1 until there are no more requires public to propagate
+ // Iteratively update g1 until there are no more requires transitive
+ // to propagate
boolean changed;
- Set toAdd = new HashSet<>();
+ List toAdd = new ArrayList<>();
do {
changed = false;
for (Set m1Reads : g1.values()) {
for (ResolvedModule m2 : m1Reads) {
- Set m2RequiresPublic = g2.get(m2);
- if (m2RequiresPublic != null) {
- for (ResolvedModule m3 : m2RequiresPublic) {
+ Set m2RequiresTransitive = g2.get(m2);
+ if (m2RequiresTransitive != null) {
+ for (ResolvedModule m3 : m2RequiresTransitive) {
if (!m1Reads.contains(m3)) {
- // m1 reads m2, m2 requires public m3
+ // m1 reads m2, m2 requires transitive m3
// => need to add m1 reads m3
toAdd.add(m3);
}
@@ -655,7 +695,7 @@ final class Resolver {
// source is exported to descriptor2
String source = export.source();
ModuleDescriptor other
- = packageToExporter.put(source, descriptor2);
+ = packageToExporter.putIfAbsent(source, descriptor2);
if (other != null && other != descriptor2) {
// package might be local to descriptor1
@@ -692,12 +732,8 @@ final class Resolver {
}
// provides S
- for (Map.Entry entry :
- descriptor1.provides().entrySet()) {
- String service = entry.getKey();
- ModuleDescriptor.Provides provides = entry.getValue();
-
- String pn = packageName(service);
+ for (ModuleDescriptor.Provides provides : descriptor1.provides()) {
+ String pn = packageName(provides.service());
if (!packageToExporter.containsKey(pn)) {
fail("Module %s does not read a module that exports %s",
descriptor1.name(), pn);
@@ -717,6 +753,18 @@ final class Resolver {
}
+ /**
+ * Find a module of the given name in the parent configurations
+ */
+ private ResolvedModule findInParent(String mn) {
+ for (Configuration parent : parents) {
+ Optional om = parent.findModule(mn);
+ if (om.isPresent())
+ return om.get();
+ }
+ return null;
+ }
+
/**
* Invokes the beforeFinder to find method to find the given module.
@@ -755,15 +803,18 @@ final class Resolver {
if (afterModules.isEmpty())
return beforeModules;
- if (beforeModules.isEmpty() && parent == Configuration.empty())
+ if (beforeModules.isEmpty()
+ && parents.size() == 1
+ && parents.get(0) == Configuration.empty())
return afterModules;
Set result = new HashSet<>(beforeModules);
for (ModuleReference mref : afterModules) {
String name = mref.descriptor().name();
if (!beforeFinder.find(name).isPresent()
- && !parent.findModule(name).isPresent())
+ && findInParent(name) == null) {
result.add(mref);
+ }
}
return result;
diff --git a/jdk/src/java.base/share/classes/java/lang/module/SystemModuleFinder.java b/jdk/src/java.base/share/classes/java/lang/module/SystemModuleFinder.java
index 9fae75cb382..6de8ce824cf 100644
--- a/jdk/src/java.base/share/classes/java/lang/module/SystemModuleFinder.java
+++ b/jdk/src/java.base/share/classes/java/lang/module/SystemModuleFinder.java
@@ -39,6 +39,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -53,6 +54,7 @@ import jdk.internal.jimage.ImageReader;
import jdk.internal.jimage.ImageReaderFactory;
import jdk.internal.misc.JavaNetUriAccess;
import jdk.internal.misc.SharedSecrets;
+import jdk.internal.module.ModuleBootstrap;
import jdk.internal.module.ModuleHashes;
import jdk.internal.module.ModuleHashes.HashSupplier;
import jdk.internal.module.SystemModules;
@@ -64,7 +66,7 @@ import jdk.internal.perf.PerfCounter;
* run-time image.
*
* The modules linked into the run-time image are assumed to have the
- * ConcealedPackages attribute.
+ * Packages attribute.
*/
class SystemModuleFinder implements ModuleFinder {
@@ -102,8 +104,11 @@ class SystemModuleFinder implements ModuleFinder {
int n = names.length;
moduleCount.add(n);
- Set mods = new HashSet<>(n);
- Map map = new HashMap<>(n);
+ ModuleReference[] mods = new ModuleReference[n];
+
+ @SuppressWarnings(value = {"rawtypes", "unchecked"})
+ Entry[] map
+ = (Entry[])new Entry[n];
for (int i = 0; i < n; i++) {
ModuleDescriptor md = descriptors[i];
@@ -111,16 +116,16 @@ class SystemModuleFinder implements ModuleFinder {
// create the ModuleReference
ModuleReference mref = toModuleReference(md, hashSupplier(i, names[i]));
- mods.add(mref);
- map.put(names[i], mref);
+ mods[i] = mref;
+ map[i] = Map.entry(names[i], mref);
// counters
packageCount.add(md.packages().size());
exportsCount.add(md.exports().size());
}
- modules = Collections.unmodifiableSet(mods);
- nameToModule = map;
+ modules = Set.of(mods);
+ nameToModule = Map.ofEntries(map);
initTime.addElapsedTimeFrom(t0);
}
@@ -190,7 +195,7 @@ class SystemModuleFinder implements ModuleFinder {
new ModuleReference(md, uri, readerSupplier, hash);
// may need a reference to a patched module if --patch-module specified
- mref = ModulePatcher.interposeIfNeeded(mref);
+ mref = ModuleBootstrap.patcher().patchIfNeeded(mref);
return mref;
}
@@ -199,7 +204,7 @@ class SystemModuleFinder implements ModuleFinder {
if (isFastPathSupported()) {
return new HashSupplier() {
@Override
- public String generate(String algorithm) {
+ public byte[] generate(String algorithm) {
return SystemModules.MODULES_TO_HASH[index];
}
};
@@ -213,7 +218,7 @@ class SystemModuleFinder implements ModuleFinder {
* It will get the recorded hashes from module-info.class.
*/
private static class Hashes {
- static Map hashes = new HashMap<>();
+ static Map hashes = new HashMap<>();
static void add(ModuleDescriptor descriptor) {
Optional ohashes = descriptor.hashes();
@@ -228,7 +233,7 @@ class SystemModuleFinder implements ModuleFinder {
return new HashSupplier() {
@Override
- public String generate(String algorithm) {
+ public byte[] generate(String algorithm) {
return hashes.get(name);
}
};
diff --git a/jdk/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java b/jdk/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java
index 353870b37d8..a046a748f7c 100644
--- a/jdk/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java
+++ b/jdk/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java
@@ -25,12 +25,12 @@
package java.lang.reflect;
+import java.lang.annotation.Annotation;
import java.security.AccessController;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import jdk.internal.reflect.ReflectionFactory;
-import java.lang.annotation.Annotation;
/**
* The AccessibleObject class is the base class for Field, Method and
@@ -81,8 +81,10 @@ public class AccessibleObject implements AnnotatedElement {
* This method cannot be used to enable access to an object that is a
* {@link Member member} of a class in a different module to the caller and
* where the class is in a package that is not exported to the caller's
- * module. Additionally, this method cannot be used to enable access to
- * non-public members of {@code AccessibleObject} or {@link Module}.
+ * module. Additionally, if the member is non-public or its declaring
+ * class is non-public, then this method can only be used to enable access
+ * if the package is {@link Module#isOpen(String,Module) open} to at least
+ * the caller's module.
*
*
If there is a security manager, its
* {@code checkPermission} method is first called with a
@@ -126,8 +128,10 @@ public class AccessibleObject implements AnnotatedElement {
*
This method cannot be used to enable access to an object that is a
* {@link Member member} of a class in a different module to the caller and
* where the class is in a package that is not exported to the caller's
- * module. Additionally, this method cannot be used to enable access to
- * non-public members of {@code AccessibleObject} or {@link Module}.
+ * module. Additionally, if the member is non-public or its declaring
+ * class is non-public, then this method can only be used to enable access
+ * if the package is {@link Module#isOpen(String,Module) open} to at least
+ * the caller's module.
*
*
If there is a security manager, its
* {@code checkPermission} method is first called with a
@@ -138,6 +142,7 @@ public class AccessibleObject implements AnnotatedElement {
* @throws SecurityException if the request is denied
* @see SecurityManager#checkPermission
* @see ReflectPermission
+ * @see java.lang.invoke.MethodHandles#privateLookupIn
*/
public void setAccessible(boolean flag) {
AccessibleObject.checkPermission();
@@ -161,35 +166,39 @@ public class AccessibleObject implements AnnotatedElement {
Module callerModule = caller.getModule();
Module declaringModule = declaringClass.getModule();
- if (callerModule != declaringModule
- && callerModule != Object.class.getModule()) {
+ if (callerModule == declaringModule) return;
+ if (callerModule == Object.class.getModule()) return;
+ if (!declaringModule.isNamed()) return;
- // check exports to target module
- String pn = packageName(declaringClass);
- if (!declaringModule.isExported(pn, callerModule)) {
- String msg = "Unable to make member of "
- + declaringClass + " accessible: "
- + declaringModule + " does not export "
- + pn + " to " + callerModule;
- Reflection.throwInaccessibleObjectException(msg);
- }
+ // package is open to caller
+ String pn = packageName(declaringClass);
+ if (declaringModule.isOpen(pn, callerModule))
+ return;
+ // package is exported to caller and class/member is public
+ boolean isExported = declaringModule.isExported(pn, callerModule);
+ boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers());
+ int modifiers;
+ if (this instanceof Executable) {
+ modifiers = ((Executable) this).getModifiers();
+ } else {
+ modifiers = ((Field) this).getModifiers();
}
+ boolean isMemberPublic = Modifier.isPublic(modifiers);
+ if (isExported && isClassPublic && isMemberPublic)
+ return;
- if (declaringClass == Module.class
- || declaringClass == AccessibleObject.class) {
- int modifiers;
- if (this instanceof Executable) {
- modifiers = ((Executable) this).getModifiers();
- } else {
- modifiers = ((Field) this).getModifiers();
- }
- if (!Modifier.isPublic(modifiers)) {
- String msg = "Cannot make a non-public member of "
- + declaringClass + " accessible";
- Reflection.throwInaccessibleObjectException(msg);
- }
- }
+ // not accessible
+ String msg = "Unable to make ";
+ if (this instanceof Field)
+ msg += "field ";
+ msg += this + " accessible: " + declaringModule + " does not \"";
+ if (isClassPublic && isMemberPublic)
+ msg += "exports";
+ else
+ msg += "opens";
+ msg += " " + pn + "\" to " + callerModule;
+ Reflection.throwInaccessibleObjectException(msg);
}
/**
diff --git a/jdk/src/java.base/share/classes/java/lang/reflect/Layer.java b/jdk/src/java.base/share/classes/java/lang/reflect/Layer.java
index 6e220a5e43c..7466c57ae22 100644
--- a/jdk/src/java.base/share/classes/java/lang/reflect/Layer.java
+++ b/jdk/src/java.base/share/classes/java/lang/reflect/Layer.java
@@ -27,23 +27,29 @@ package java.lang.reflect;
import java.lang.module.Configuration;
import java.lang.module.ModuleDescriptor;
-import java.lang.module.ModuleDescriptor.Provides;
import java.lang.module.ResolvedModule;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
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.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.internal.loader.ClassLoaderValue;
import jdk.internal.loader.Loader;
import jdk.internal.loader.LoaderPool;
import jdk.internal.misc.SharedSecrets;
+import jdk.internal.module.Modules;
import jdk.internal.module.ServicesCatalog;
-import jdk.internal.module.ServicesCatalog.ServiceProvider;
import sun.security.util.SecurityConstants;
@@ -55,7 +61,7 @@ import sun.security.util.SecurityConstants;
* Creating a layer informs the Java virtual machine about the classes that
* may be loaded from modules so that the Java virtual machine knows which
* module that each class is a member of. Each layer, except the {@link
- * #empty() empty} layer, has a {@link #parent() parent}.
+ * #empty() empty} layer, has at least one {@link #parents() parent}.
*
* Creating a layer creates a {@link Module} object for each {@link
* ResolvedModule} in the configuration. For each resolved module that is
@@ -71,7 +77,11 @@ import sun.security.util.SecurityConstants;
* mapped to a single class loader or where each module is mapped to its own
* class loader. The {@link #defineModules defineModules} method is for more
* advanced cases where modules are mapped to custom class loaders by means of
- * a function specified to the method.
+ * a function specified to the method. Each of these methods has an instance
+ * and static variant. The instance methods create a layer with the receiver
+ * as the parent layer. The static methods are for more advanced cases where
+ * there can be more than one parent layer or a {@link Layer.Controller
+ * Controller} is needed to control modules in the layer.
*
* A Java virtual machine has at least one non-empty layer, the {@link
* #boot() boot} layer, that is created when the Java virtual machine is
@@ -80,7 +90,7 @@ import sun.security.util.SecurityConstants;
* The modules in the boot layer are mapped to the bootstrap class loader and
* other class loaders that are
* built-in into the Java virtual machine. The boot layer will often be
- * the {@link #parent() parent} when creating additional layers.
+ * the {@link #parents() parent} when creating additional layers.
*
* As when creating a {@code Configuration},
* {@link ModuleDescriptor#isAutomatic() automatic} modules receive
@@ -123,30 +133,29 @@ public final class Layer {
// the empty Layer
private static final Layer EMPTY_LAYER
- = new Layer(Configuration.empty(), null, null);
+ = new Layer(Configuration.empty(), List.of(), null);
// the configuration from which this Layer was created
private final Configuration cf;
- // parent layer, null in the case of the empty layer
- private final Layer parent;
+ // parent layers, empty in the case of the empty layer
+ private final List parents;
// maps module name to jlr.Module
private final Map nameToModule;
-
/**
* Creates a new Layer from the modules in the given configuration.
*/
private Layer(Configuration cf,
- Layer parent,
+ List parents,
Function clf)
{
this.cf = cf;
- this.parent = parent;
+ this.parents = parents; // no need to do defensive copy
Map map;
- if (parent == null) {
+ if (parents.isEmpty()) {
map = Collections.emptyMap();
} else {
map = Module.defineModules(cf, clf, this);
@@ -154,12 +163,230 @@ public final class Layer {
this.nameToModule = map; // no need to do defensive copy
}
+ /**
+ * Controls a layer. The static methods defined by {@link Layer} to create
+ * module layers return a {@code Controller} that can be used to control
+ * modules in the layer.
+ *
+ * @apiNote Care should be taken with {@code Controller} objects, they
+ * should never be shared with untrusted code.
+ *
+ * @since 9
+ */
+ public static final class Controller {
+ private final Layer layer;
+
+ Controller(Layer layer) {
+ this.layer = layer;
+ }
+
+ /**
+ * Returns the layer that this object controls.
+ *
+ * @return the layer
+ */
+ public Layer layer() {
+ return layer;
+ }
+
+ private void ensureInLayer(Module source) {
+ if (!layer.modules().contains(source))
+ throw new IllegalArgumentException(source + " not in layer");
+ }
+
+
+ /**
+ * Updates module {@code source} in the layer to read module
+ * {@code target}. This method is a no-op if {@code source} already
+ * reads {@code target}.
+ *
+ * @implNote Read edges added by this method are weak
+ * and do not prevent {@code target} from being GC'ed when {@code source}
+ * is strongly reachable.
+ *
+ * @param source
+ * The source module
+ * @param target
+ * The target module to read
+ *
+ * @return This controller
+ *
+ * @throws IllegalArgumentException
+ * If {@code source} is not in the layer
+ *
+ * @see Module#addReads
+ */
+ public Controller addReads(Module source, Module target) {
+ Objects.requireNonNull(source);
+ Objects.requireNonNull(target);
+ ensureInLayer(source);
+ Modules.addReads(source, target);
+ return this;
+ }
+
+ /**
+ * Updates module {@code source} in the layer to open a package to
+ * module {@code target}. This method is a no-op if {@code source}
+ * already opens the package to at least {@code target}.
+ *
+ * @param source
+ * The source module
+ * @param pn
+ * The package name
+ * @param target
+ * The target module to read
+ *
+ * @return This controller
+ *
+ * @throws IllegalArgumentException
+ * If {@code source} is not in the layer or the package is not
+ * in the source module
+ *
+ * @see Module#addOpens
+ */
+ public Controller addOpens(Module source, String pn, Module target) {
+ Objects.requireNonNull(source);
+ Objects.requireNonNull(source);
+ Objects.requireNonNull(target);
+ ensureInLayer(source);
+ Modules.addOpens(source, pn, target);
+ return this;
+ }
+ }
+
/**
* Creates a new layer, with this layer as its parent, by defining the
* modules in the given {@code Configuration} to the Java virtual machine.
* This method creates one class loader and defines all modules to that
- * class loader.
+ * class loader. The {@link ClassLoader#getParent() parent} of each class
+ * loader is the given parent class loader. This method works exactly as
+ * specified by the static {@link
+ * #defineModulesWithOneLoader(Configuration,List,ClassLoader)
+ * defineModulesWithOneLoader} method when invoked with this layer as the
+ * parent. In other words, if this layer is {@code thisLayer} then this
+ * method is equivalent to invoking:
+ * {@code
+ * Layer.defineModulesWithOneLoader(cf, List.of(thisLayer), parentLoader).layer();
+ * }
+ *
+ * @param cf
+ * The configuration for the layer
+ * @param parentLoader
+ * The parent class loader for the class loader created by this
+ * method; may be {@code null} for the bootstrap class loader
+ *
+ * @return The newly created layer
+ *
+ * @throws IllegalArgumentException
+ * If the parent of the given configuration is not the configuration
+ * for this layer
+ * @throws LayerInstantiationException
+ * If all modules cannot be defined to the same class loader for any
+ * of the reasons listed above or the layer cannot be created because
+ * the configuration contains a module named "{@code java.base}" or
+ * a module with a package name starting with "{@code java.}"
+ * @throws SecurityException
+ * If {@code RuntimePermission("createClassLoader")} or
+ * {@code RuntimePermission("getClassLoader")} is denied by
+ * the security manager
+ *
+ * @see #findLoader
+ */
+ public Layer defineModulesWithOneLoader(Configuration cf,
+ ClassLoader parentLoader) {
+ return defineModulesWithOneLoader(cf, List.of(this), parentLoader).layer();
+ }
+
+
+ /**
+ * Creates a new layer, with this layer as its parent, by defining the
+ * modules in the given {@code Configuration} to the Java virtual machine.
+ * Each module is defined to its own {@link ClassLoader} created by this
+ * method. The {@link ClassLoader#getParent() parent} of each class loader
+ * is the given parent class loader. This method works exactly as specified
+ * by the static {@link
+ * #defineModulesWithManyLoaders(Configuration,List,ClassLoader)
+ * defineModulesWithManyLoaders} method when invoked with this layer as the
+ * parent. In other words, if this layer is {@code thisLayer} then this
+ * method is equivalent to invoking:
+ * {@code
+ * Layer.defineModulesWithManyLoaders(cf, List.of(thisLayer), parentLoader).layer();
+ * }
+ *
+ * @param cf
+ * The configuration for the layer
+ * @param parentLoader
+ * The parent class loader for each of the class loaders created by
+ * this method; may be {@code null} for the bootstrap class loader
+ *
+ * @return The newly created layer
+ *
+ * @throws IllegalArgumentException
+ * If the parent of the given configuration is not the configuration
+ * for this layer
+ * @throws LayerInstantiationException
+ * If the layer cannot be created because the configuration contains
+ * a module named "{@code java.base}" or a module with a package
+ * name starting with "{@code java.}"
+ * @throws SecurityException
+ * If {@code RuntimePermission("createClassLoader")} or
+ * {@code RuntimePermission("getClassLoader")} is denied by
+ * the security manager
+ *
+ * @see #findLoader
+ */
+ public Layer defineModulesWithManyLoaders(Configuration cf,
+ ClassLoader parentLoader) {
+ return defineModulesWithManyLoaders(cf, List.of(this), parentLoader).layer();
+ }
+
+
+ /**
+ * Creates a new layer, with this layer as its parent, by defining the
+ * modules in the given {@code Configuration} to the Java virtual machine.
+ * Each module is mapped, by name, to its class loader by means of the
+ * given function. This method works exactly as specified by the static
+ * {@link #defineModules(Configuration,List,Function) defineModules}
+ * method when invoked with this layer as the parent. In other words, if
+ * this layer is {@code thisLayer} then this method is equivalent to
+ * invoking:
+ * {@code
+ * Layer.defineModules(cf, List.of(thisLayer), clf).layer();
+ * }
+ *
+ * @param cf
+ * The configuration for the layer
+ * @param clf
+ * The function to map a module name to a class loader
+ *
+ * @return The newly created layer
+ *
+ * @throws IllegalArgumentException
+ * If the parent of the given configuration is not the configuration
+ * for this layer
+ * @throws LayerInstantiationException
+ * If creating the {@code Layer} fails for any of the reasons
+ * listed above, the layer cannot be created because the
+ * configuration contains a module named "{@code java.base}",
+ * a module with a package name starting with "{@code java.}" is
+ * mapped to a class loader other than the {@link
+ * ClassLoader#getPlatformClassLoader() platform class loader},
+ * or the function to map a module name to a class loader returns
+ * {@code null}
+ * @throws SecurityException
+ * If {@code RuntimePermission("getClassLoader")} is denied by
+ * the security manager
+ */
+ public Layer defineModules(Configuration cf,
+ Function clf) {
+ return defineModules(cf, List.of(this), clf).layer();
+ }
+
+ /**
+ * Creates a new layer by defining the modules in the given {@code
+ * Configuration} to the Java virtual machine. This method creates one
+ * class loader and defines all modules to that class loader.
*
* The class loader created by this method implements direct
* delegation when loading types from modules. When its {@link
@@ -180,7 +407,7 @@ public final class Layer {
*
*
* Overlapping packages: Two or more modules in the
- * configuration have the same package (exported or concealed).
+ * configuration have the same package.
*
* Split delegation: The resulting class loader would
* need to delegate to more than one class loader in order to load types
@@ -194,19 +421,22 @@ public final class Layer {
*
* @param cf
* The configuration for the layer
+ * @param parentLayers
+ * The list parent layers in search order
* @param parentLoader
* The parent class loader for the class loader created by this
* method; may be {@code null} for the bootstrap class loader
*
- * @return The newly created layer
+ * @return A controller that controls the newly created layer
*
* @throws IllegalArgumentException
- * If the parent of the given configuration is not the configuration
- * for this layer
+ * If the parent configurations do not match the configuration of
+ * the parent layers, including order
* @throws LayerInstantiationException
* If all modules cannot be defined to the same class loader for any
* of the reasons listed above or the layer cannot be created because
- * the configuration contains a module named "{@code java.base}"
+ * the configuration contains a module named "{@code java.base}" or
+ * a module with a package name starting with "{@code java.}"
* @throws SecurityException
* If {@code RuntimePermission("createClassLoader")} or
* {@code RuntimePermission("getClassLoader")} is denied by
@@ -214,29 +444,32 @@ public final class Layer {
*
* @see #findLoader
*/
- public Layer defineModulesWithOneLoader(Configuration cf,
- ClassLoader parentLoader)
+ public static Controller defineModulesWithOneLoader(Configuration cf,
+ List parentLayers,
+ ClassLoader parentLoader)
{
- checkConfiguration(cf);
+ List parents = new ArrayList<>(parentLayers);
+ checkConfiguration(cf, parents);
+
checkCreateClassLoaderPermission();
checkGetClassLoaderPermission();
try {
Loader loader = new Loader(cf.modules(), parentLoader);
- loader.initRemotePackageMap(cf, this);
- return new Layer(cf, this, mn -> loader);
+ loader.initRemotePackageMap(cf, parents);
+ Layer layer = new Layer(cf, parents, mn -> loader);
+ return new Controller(layer);
} catch (IllegalArgumentException e) {
throw new LayerInstantiationException(e.getMessage());
}
}
-
/**
- * Creates a new layer, with this layer as its parent, by defining the
- * modules in the given {@code Configuration} to the Java virtual machine.
- * Each module is defined to its own {@link ClassLoader} created by this
- * method. The {@link ClassLoader#getParent() parent} of each class loader
- * is the given parent class loader.
+ * Creates a new layer by defining the modules in the given {@code
+ * Configuration} to the Java virtual machine. Each module is defined to
+ * its own {@link ClassLoader} created by this method. The {@link
+ * ClassLoader#getParent() parent} of each class loader is the given parent
+ * class loader.
*
* The class loaders created by this method implement direct
* delegation when loading types from modules. When {@link
@@ -258,18 +491,21 @@ public final class Layer {
*
* @param cf
* The configuration for the layer
+ * @param parentLayers
+ * The list parent layers in search order
* @param parentLoader
* The parent class loader for each of the class loaders created by
* this method; may be {@code null} for the bootstrap class loader
*
- * @return The newly created layer
+ * @return A controller that controls the newly created layer
*
* @throws IllegalArgumentException
- * If the parent of the given configuration is not the configuration
- * for this layer
+ * If the parent configurations do not match the configuration of
+ * the parent layers, including order
* @throws LayerInstantiationException
* If the layer cannot be created because the configuration contains
- * a module named "{@code java.base}"
+ * a module named "{@code java.base}" or a module with a package
+ * name starting with "{@code java.}"
* @throws SecurityException
* If {@code RuntimePermission("createClassLoader")} or
* {@code RuntimePermission("getClassLoader")} is denied by
@@ -277,37 +513,43 @@ public final class Layer {
*
* @see #findLoader
*/
- public Layer defineModulesWithManyLoaders(Configuration cf,
- ClassLoader parentLoader)
+ public static Controller defineModulesWithManyLoaders(Configuration cf,
+ List parentLayers,
+ ClassLoader parentLoader)
{
- checkConfiguration(cf);
+ List parents = new ArrayList<>(parentLayers);
+ checkConfiguration(cf, parents);
+
checkCreateClassLoaderPermission();
checkGetClassLoaderPermission();
- LoaderPool pool = new LoaderPool(cf, this, parentLoader);
+ LoaderPool pool = new LoaderPool(cf, parents, parentLoader);
try {
- return new Layer(cf, this, pool::loaderFor);
+ Layer layer = new Layer(cf, parents, pool::loaderFor);
+ return new Controller(layer);
} catch (IllegalArgumentException e) {
throw new LayerInstantiationException(e.getMessage());
}
}
-
/**
- * Creates a new layer, with this layer as its parent, by defining the
- * modules in the given {@code Configuration} to the Java virtual machine.
+ * Creates a new layer by defining the modules in the given {@code
+ * Configuration} to the Java virtual machine.
* Each module is mapped, by name, to its class loader by means of the
* given function. The class loader delegation implemented by these class
- * loaders must respect module readability. In addition, the caller needs
- * to arrange that the class loaders are ready to load from these module
- * before there are any attempts to load classes or resources.
+ * loaders must respect module readability. The class loaders should be
+ * {@link ClassLoader#registerAsParallelCapable parallel-capable} so as to
+ * avoid deadlocks during class loading. In addition, the entity creating
+ * a new layer with this method should arrange that the class loaders are
+ * ready to load from these module before there are any attempts to load
+ * classes or resources.
*
* Creating a {@code Layer} can fail for the following reasons:
*
*
*
- * Two or more modules with the same package (exported or
- * concealed) are mapped to the same class loader.
+ * Two or more modules with the same package are mapped to the
+ * same class loader.
*
* A module is mapped to a class loader that already has a
* module of the same name defined to it.
@@ -328,26 +570,35 @@ public final class Layer {
*
* @param cf
* The configuration for the layer
+ * @param parentLayers
+ * The list parent layers in search order
* @param clf
* The function to map a module name to a class loader
*
- * @return The newly created layer
+ * @return A controller that controls the newly created layer
*
* @throws IllegalArgumentException
- * If the parent of the given configuration is not the configuration
- * for this layer
+ * If the parent configurations do not match the configuration of
+ * the parent layers, including order
* @throws LayerInstantiationException
* If creating the {@code Layer} fails for any of the reasons
- * listed above or the layer cannot be created because the
- * configuration contains a module named "{@code java.base}"
+ * listed above, the layer cannot be created because the
+ * configuration contains a module named "{@code java.base}",
+ * a module with a package name starting with "{@code java.}" is
+ * mapped to a class loader other than the {@link
+ * ClassLoader#getPlatformClassLoader() platform class loader},
+ * or the function to map a module name to a class loader returns
+ * {@code null}
* @throws SecurityException
* If {@code RuntimePermission("getClassLoader")} is denied by
* the security manager
*/
- public Layer defineModules(Configuration cf,
- Function clf)
+ public static Controller defineModules(Configuration cf,
+ List parentLayers,
+ Function clf)
{
- checkConfiguration(cf);
+ List parents = new ArrayList<>(parentLayers);
+ checkConfiguration(cf, parents);
Objects.requireNonNull(clf);
checkGetClassLoaderPermission();
@@ -362,7 +613,8 @@ public final class Layer {
}
try {
- return new Layer(cf, this, clf);
+ Layer layer = new Layer(cf, parents, clf);
+ return new Controller(layer);
} catch (IllegalArgumentException iae) {
// IAE is thrown by VM when defining the module fails
throw new LayerInstantiationException(iae.getMessage());
@@ -370,13 +622,26 @@ public final class Layer {
}
- private void checkConfiguration(Configuration cf) {
+ /**
+ * Checks that the parent configurations match the configuration of
+ * the parent layers.
+ */
+ private static void checkConfiguration(Configuration cf,
+ List parentLayers)
+ {
Objects.requireNonNull(cf);
- Optional oparent = cf.parent();
- if (!oparent.isPresent() || oparent.get() != this.configuration()) {
- throw new IllegalArgumentException(
- "Parent of configuration != configuration of this Layer");
+ List parentConfigurations = cf.parents();
+ if (parentLayers.size() != parentConfigurations.size())
+ throw new IllegalArgumentException("wrong number of parents");
+
+ int index = 0;
+ for (Layer parent : parentLayers) {
+ if (parent.configuration() != parentConfigurations.get(index)) {
+ throw new IllegalArgumentException(
+ "Parent of configuration != configuration of this Layer");
+ }
+ index++;
}
}
@@ -463,18 +728,57 @@ public final class Layer {
/**
- * Returns this layer's parent unless this is the {@linkplain #empty empty
- * layer}, which has no parent.
+ * Returns the list of this layer's parents unless this is the
+ * {@linkplain #empty empty layer}, which has no parents and so an
+ * empty list is returned.
*
- * @return This layer's parent
+ * @return The list of this layer's parents
*/
- public Optional parent() {
- return Optional.ofNullable(parent);
+ public List parents() {
+ return parents;
}
/**
- * Returns a set of the modules in this layer.
+ * Returns an ordered stream of layers. The first element is is this layer,
+ * the remaining elements are the parent layers 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 layers() {
+ List allLayers = this.allLayers;
+ if (allLayers != null)
+ return allLayers.stream();
+
+ allLayers = new ArrayList<>();
+ Set visited = new HashSet<>();
+ Deque stack = new ArrayDeque<>();
+ visited.add(this);
+ stack.push(this);
+
+ while (!stack.isEmpty()) {
+ Layer layer = stack.pop();
+ allLayers.add(layer);
+
+ // push in reverse order
+ for (int i = layer.parents.size() - 1; i >= 0; i--) {
+ Layer parent = layer.parents.get(i);
+ if (!visited.contains(parent)) {
+ visited.add(parent);
+ stack.push(parent);
+ }
+ }
+ }
+
+ this.allLayers = allLayers = Collections.unmodifiableList(allLayers);
+ return allLayers.stream();
+ }
+
+ private volatile List allLayers;
+
+ /**
+ * Returns the set of the modules in this layer.
*
* @return A possibly-empty unmodifiable set of the modules in this layer
*/
@@ -486,7 +790,11 @@ public final class Layer {
/**
* Returns the module with the given name in this layer, or if not in this
- * layer, the {@linkplain #parent parent} layer.
+ * layer, the {@linkplain #parents parents} layers. Finding a module in
+ * parent layers 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 tree of layers then this is equivalent to
+ * a depth-first search.
*
* @param name
* The name of the module to find
@@ -496,17 +804,25 @@ public final class Layer {
* parent layer
*/
public Optional findModule(String name) {
- Module m = nameToModule.get(Objects.requireNonNull(name));
+ Objects.requireNonNull(name);
+ Module m = nameToModule.get(name);
if (m != null)
return Optional.of(m);
- return parent().flatMap(l -> l.findModule(name));
+
+ return layers()
+ .skip(1) // skip this layer
+ .map(l -> l.nameToModule)
+ .filter(map -> map.containsKey(name))
+ .map(map -> map.get(name))
+ .findAny();
}
/**
* Returns the {@code ClassLoader} for the module with the given name. If
- * a module of the given name is not in this layer then the {@link #parent}
- * layer is checked.
+ * a module of the given name is not in this layer then the {@link #parents
+ * parent} layers are searched in the manner specified by {@link
+ * #findModule(String) findModule}.
*
* If there is a security manager then its {@code checkPermission}
* method is called with a {@code RuntimePermission("getClassLoader")}
@@ -527,20 +843,32 @@ public final class Layer {
* @throws SecurityException if denied by the security manager
*/
public ClassLoader findLoader(String name) {
- Module m = nameToModule.get(Objects.requireNonNull(name));
- if (m != null)
- return m.getClassLoader();
- Optional ol = parent();
- if (ol.isPresent())
- return ol.get().findLoader(name);
- throw new IllegalArgumentException("Module " + name
- + " not known to this layer");
+ Optional om = findModule(name);
+
+ // can't use map(Module::getClassLoader) as class loader can be null
+ if (om.isPresent()) {
+ return om.get().getClassLoader();
+ } else {
+ throw new IllegalArgumentException("Module " + name
+ + " not known to this layer");
+ }
}
+ /**
+ * Returns a string describing this layer.
+ *
+ * @return A possibly empty string describing this layer
+ */
+ @Override
+ public String toString() {
+ return modules().stream()
+ .map(Module::getName)
+ .collect(Collectors.joining(", "));
+ }
/**
* Returns the empty layer. There are no modules in the empty
- * layer. It has no parent.
+ * layer. It has no parents.
*
* @return The empty layer
*/
@@ -572,39 +900,12 @@ public final class Layer {
if (servicesCatalog != null)
return servicesCatalog;
- Map> map = new HashMap<>();
- for (Module m : nameToModule.values()) {
- ModuleDescriptor descriptor = m.getDescriptor();
- for (Provides provides : descriptor.provides().values()) {
- String service = provides.service();
- Set providers
- = map.computeIfAbsent(service, k -> new HashSet<>());
- for (String pn : provides.providers()) {
- providers.add(new ServiceProvider(m, pn));
- }
- }
- }
-
- ServicesCatalog catalog = new ServicesCatalog() {
- @Override
- public void register(Module module) {
- throw new UnsupportedOperationException();
- }
- @Override
- public Set findServices(String service) {
- Set providers = map.get(service);
- if (providers == null) {
- return Collections.emptySet();
- } else {
- return Collections.unmodifiableSet(providers);
- }
- }
- };
-
synchronized (this) {
servicesCatalog = this.servicesCatalog;
if (servicesCatalog == null) {
- this.servicesCatalog = servicesCatalog = catalog;
+ servicesCatalog = ServicesCatalog.create();
+ nameToModule.values().forEach(servicesCatalog::register);
+ this.servicesCatalog = servicesCatalog;
}
}
@@ -612,4 +913,36 @@ public final class Layer {
}
private volatile ServicesCatalog servicesCatalog;
+
+
+ /**
+ * Record that this layer has at least one module defined to the given
+ * class loader.
+ */
+ void bindToLoader(ClassLoader loader) {
+ // CLV.computeIfAbsent(loader, (cl, clv) -> new CopyOnWriteArrayList<>())
+ List list = CLV.get(loader);
+ if (list == null) {
+ list = new CopyOnWriteArrayList<>();
+ List previous = CLV.putIfAbsent(loader, list);
+ if (previous != null) list = previous;
+ }
+ list.add(this);
+ }
+
+ /**
+ * Returns a stream of the layers that have at least one module defined to
+ * the given class loader.
+ */
+ static Stream layers(ClassLoader loader) {
+ List list = CLV.get(loader);
+ if (list != null) {
+ return list.stream();
+ } else {
+ return Stream.empty();
+ }
+ }
+
+ // the list of layers with modules defined to a class loader
+ private static final ClassLoaderValue> CLV = new ClassLoaderValue<>();
}
diff --git a/jdk/src/java.base/share/classes/java/lang/reflect/Module.java b/jdk/src/java.base/share/classes/java/lang/reflect/Module.java
index 26e47245f38..29f21cc0108 100644
--- a/jdk/src/java.base/share/classes/java/lang/reflect/Module.java
+++ b/jdk/src/java.base/share/classes/java/lang/reflect/Module.java
@@ -27,15 +27,18 @@ package java.lang.reflect;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.annotation.Annotation;
import java.lang.module.Configuration;
import java.lang.module.ModuleReference;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Exports;
-import java.lang.module.ModuleDescriptor.Provides;
+import java.lang.module.ModuleDescriptor.Opens;
import java.lang.module.ModuleDescriptor.Version;
import java.lang.module.ResolvedModule;
import java.net.URI;
import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -49,9 +52,17 @@ import java.util.stream.Stream;
import jdk.internal.loader.BuiltinClassLoader;
import jdk.internal.loader.BootLoader;
+import jdk.internal.loader.ResourceHelper;
+import jdk.internal.misc.JavaLangAccess;
import jdk.internal.misc.JavaLangReflectModuleAccess;
import jdk.internal.misc.SharedSecrets;
import jdk.internal.module.ServicesCatalog;
+import jdk.internal.org.objectweb.asm.AnnotationVisitor;
+import jdk.internal.org.objectweb.asm.Attribute;
+import jdk.internal.org.objectweb.asm.ClassReader;
+import jdk.internal.org.objectweb.asm.ClassVisitor;
+import jdk.internal.org.objectweb.asm.ClassWriter;
+import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import sun.security.util.SecurityConstants;
@@ -83,7 +94,7 @@ import sun.security.util.SecurityConstants;
* @see java.lang.Class#getModule
*/
-public final class Module {
+public final class Module implements AnnotatedElement {
// the layer that contains this module, can be null
private final Layer layer;
@@ -113,6 +124,10 @@ public final class Module {
// define module to VM
+ boolean isOpen = descriptor.isOpen();
+ Version version = descriptor.version().orElse(null);
+ String vs = Objects.toString(version, null);
+ String loc = Objects.toString(uri, null);
Set packages = descriptor.packages();
int n = packages.size();
String[] array = new String[n];
@@ -120,11 +135,7 @@ public final class Module {
for (String pn : packages) {
array[i++] = pn.replace('.', '/');
}
- Version version = descriptor.version().orElse(null);
- String vs = Objects.toString(version, null);
- String loc = Objects.toString(uri, null);
-
- defineModule0(this, vs, loc, array);
+ defineModule0(this, isOpen, vs, loc, array);
}
@@ -240,24 +251,24 @@ public final class Module {
// --
- // the special Module to mean reads or exported to "all unnamed modules"
+ // special Module to mean "all unnamed modules"
private static final Module ALL_UNNAMED_MODULE = new Module(null);
- // special Module to mean exported to "everyone"
+ // special Module to mean "everyone"
private static final Module EVERYONE_MODULE = new Module(null);
- // exported to all modules
- private static final Set EVERYONE = Collections.singleton(EVERYONE_MODULE);
+ // set contains EVERYONE_MODULE, used when a package is opened or
+ // exported unconditionally
+ private static final Set EVERYONE_SET = Set.of(EVERYONE_MODULE);
// -- readability --
- // the modules that this module permanently reads
- // (will be final when the modules are defined in reverse topology order)
+ // the modules that this module reads
private volatile Set reads;
// additional module (2nd key) that some module (1st key) reflectively reads
- private static final WeakPairMap transientReads
+ private static final WeakPairMap reflectivelyReads
= new WeakPairMap<>();
@@ -293,13 +304,13 @@ public final class Module {
}
// check if this module reads the other module reflectively
- if (transientReads.containsKeyPair(this, other))
+ if (reflectivelyReads.containsKeyPair(this, other))
return true;
// if other is an unnamed module then check if this module reads
// all unnamed modules
if (!other.isNamed()
- && transientReads.containsKeyPair(this, ALL_UNNAMED_MODULE))
+ && reflectivelyReads.containsKeyPair(this, ALL_UNNAMED_MODULE))
return true;
return false;
@@ -309,9 +320,13 @@ public final class Module {
* If the caller's module is this module then update this module to read
* the given module.
*
- * This method is a no-op if {@code other} is this module (all modules can
- * read themselves) or this module is an unnamed module (as unnamed modules
- * read all modules).
+ * This method is a no-op if {@code other} is this module (all modules read
+ * themselves), this module is an unnamed module (as unnamed modules read
+ * all modules), or this module already reads {@code other}.
+ *
+ * @implNote Read edges added by this method are weak and
+ * do not prevent {@code other} from being GC'ed when this module is
+ * strongly reachable.
*
* @param other
* The other module
@@ -381,30 +396,39 @@ public final class Module {
}
// add reflective read
- transientReads.putIfAbsent(this, other, Boolean.TRUE);
+ reflectivelyReads.putIfAbsent(this, other, Boolean.TRUE);
}
- // -- exports --
+ // -- exported and open packages --
- // the packages that are permanently exported
- // (will be final when the modules are defined in reverse topology order)
- private volatile Map> exports;
+ // the packages are open to other modules, can be null
+ // if the value contains EVERYONE_MODULE then the package is open to all
+ private volatile Map> openPackages;
- // additional exports added at run-time
- // this module (1st key), other module (2nd key), exported packages (value)
+ // the packages that are exported, can be null
+ // if the value contains EVERYONE_MODULE then the package is exported to all
+ private volatile Map> exportedPackages;
+
+ // additional exports or opens added at run-time
+ // this module (1st key), other module (2nd key)
+ // (package name, open?) (value)
private static final WeakPairMap>
- transientExports = new WeakPairMap<>();
+ reflectivelyExports = new WeakPairMap<>();
/**
* Returns {@code true} if this module exports the given package to at
* least the given module.
*
- * This method always return {@code true} when invoked on an unnamed
+ *
This method returns {@code true} if invoked to test if a package in
+ * this module is exported to itself. It always returns {@code true} when
+ * invoked on an unnamed module. A package that is {@link #isOpen open} to
+ * the given module is considered exported to that module at run-time and
+ * so this method returns {@code true} if the package is open to the given
* module.
*
- * This method does not check if the given module reads this module
+ * This method does not check if the given module reads this module.
*
* @param pn
* The package name
@@ -413,93 +437,196 @@ public final class Module {
*
* @return {@code true} if this module exports the package to at least the
* given module
+ *
+ * @see ModuleDescriptor#exports()
+ * @see #addExports(String,Module)
*/
public boolean isExported(String pn, Module other) {
Objects.requireNonNull(pn);
Objects.requireNonNull(other);
- return implIsExported(pn, other);
+ return implIsExportedOrOpen(pn, other, /*open*/false);
+ }
+
+ /**
+ * Returns {@code true} if this module has opened a package to at
+ * least the given module.
+ *
+ * This method returns {@code true} if invoked to test if a package in
+ * this module is open to itself. It returns {@code true} when invoked on an
+ * {@link ModuleDescriptor#isOpen open} module with a package in the module.
+ * It always returns {@code true} when invoked on an unnamed module.
+ *
+ * This method does not check if the given module reads this module.
+ *
+ * @param pn
+ * The package name
+ * @param other
+ * The other module
+ *
+ * @return {@code true} if this module has opened the package
+ * to at least the given module
+ *
+ * @see ModuleDescriptor#opens()
+ * @see #addOpens(String,Module)
+ * @see AccessibleObject#setAccessible(boolean)
+ * @see java.lang.invoke.MethodHandles#privateLookupIn
+ */
+ public boolean isOpen(String pn, Module other) {
+ Objects.requireNonNull(pn);
+ Objects.requireNonNull(other);
+ return implIsExportedOrOpen(pn, other, /*open*/true);
}
/**
* Returns {@code true} if this module exports the given package
* unconditionally.
*
- * This method always return {@code true} when invoked on an unnamed
- * module.
+ * This method always returns {@code true} when invoked on an unnamed
+ * module. A package that is {@link #isOpen(String) opened} unconditionally
+ * is considered exported unconditionally at run-time and so this method
+ * returns {@code true} if the package is opened unconditionally.
*
- * This method does not check if the given module reads this module
+ * This method does not check if the given module reads this module.
*
* @param pn
* The package name
*
* @return {@code true} if this module exports the package unconditionally
+ *
+ * @see ModuleDescriptor#exports()
*/
public boolean isExported(String pn) {
Objects.requireNonNull(pn);
- return implIsExported(pn, EVERYONE_MODULE);
+ return implIsExportedOrOpen(pn, EVERYONE_MODULE, /*open*/false);
}
/**
- * Returns {@code true} if this module exports the given package to the
- * given module. If the other module is {@code EVERYONE_MODULE} then
- * this method tests if the package is exported unconditionally.
+ * Returns {@code true} if this module has opened a package
+ * unconditionally.
+ *
+ * This method always returns {@code true} when invoked on an unnamed
+ * module. Additionally, it always returns {@code true} when invoked on an
+ * {@link ModuleDescriptor#isOpen open} module with a package in the
+ * module.
+ *
+ * This method does not check if the given module reads this module.
+ *
+ * @param pn
+ * The package name
+ *
+ * @return {@code true} if this module has opened the package
+ * unconditionally
+ *
+ * @see ModuleDescriptor#opens()
*/
- private boolean implIsExported(String pn, Module other) {
+ public boolean isOpen(String pn) {
+ Objects.requireNonNull(pn);
+ return implIsExportedOrOpen(pn, EVERYONE_MODULE, /*open*/true);
+ }
- // all packages are exported by unnamed modules
+
+ /**
+ * Returns {@code true} if this module exports or opens the given package
+ * to the given module. If the other module is {@code EVERYONE_MODULE} then
+ * this method tests if the package is exported or opened unconditionally.
+ */
+ private boolean implIsExportedOrOpen(String pn, Module other, boolean open) {
+ // all packages in unnamed modules are open
if (!isNamed())
return true;
- // exported via module declaration/descriptor
- if (isExportedPermanently(pn, other))
+ // all packages are exported/open to self
+ if (other == this && containsPackage(pn))
return true;
- // exported via addExports
- if (isExportedReflectively(pn, other))
+ // all packages in open modules are open
+ if (descriptor.isOpen())
+ return containsPackage(pn);
+
+ // exported/opened via module declaration/descriptor
+ if (isStaticallyExportedOrOpen(pn, other, open))
return true;
- // not exported or not exported to other
+ // exported via addExports/addOpens
+ if (isReflectivelyExportedOrOpen(pn, other, open))
+ return true;
+
+ // not exported or open to other
return false;
}
/**
- * Returns {@code true} if this module permanently exports the given
- * package to the given module.
+ * Returns {@code true} if this module exports or opens a package to
+ * the given module via its module declaration.
*/
- private boolean isExportedPermanently(String pn, Module other) {
- Map> exports = this.exports;
- if (exports != null) {
- Set targets = exports.get(pn);
-
- if ((targets != null)
- && (targets.contains(other) || targets.contains(EVERYONE_MODULE)))
- return true;
+ private boolean isStaticallyExportedOrOpen(String pn, Module other, boolean open) {
+ // package is open to everyone or
+ Map> openPackages = this.openPackages;
+ if (openPackages != null) {
+ Set targets = openPackages.get(pn);
+ if (targets != null) {
+ if (targets.contains(EVERYONE_MODULE))
+ return true;
+ if (other != EVERYONE_MODULE && targets.contains(other))
+ return true;
+ }
}
+
+ if (!open) {
+ // package is exported to everyone or
+ Map> exportedPackages = this.exportedPackages;
+ if (exportedPackages != null) {
+ Set targets = exportedPackages.get(pn);
+ if (targets != null) {
+ if (targets.contains(EVERYONE_MODULE))
+ return true;
+ if (other != EVERYONE_MODULE && targets.contains(other))
+ return true;
+ }
+ }
+ }
+
return false;
}
+
/**
- * Returns {@code true} if this module reflectively exports the given
+ * Returns {@code true} if this module reflectively exports or opens given
* package package to the given module.
*/
- private boolean isExportedReflectively(String pn, Module other) {
- // exported to all modules
- Map exports = transientExports.get(this, EVERYONE_MODULE);
- if (exports != null && exports.containsKey(pn))
- return true;
+ private boolean isReflectivelyExportedOrOpen(String pn, Module other, boolean open) {
+ // exported or open to all modules
+ Map exports = reflectivelyExports.get(this, EVERYONE_MODULE);
+ if (exports != null) {
+ Boolean b = exports.get(pn);
+ if (b != null) {
+ boolean isOpen = b.booleanValue();
+ if (!open || isOpen) return true;
+ }
+ }
if (other != EVERYONE_MODULE) {
- // exported to other
- exports = transientExports.get(this, other);
- if (exports != null && exports.containsKey(pn))
- return true;
+ // exported or open to other
+ exports = reflectivelyExports.get(this, other);
+ if (exports != null) {
+ Boolean b = exports.get(pn);
+ if (b != null) {
+ boolean isOpen = b.booleanValue();
+ if (!open || isOpen) return true;
+ }
+ }
- // other is an unnamed module && exported to all unnamed
+ // other is an unnamed module && exported or open to all unnamed
if (!other.isNamed()) {
- exports = transientExports.get(this, ALL_UNNAMED_MODULE);
- if (exports != null && exports.containsKey(pn))
- return true;
+ exports = reflectivelyExports.get(this, ALL_UNNAMED_MODULE);
+ if (exports != null) {
+ Boolean b = exports.get(pn);
+ if (b != null) {
+ boolean isOpen = b.booleanValue();
+ if (!open || isOpen) return true;
+ }
+ }
}
}
@@ -510,11 +637,11 @@ public final class Module {
/**
* If the caller's module is this module then update this module to export
- * package {@code pn} to the given module.
+ * the given package to the given module.
*
- * This method has no effect if the package is already exported to the
- * given module. It also has no effect if invoked on an unnamed module (as
- * unnamed modules export all packages).
+ * This method has no effect if the package is already exported (or
+ * open) to the given module. It also has no effect if
+ * invoked on an {@link ModuleDescriptor#isOpen open} module.
*
* @param pn
* The package name
@@ -528,6 +655,8 @@ public final class Module {
* package {@code pn} is not a package in this module
* @throws IllegalStateException
* If this is a named module and the caller is not this module
+ *
+ * @see #isExported(String,Module)
*/
@CallerSensitive
public Module addExports(String pn, Module other) {
@@ -535,17 +664,65 @@ public final class Module {
throw new IllegalArgumentException("package is null");
Objects.requireNonNull(other);
- if (isNamed()) {
+ if (isNamed() && !descriptor.isOpen()) {
Module caller = Reflection.getCallerClass().getModule();
if (caller != this) {
throw new IllegalStateException(caller + " != " + this);
}
- implAddExports(pn, other, true);
+ implAddExportsOrOpens(pn, other, /*open*/false, /*syncVM*/true);
}
return this;
}
+ /**
+ * If the caller's module is this module then update this module to
+ * open the given package to the given module.
+ * Opening a package with this method allows all types in the package,
+ * and all their members, not just public types and their public members,
+ * to be reflected on by the given module when using APIs that support
+ * private access or a way to bypass or suppress default Java language
+ * access control checks.
+ *
+ * This method has no effect if the package is already open
+ * to the given module. It also has no effect if invoked on an {@link
+ * ModuleDescriptor#isOpen open} module.
+ *
+ * @param pn
+ * The package name
+ * @param other
+ * The module
+ *
+ * @return this module
+ *
+ * @throws IllegalArgumentException
+ * If {@code pn} is {@code null}, or this is a named module and the
+ * package {@code pn} is not a package in this module
+ * @throws IllegalStateException
+ * If this is a named module and the caller is not this module
+ *
+ * @see #isOpen(String,Module)
+ * @see AccessibleObject#setAccessible(boolean)
+ * @see java.lang.invoke.MethodHandles#privateLookupIn
+ */
+ @CallerSensitive
+ public Module addOpens(String pn, Module other) {
+ if (pn == null)
+ throw new IllegalArgumentException("package is null");
+ Objects.requireNonNull(other);
+
+ if (isNamed() && !descriptor.isOpen()) {
+ Module caller = Reflection.getCallerClass().getModule();
+ if (caller != this) {
+ throw new IllegalStateException(caller + " != " + this);
+ }
+ implAddExportsOrOpens(pn, other, /*open*/true, /*syncVM*/true);
+ }
+
+ return this;
+ }
+
+
/**
* Updates the exports so that package {@code pn} is exported to module
* {@code other} but without notifying the VM.
@@ -555,7 +732,7 @@ public final class Module {
void implAddExportsNoSync(String pn, Module other) {
if (other == null)
other = EVERYONE_MODULE;
- implAddExports(pn.replace('/', '.'), other, false);
+ implAddExportsOrOpens(pn.replace('/', '.'), other, false, false);
}
/**
@@ -565,25 +742,36 @@ public final class Module {
* @apiNote This method is for white-box testing.
*/
void implAddExports(String pn, Module other) {
- implAddExports(pn, other, true);
+ implAddExportsOrOpens(pn, other, false, true);
}
/**
- * Updates the exports so that package {@code pn} is exported to module
- * {@code other}.
+ * Updates the module to open package {@code pn} to module {@code other}.
+ *
+ * @apiNote This method is for white-box tests and jtreg
+ */
+ void implAddOpens(String pn, Module other) {
+ implAddExportsOrOpens(pn, other, true, true);
+ }
+
+ /**
+ * Updates a module to export or open a module to another module.
*
* If {@code syncVM} is {@code true} then the VM is notified.
*/
- private void implAddExports(String pn, Module other, boolean syncVM) {
+ private void implAddExportsOrOpens(String pn,
+ Module other,
+ boolean open,
+ boolean syncVM) {
Objects.requireNonNull(other);
Objects.requireNonNull(pn);
- // unnamed modules export all packages
- if (!isNamed())
+ // all packages are open in unnamed and open modules
+ if (!isNamed() || descriptor.isOpen())
return;
- // nothing to do if already exported to other
- if (implIsExported(pn, other))
+ // nothing to do if already exported/open to other
+ if (implIsExportedOrOpen(pn, other, open))
return;
// can only export a package in the module
@@ -604,18 +792,23 @@ public final class Module {
}
}
- // add package name to transientExports if absent
- transientExports
+ // add package name to reflectivelyExports if absent
+ Map map = reflectivelyExports
.computeIfAbsent(this, other,
- (_this, _other) -> new ConcurrentHashMap<>())
- .putIfAbsent(pn, Boolean.TRUE);
+ (m1, m2) -> new ConcurrentHashMap<>());
+
+ if (open) {
+ map.put(pn, Boolean.TRUE); // may need to promote from FALSE to TRUE
+ } else {
+ map.putIfAbsent(pn, Boolean.FALSE);
+ }
}
// -- services --
// additional service type (2nd key) that some module (1st key) uses
- private static final WeakPairMap, Boolean> transientUses
+ private static final WeakPairMap, Boolean> reflectivelyUses
= new WeakPairMap<>();
/**
@@ -624,13 +817,13 @@ public final class Module {
* for use by frameworks that invoke {@link java.util.ServiceLoader
* ServiceLoader} on behalf of other modules or where the framework is
* passed a reference to the service type by other code. This method is
- * a no-op when invoked on an unnamed module.
+ * a no-op when invoked on an unnamed module or an automatic module.
*
* This method does not cause {@link
* Configuration#resolveRequiresAndUses resolveRequiresAndUses} to be
* re-run.
*
- * @param st
+ * @param service
* The service type
*
* @return this module
@@ -642,39 +835,45 @@ public final class Module {
* @see ModuleDescriptor#uses()
*/
@CallerSensitive
- public Module addUses(Class> st) {
- Objects.requireNonNull(st);
-
- if (isNamed()) {
+ public Module addUses(Class> service) {
+ Objects.requireNonNull(service);
+ if (isNamed() && !descriptor.isAutomatic()) {
Module caller = Reflection.getCallerClass().getModule();
if (caller != this) {
throw new IllegalStateException(caller + " != " + this);
}
-
- if (!canUse(st)) {
- transientUses.putIfAbsent(this, st, Boolean.TRUE);
- }
-
+ implAddUses(service);
}
return this;
}
+ /**
+ * Update this module to add a service dependence on the given service
+ * type.
+ */
+ void implAddUses(Class> service) {
+ if (!canUse(service)) {
+ reflectivelyUses.putIfAbsent(this, service, Boolean.TRUE);
+ }
+ }
+
+
/**
* Indicates if this module has a service dependence on the given service
* type. This method always returns {@code true} when invoked on an unnamed
- * module.
+ * module or an automatic module.
*
- * @param st
+ * @param service
* The service type
*
* @return {@code true} if this module uses service type {@code st}
*
* @see #addUses(Class)
*/
- public boolean canUse(Class> st) {
- Objects.requireNonNull(st);
+ public boolean canUse(Class> service) {
+ Objects.requireNonNull(service);
if (!isNamed())
return true;
@@ -683,11 +882,11 @@ public final class Module {
return true;
// uses was declared
- if (descriptor.uses().contains(st.getName()))
+ if (descriptor.uses().contains(service.getName()))
return true;
// uses added via addUses
- return transientUses.containsKeyPair(this, st);
+ return reflectivelyUses.containsKeyPair(this, service);
}
@@ -780,8 +979,12 @@ public final class Module {
* If {@code syncVM} is {@code true} then the VM is notified.
*/
private void implAddPackage(String pn, boolean syncVM) {
- if (pn.length() == 0)
- throw new IllegalArgumentException(" package not allowed");
+ if (!isNamed())
+ throw new InternalError("adding package to unnamed module?");
+ if (descriptor.isOpen())
+ throw new InternalError("adding package to open module?");
+ if (pn.isEmpty())
+ throw new InternalError("adding package to module?");
if (descriptor.packages().contains(pn)) {
// already in module
@@ -822,28 +1025,7 @@ public final class Module {
// -- creating Module objects --
/**
- * Find the runtime Module corresponding to the given ResolvedModule
- * in the given parent Layer (or its parents).
- */
- private static Module find(ResolvedModule resolvedModule, Layer layer) {
- Configuration cf = resolvedModule.configuration();
- String dn = resolvedModule.name();
-
- Module m = null;
- while (layer != null) {
- if (layer.configuration() == cf) {
- Optional om = layer.findModule(dn);
- m = om.get();
- assert m.getLayer() == layer;
- break;
- }
- layer = layer.parent().orElse(null);
- }
- return m;
- }
-
- /**
- * Defines each of the module in the given configuration to the runtime.
+ * Defines all module in a configuration to the runtime.
*
* @return a map of module name to runtime {@code Module}
*
@@ -854,26 +1036,40 @@ public final class Module {
Function clf,
Layer layer)
{
- Map modules = new HashMap<>();
- Map loaders = new HashMap<>();
+ Map nameToModule = new HashMap<>();
+ Map moduleToLoader = new HashMap<>();
+
+ boolean isBootLayer = (Layer.boot() == null);
+ Set loaders = new HashSet<>();
+
+ // map each module to a class loader
+ for (ResolvedModule resolvedModule : cf.modules()) {
+ String name = resolvedModule.name();
+ ClassLoader loader = clf.apply(name);
+ if (loader != null) {
+ moduleToLoader.put(name, loader);
+ loaders.add(loader);
+ } else if (!isBootLayer) {
+ throw new IllegalArgumentException("loader can't be 'null'");
+ }
+ }
// define each module in the configuration to the VM
for (ResolvedModule resolvedModule : cf.modules()) {
ModuleReference mref = resolvedModule.reference();
ModuleDescriptor descriptor = mref.descriptor();
String name = descriptor.name();
- ClassLoader loader = clf.apply(name);
URI uri = mref.location().orElse(null);
-
+ ClassLoader loader = moduleToLoader.get(resolvedModule.name());
Module m;
- if (loader == null && name.equals("java.base") && Layer.boot() == null) {
+ if (loader == null && isBootLayer && name.equals("java.base")) {
+ // java.base is already defined to the VM
m = Object.class.getModule();
} else {
m = new Module(layer, loader, descriptor, uri);
}
-
- modules.put(name, m);
- loaders.put(name, loader);
+ nameToModule.put(name, m);
+ moduleToLoader.put(name, loader);
}
// setup readability and exports
@@ -882,20 +1078,24 @@ public final class Module {
ModuleDescriptor descriptor = mref.descriptor();
String mn = descriptor.name();
- Module m = modules.get(mn);
+ Module m = nameToModule.get(mn);
assert m != null;
// reads
Set reads = new HashSet<>();
- for (ResolvedModule d : resolvedModule.reads()) {
- Module m2;
- if (d.configuration() == cf) {
- String dn = d.reference().descriptor().name();
- m2 = modules.get(dn);
- assert m2 != null;
+ for (ResolvedModule other : resolvedModule.reads()) {
+ Module m2 = null;
+ if (other.configuration() == cf) {
+ String dn = other.reference().descriptor().name();
+ m2 = nameToModule.get(dn);
} else {
- m2 = find(d, layer.parent().orElse(null));
+ for (Layer parent: layer.parents()) {
+ m2 = findModule(parent, other);
+ if (m2 != null)
+ break;
+ }
}
+ assert m2 != null;
reads.add(m2);
@@ -904,64 +1104,273 @@ public final class Module {
}
m.reads = reads;
- // automatic modules reads all unnamed modules
+ // automatic modules read all unnamed modules
if (descriptor.isAutomatic()) {
m.implAddReads(ALL_UNNAMED_MODULE, true);
}
- // exports
- Map> exports = new HashMap<>();
- for (Exports export : descriptor.exports()) {
- String source = export.source();
+ // exports and opens
+ initExportsAndOpens(descriptor, nameToModule, m);
+ }
+
+ // register the modules in the boot layer
+ if (isBootLayer) {
+ for (ResolvedModule resolvedModule : cf.modules()) {
+ ModuleReference mref = resolvedModule.reference();
+ ModuleDescriptor descriptor = mref.descriptor();
+ if (!descriptor.provides().isEmpty()) {
+ String name = descriptor.name();
+ Module m = nameToModule.get(name);
+ ClassLoader loader = moduleToLoader.get(name);
+ ServicesCatalog catalog;
+ if (loader == null) {
+ catalog = BootLoader.getServicesCatalog();
+ } else {
+ catalog = ServicesCatalog.getServicesCatalog(loader);
+ }
+ catalog.register(m);
+ }
+ }
+ }
+
+ // record that there is a layer with modules defined to the class loader
+ for (ClassLoader loader : loaders) {
+ layer.bindToLoader(loader);
+ }
+
+ return nameToModule;
+ }
+
+
+ /**
+ * Find the runtime Module corresponding to the given ResolvedModule
+ * in the given parent layer (or its parents).
+ */
+ private static Module findModule(Layer parent, ResolvedModule resolvedModule) {
+ Configuration cf = resolvedModule.configuration();
+ String dn = resolvedModule.name();
+ return parent.layers()
+ .filter(l -> l.configuration() == cf)
+ .findAny()
+ .map(layer -> {
+ Optional om = layer.findModule(dn);
+ assert om.isPresent() : dn + " not found in layer";
+ Module m = om.get();
+ assert m.getLayer() == layer : m + " not in expected layer";
+ return m;
+ })
+ .orElse(null);
+ }
+
+ /**
+ * Initialize the maps of exported and open packages for module m.
+ */
+ private static void initExportsAndOpens(ModuleDescriptor descriptor,
+ Map nameToModule,
+ Module m)
+ {
+ // The VM doesn't know about open modules so need to export all packages
+ if (descriptor.isOpen()) {
+ assert descriptor.opens().isEmpty();
+ for (String source : descriptor.packages()) {
String sourceInternalForm = source.replace('.', '/');
+ addExportsToAll0(m, sourceInternalForm);
+ }
+ return;
+ }
- if (export.isQualified()) {
+ Map> openPackages = new HashMap<>();
+ Map> exportedPackages = new HashMap<>();
- // qualified export
- Set targets = new HashSet<>();
- for (String target : export.targets()) {
- // only export to modules that are in this configuration
- Module m2 = modules.get(target);
- if (m2 != null) {
- targets.add(m2);
+ // process the open packages first
+ for (Opens opens : descriptor.opens()) {
+ String source = opens.source();
+ String sourceInternalForm = source.replace('.', '/');
+
+ if (opens.isQualified()) {
+ // qualified opens
+ Set targets = new HashSet<>();
+ for (String target : opens.targets()) {
+ // only open to modules that are in this configuration
+ Module m2 = nameToModule.get(target);
+ if (m2 != null) {
+ addExports0(m, sourceInternalForm, m2);
+ targets.add(m2);
+ }
+ }
+ if (!targets.isEmpty()) {
+ openPackages.put(source, targets);
+ }
+ } else {
+ // unqualified opens
+ addExportsToAll0(m, sourceInternalForm);
+ openPackages.put(source, EVERYONE_SET);
+ }
+ }
+
+ // next the exports, skipping exports when the package is open
+ for (Exports exports : descriptor.exports()) {
+ String source = exports.source();
+ String sourceInternalForm = source.replace('.', '/');
+
+ // skip export if package is already open to everyone
+ Set openToTargets = openPackages.get(source);
+ if (openToTargets != null && openToTargets.contains(EVERYONE_MODULE))
+ continue;
+
+ if (exports.isQualified()) {
+ // qualified exports
+ Set targets = new HashSet<>();
+ for (String target : exports.targets()) {
+ // only export to modules that are in this configuration
+ Module m2 = nameToModule.get(target);
+ if (m2 != null) {
+ // skip qualified export if already open to m2
+ if (openToTargets == null || !openToTargets.contains(m2)) {
addExports0(m, sourceInternalForm, m2);
+ targets.add(m2);
}
}
- if (!targets.isEmpty()) {
- exports.put(source, targets);
- }
-
- } else {
-
- // unqualified export
- exports.put(source, EVERYONE);
- addExportsToAll0(m, sourceInternalForm);
}
- }
- m.exports = exports;
- }
-
- // register the modules in the service catalog if they provide services
- for (ResolvedModule resolvedModule : cf.modules()) {
- ModuleReference mref = resolvedModule.reference();
- ModuleDescriptor descriptor = mref.descriptor();
- Map services = descriptor.provides();
- if (!services.isEmpty()) {
- String name = descriptor.name();
- Module m = modules.get(name);
- ClassLoader loader = loaders.get(name);
- ServicesCatalog catalog;
- if (loader == null) {
- catalog = BootLoader.getServicesCatalog();
- } else {
- catalog = SharedSecrets.getJavaLangAccess()
- .createOrGetServicesCatalog(loader);
+ if (!targets.isEmpty()) {
+ exportedPackages.put(source, targets);
}
- catalog.register(m);
+
+ } else {
+ // unqualified exports
+ addExportsToAll0(m, sourceInternalForm);
+ exportedPackages.put(source, EVERYONE_SET);
}
}
- return modules;
+ if (!openPackages.isEmpty())
+ m.openPackages = openPackages;
+ if (!exportedPackages.isEmpty())
+ m.exportedPackages = exportedPackages;
+ }
+
+
+ // -- annotations --
+
+ /**
+ * {@inheritDoc}
+ * This method returns {@code null} when invoked on an unnamed module.
+ */
+ @Override
+ public T getAnnotation(Class annotationClass) {
+ return moduleInfoClass().getDeclaredAnnotation(annotationClass);
+ }
+
+ /**
+ * {@inheritDoc}
+ * This method returns an empty array when invoked on an unnamed module.
+ */
+ @Override
+ public Annotation[] getAnnotations() {
+ return moduleInfoClass().getAnnotations();
+ }
+
+ /**
+ * {@inheritDoc}
+ * This method returns an empty array when invoked on an unnamed module.
+ */
+ @Override
+ public Annotation[] getDeclaredAnnotations() {
+ return moduleInfoClass().getDeclaredAnnotations();
+ }
+
+ // cached class file with annotations
+ private volatile Class> moduleInfoClass;
+
+ private Class> moduleInfoClass() {
+ Class> clazz = this.moduleInfoClass;
+ if (clazz != null)
+ return clazz;
+
+ synchronized (this) {
+ clazz = this.moduleInfoClass;
+ if (clazz == null) {
+ if (isNamed()) {
+ PrivilegedAction> pa = this::loadModuleInfoClass;
+ clazz = AccessController.doPrivileged(pa);
+ }
+ if (clazz == null) {
+ class DummyModuleInfo { }
+ clazz = DummyModuleInfo.class;
+ }
+ this.moduleInfoClass = clazz;
+ }
+ return clazz;
+ }
+ }
+
+ private Class> loadModuleInfoClass() {
+ Class> clazz = null;
+ try (InputStream in = getResourceAsStream("module-info.class")) {
+ if (in != null)
+ clazz = loadModuleInfoClass(in);
+ } catch (Exception ignore) { }
+ return clazz;
+ }
+
+ /**
+ * Loads module-info.class as a package-private interface in a class loader
+ * that is a child of this module's class loader.
+ */
+ private Class> loadModuleInfoClass(InputStream in) throws IOException {
+ final String MODULE_INFO = "module-info";
+
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS
+ + ClassWriter.COMPUTE_FRAMES);
+
+ ClassVisitor cv = new ClassVisitor(Opcodes.ASM5, cw) {
+ @Override
+ public void visit(int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ cw.visit(version,
+ Opcodes.ACC_INTERFACE
+ + Opcodes.ACC_ABSTRACT
+ + Opcodes.ACC_SYNTHETIC,
+ MODULE_INFO,
+ null,
+ "java/lang/Object",
+ null);
+ }
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // keep annotations
+ return super.visitAnnotation(desc, visible);
+ }
+ @Override
+ public void visitAttribute(Attribute attr) {
+ // drop non-annotation attributes
+ }
+ };
+
+ ClassReader cr = new ClassReader(in);
+ cr.accept(cv, 0);
+ byte[] bytes = cw.toByteArray();
+
+ ClassLoader cl = new ClassLoader(loader) {
+ @Override
+ protected Class> findClass(String cn)throws ClassNotFoundException {
+ if (cn.equals(MODULE_INFO)) {
+ return super.defineClass(cn, bytes, 0, bytes.length);
+ } else {
+ throw new ClassNotFoundException(cn);
+ }
+ }
+ };
+
+ try {
+ return cl.loadClass(MODULE_INFO);
+ } catch (ClassNotFoundException e) {
+ throw new InternalError(e);
+ }
}
@@ -969,16 +1378,35 @@ public final class Module {
/**
- * Returns an input stream for reading a resource in this module. Returns
- * {@code null} if the resource is not in this module or access to the
- * resource is denied by the security manager.
- * The {@code name} is a {@code '/'}-separated path name that identifies
- * the resource.
+ * Returns an input stream for reading a resource in this module. The
+ * {@code name} parameter is a {@code '/'}-separated path name that
+ * identifies the resource.
*
- * If this module is an unnamed module, and the {@code ClassLoader} for
- * this module is not {@code null}, then this method is equivalent to
- * invoking the {@link ClassLoader#getResourceAsStream(String)
- * getResourceAsStream} method on the class loader for this module.
+ *
A resource in a named modules may be encapsulated so that
+ * it cannot be located by code in other modules. Whether a resource can be
+ * located or not is determined as follows:
+ *
+ *
+ * - The package name of the resource is derived from the
+ * subsequence of characters that precedes the last {@code '/'} and then
+ * replacing each {@code '/'} character in the subsequence with
+ * {@code '.'}. For example, the package name derived for a resource
+ * named "{@code a/b/c/foo.properties}" is "{@code a.b.c}".
+ *
+ * - If the package name is a package in the module then the package
+ * must be {@link #isOpen open} the module of the caller of this method.
+ * If the package is not in the module then the resource is not
+ * encapsulated. Resources in the unnamed package or "{@code META-INF}",
+ * for example, are never encapsulated because they can never be
+ * packages in a named module.
+ *
+ * - As a special case, resources ending with "{@code .class}" are
+ * never encapsulated.
+ *
+ *
+ * This method returns {@code null} if the resource is not in this
+ * module, the resource is encapsulated and cannot be located by the caller,
+ * or access to the resource is denied by the security manager.
*
* @param name
* The resource name
@@ -990,36 +1418,35 @@ public final class Module {
*
* @see java.lang.module.ModuleReader#open(String)
*/
+ @CallerSensitive
public InputStream getResourceAsStream(String name) throws IOException {
Objects.requireNonNull(name);
- URL url = null;
-
- if (isNamed()) {
- String mn = this.name;
-
- // special-case built-in class loaders to avoid URL connection
- if (loader == null) {
- return BootLoader.findResourceAsStream(mn, name);
- } else if (loader instanceof BuiltinClassLoader) {
- return ((BuiltinClassLoader) loader).findResourceAsStream(mn, name);
+ if (isNamed() && !ResourceHelper.isSimpleResource(name)) {
+ Module caller = Reflection.getCallerClass().getModule();
+ if (caller != this && caller != Object.class.getModule()) {
+ // ignore packages added for proxies via addPackage
+ Set packages = getDescriptor().packages();
+ String pn = ResourceHelper.getPackageName(name);
+ if (packages.contains(pn) && !isOpen(pn, caller)) {
+ // resource is in package not open to caller
+ return null;
+ }
}
-
- // use SharedSecrets to invoke protected method
- url = SharedSecrets.getJavaLangAccess().findResource(loader, mn, name);
-
- } else {
-
- // unnamed module
- if (loader == null) {
- url = BootLoader.findResource(name);
- } else {
- return loader.getResourceAsStream(name);
- }
-
}
- // fallthrough to URL case
+ String mn = this.name;
+
+ // special-case built-in class loaders to avoid URL connection
+ if (loader == null) {
+ return BootLoader.findResourceAsStream(mn, name);
+ } else if (loader instanceof BuiltinClassLoader) {
+ return ((BuiltinClassLoader) loader).findResourceAsStream(mn, name);
+ }
+
+ // locate resource in module
+ JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
+ URL url = jla.findResource(loader, mn, name);
if (url != null) {
try {
return url.openStream();
@@ -1053,6 +1480,7 @@ public final class Module {
// JVM_DefineModule
private static native void defineModule0(Module module,
+ boolean isOpen,
String version,
String location,
String[] pns);
@@ -1098,15 +1526,31 @@ public final class Module {
}
@Override
public void addExports(Module m, String pn, Module other) {
- m.implAddExports(pn, other, true);
+ m.implAddExportsOrOpens(pn, other, false, true);
+ }
+ @Override
+ public void addOpens(Module m, String pn, Module other) {
+ m.implAddExportsOrOpens(pn, other, true, true);
}
@Override
public void addExportsToAll(Module m, String pn) {
- m.implAddExports(pn, Module.EVERYONE_MODULE, true);
+ m.implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, false, true);
+ }
+ @Override
+ public void addOpensToAll(Module m, String pn) {
+ m.implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, true, true);
}
@Override
public void addExportsToAllUnnamed(Module m, String pn) {
- m.implAddExports(pn, Module.ALL_UNNAMED_MODULE, true);
+ m.implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, false, true);
+ }
+ @Override
+ public void addOpensToAllUnnamed(Module m, String pn) {
+ m.implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, true, true);
+ }
+ @Override
+ public void addUses(Module m, Class> service) {
+ m.implAddUses(service);
}
@Override
public void addPackage(Module m, String pn) {
@@ -1116,6 +1560,14 @@ public final class Module {
public ServicesCatalog getServicesCatalog(Layer layer) {
return layer.getServicesCatalog();
}
+ @Override
+ public Stream layers(Layer layer) {
+ return layer.layers();
+ }
+ @Override
+ public Stream layers(ClassLoader loader) {
+ return Layer.layers(loader);
+ }
});
}
}
diff --git a/jdk/src/java.base/share/classes/java/util/ResourceBundle.java b/jdk/src/java.base/share/classes/java/util/ResourceBundle.java
index 1d84e2dee6b..f04452664ff 100644
--- a/jdk/src/java.base/share/classes/java/util/ResourceBundle.java
+++ b/jdk/src/java.base/share/classes/java/util/ResourceBundle.java
@@ -42,6 +42,7 @@ package java.util;
import java.io.IOException;
import java.io.InputStream;
+import java.io.UncheckedIOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
@@ -62,13 +63,14 @@ import java.util.jar.JarEntry;
import java.util.spi.ResourceBundleControlProvider;
import java.util.spi.ResourceBundleProvider;
+import jdk.internal.loader.BootLoader;
import jdk.internal.misc.JavaUtilResourceBundleAccess;
import jdk.internal.misc.SharedSecrets;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
+import sun.security.action.GetPropertyAction;
import sun.util.locale.BaseLocale;
import sun.util.locale.LocaleObjectCache;
-import sun.util.locale.provider.ResourceBundleProviderSupport;
import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION;
@@ -247,7 +249,8 @@ import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION;
* used to load resource bundles. If no service provider is available, or if
* none of the service providers returns a resource bundle and the caller module
* doesn't have its own service provider, the {@code getBundle} factory method
- * searches for resource bundles local to the caller module. The resource bundle
+ * searches for resource bundles that are local in the caller module and that
+ * are visible to the class loader of the caller module. The resource bundle
* formats for local module searching are "java.class" and "java.properties".
*
* ResourceBundle.Control
@@ -372,6 +375,18 @@ public abstract class ResourceBundle {
public void setName(ResourceBundle bundle, String name) {
bundle.name = name;
}
+
+ @Override
+ public ResourceBundle getBundle(String baseName, Locale locale, Module module) {
+ // use the given module as the caller to bypass the access check
+ return getBundleImpl(module, module, getLoader(module),
+ baseName, locale, Control.INSTANCE);
+ }
+
+ @Override
+ public ResourceBundle newResourceBundle(Class extends ResourceBundle> bundleClass) {
+ return ResourceBundleProviderHelper.newResourceBundle(bundleClass);
+ }
});
}
@@ -999,6 +1014,14 @@ public abstract class ResourceBundle {
* getBundle(baseName, Locale.getDefault(), module)
*
*
+ * Resource bundles in named modules may be encapsulated. When
+ * the resource bundle is loaded from a provider, the caller module
+ * must have an appropriate uses clause in its module descriptor
+ * to declare that the module uses implementations of {@code "baseName"Provider}.
+ * When the resource bundle is loaded from the specified module, it is
+ * subject to the encapsulation rules specified by
+ * {@link Module#getResourceAsStream Module.getResourceAsStream}.
+ *
* @param baseName the base name of the resource bundle,
* a fully qualified class name
* @param module the module for which the resource bundle is searched
@@ -1024,10 +1047,19 @@ public abstract class ResourceBundle {
* Gets a resource bundle using the specified base name and locale
* on behalf of the specified module.
*
+ *
Resource bundles in named modules may be encapsulated. When
+ * the resource bundle is loaded from a provider, the caller module
+ * must have an appropriate uses clause in its module descriptor
+ * to declare that the module uses implementations of {@code "baseName"Provider}.
+ * When the resource bundle is loaded from the specified module, it is
+ * subject to the encapsulation rules specified by
+ * {@link Module#getResourceAsStream Module.getResourceAsStream}.
+ *
*
* If the given {@code module} is a named module, this method will
* load the service providers for {@link java.util.spi.ResourceBundleProvider}
- * and also resource bundles local in the given module (refer to the
+ * and also resource bundles that are local in the given module or that
+ * are visible to the class loader of the given module (refer to the
* Resource Bundles in Named Modules section
* for details).
*
@@ -1035,9 +1067,8 @@ public abstract class ResourceBundle {
* If the given {@code module} is an unnamed module, then this method is
* equivalent to calling {@link #getBundle(String, Locale, ClassLoader)
* getBundle(baseName, targetLocale, module.getClassLoader()} to load
- * resource bundles that are in unnamed modules visible to the
- * class loader of the given unnamed module. It will not find resource
- * bundles from named modules.
+ * resource bundles that are visible to the class loader of the given
+ * unnamed module.
*
* @param baseName the base name of the resource bundle,
* a fully qualified class name
@@ -1126,7 +1157,8 @@ public abstract class ResourceBundle {
* Resource bundles in a named module are private to that module. If
* the caller is in a named module, this method will find resource bundles
* from the service providers of {@link java.util.spi.ResourceBundleProvider}
- * and also find resource bundles private to the caller's module.
+ * and also find resource bundles that are in the caller's module or
+ * that are visible to the given class loader.
* If the caller is in a named module and the given {@code loader} is
* different than the caller's class loader, or if the caller is not in
* a named module, this method will not find resource bundles from named
@@ -1587,13 +1619,20 @@ public abstract class ResourceBundle {
// get resource bundles for a named module only
// if loader is the module's class loader
if (loader == ml || (ml == null && loader == RBClassLoader.INSTANCE)) {
- return getBundleImpl(baseName, locale, loader, module, control);
+ return getBundleImpl(module, module, loader, baseName, locale, control);
}
}
// find resource bundles from unnamed module
- Module module = loader != null ? loader.getUnnamedModule()
- : ClassLoader.getSystemClassLoader().getUnnamedModule();
- return getBundleImpl(baseName, locale, loader, module, control);
+ Module unnamedModule = loader != null
+ ? loader.getUnnamedModule()
+ : ClassLoader.getSystemClassLoader().getUnnamedModule();
+
+ if (caller == null) {
+ throw new InternalError("null caller");
+ }
+
+ Module callerModule = caller.getModule();
+ return getBundleImpl(callerModule, unnamedModule, loader, baseName, locale, control);
}
private static ResourceBundle getBundleFromModule(Class> caller,
@@ -1602,19 +1641,21 @@ public abstract class ResourceBundle {
Locale locale,
Control control) {
Objects.requireNonNull(module);
- if (caller.getModule() != module) {
+ Module callerModule = caller.getModule();
+ if (callerModule != module) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(GET_CLASSLOADER_PERMISSION);
}
}
- return getBundleImpl(baseName, locale, getLoader(module), module, control);
+ return getBundleImpl(callerModule, module, getLoader(module), baseName, locale, control);
}
- private static ResourceBundle getBundleImpl(String baseName,
- Locale locale,
- ClassLoader loader,
+ private static ResourceBundle getBundleImpl(Module callerModule,
Module module,
+ ClassLoader loader,
+ String baseName,
+ Locale locale,
Control control) {
if (locale == null || control == null) {
throw new NullPointerException();
@@ -1661,7 +1702,8 @@ public abstract class ResourceBundle {
throw new IllegalArgumentException("Invalid Control: getCandidateLocales");
}
- bundle = findBundle(cacheKey, module, candidateLocales, formats, 0, control, baseBundle);
+ bundle = findBundle(callerModule, module, cacheKey,
+ candidateLocales, formats, 0, control, baseBundle);
// If the loaded bundle is the base bundle and exactly for the
// requested locale or the only candidate locale, then take the
@@ -1710,8 +1752,9 @@ public abstract class ResourceBundle {
return valid;
}
- private static ResourceBundle findBundle(CacheKey cacheKey,
+ private static ResourceBundle findBundle(Module callerModule,
Module module,
+ CacheKey cacheKey,
List candidateLocales,
List formats,
int index,
@@ -1720,7 +1763,8 @@ public abstract class ResourceBundle {
Locale targetLocale = candidateLocales.get(index);
ResourceBundle parent = null;
if (index != candidateLocales.size() - 1) {
- parent = findBundle(cacheKey, module, candidateLocales, formats, index + 1,
+ parent = findBundle(callerModule, module, cacheKey,
+ candidateLocales, formats, index + 1,
control, baseBundle);
} else if (baseBundle != null && Locale.ROOT.equals(targetLocale)) {
return baseBundle;
@@ -1764,10 +1808,10 @@ public abstract class ResourceBundle {
if (bundle != NONEXISTENT_BUNDLE) {
CacheKey constKey = (CacheKey) cacheKey.clone();
-
+ trace("findBundle: %d %s %s formats: %s%n", index, candidateLocales, cacheKey, formats);
try {
if (module.isNamed()) {
- bundle = loadBundle(cacheKey, formats, control, module);
+ bundle = loadBundle(cacheKey, formats, control, module, callerModule);
} else {
bundle = loadBundle(cacheKey, formats, control, expiredBundle);
}
@@ -1794,39 +1838,60 @@ public abstract class ResourceBundle {
private static final String UNKNOWN_FORMAT = "";
+
/*
* Loads a ResourceBundle in named modules
*/
private static ResourceBundle loadBundle(CacheKey cacheKey,
List formats,
Control control,
- Module module) {
+ Module module,
+ Module callerModule) {
String baseName = cacheKey.getName();
Locale targetLocale = cacheKey.getLocale();
ResourceBundle bundle = null;
if (cacheKey.hasProviders()) {
- bundle = loadBundleFromProviders(baseName, targetLocale,
- cacheKey.getProviders(), cacheKey);
+ if (callerModule == module) {
+ bundle = loadBundleFromProviders(baseName,
+ targetLocale,
+ cacheKey.getProviders(),
+ cacheKey);
+ } else {
+ // load from provider if the caller module has access to the
+ // service type and also declares `uses`
+ ClassLoader loader = getLoader(module);
+ Class svc =
+ getResourceBundleProviderType(baseName, loader);
+ if (svc != null
+ && Reflection.verifyModuleAccess(callerModule, svc)
+ && callerModule.canUse(svc)) {
+ bundle = loadBundleFromProviders(baseName,
+ targetLocale,
+ cacheKey.getProviders(),
+ cacheKey);
+ }
+ }
+
if (bundle != null) {
cacheKey.setFormat(UNKNOWN_FORMAT);
}
}
+
// If none of providers returned a bundle and the caller has no provider,
- // look up module-local bundles.
+ // look up module-local bundles or from the class path
if (bundle == null && !cacheKey.callerHasProvider()) {
- String bundleName = control.toBundleName(baseName, targetLocale);
for (String format : formats) {
try {
switch (format) {
case "java.class":
- PrivilegedAction pa = ()
- -> ResourceBundleProviderSupport
- .loadResourceBundle(module, bundleName);
- bundle = AccessController.doPrivileged(pa, null, GET_CLASSLOADER_PERMISSION);
+ bundle = ResourceBundleProviderHelper
+ .loadResourceBundle(callerModule, module, baseName, targetLocale);
+
break;
case "java.properties":
- bundle = ResourceBundleProviderSupport.loadPropertyResourceBundle(module, bundleName);
+ bundle = ResourceBundleProviderHelper
+ .loadPropertyResourceBundle(callerModule, module, baseName, targetLocale);
break;
default:
throw new InternalError("unexpected format: " + format);
@@ -1844,29 +1909,46 @@ public abstract class ResourceBundle {
return bundle;
}
+ /**
+ * Returns a ServiceLoader that will find providers that are bound to
+ * a given named module.
+ */
private static ServiceLoader getServiceLoader(Module module,
- String baseName) {
+ String baseName)
+ {
if (!module.isNamed()) {
return null;
}
- PrivilegedAction pa = module::getClassLoader;
- ClassLoader loader = AccessController.doPrivileged(pa);
- return getServiceLoader(module, loader, baseName);
+
+ ClassLoader loader = getLoader(module);
+ Class service =
+ getResourceBundleProviderType(baseName, loader);
+ if (service != null && Reflection.verifyModuleAccess(module, service)) {
+ try {
+ // locate providers that are visible to the class loader
+ // ServiceConfigurationError will be thrown if the module
+ // does not declare `uses` the service type
+ return ServiceLoader.load(service, loader, module);
+ } catch (ServiceConfigurationError e) {
+ // "uses" not declared
+ return null;
+ }
+ }
+ return null;
}
- /**
- * Returns a ServiceLoader that will find providers that are bound to
- * a given module that may be named or unnamed.
- */
- private static ServiceLoader getServiceLoader(Module module,
- ClassLoader loader,
- String baseName)
+ /**
+ * Returns the service type of the given baseName that is visible
+ * to the given class loader
+ */
+ private static Class
+ getResourceBundleProviderType(String baseName, ClassLoader loader)
{
// Look up + "Provider"
String providerName = baseName + "Provider";
// Use the class loader of the getBundle caller so that the caller's
// visibility of the provider type is checked.
- Class service = AccessController.doPrivileged(
+ return AccessController.doPrivileged(
new PrivilegedAction<>() {
@Override
public Class run() {
@@ -1881,16 +1963,6 @@ public abstract class ResourceBundle {
return null;
}
});
-
- if (service != null && Reflection.verifyModuleAccess(module, service)) {
- try {
- return ServiceLoader.load(service, loader, module);
- } catch (ServiceConfigurationError e) {
- // "uses" not declared: load bundle local in the module
- return null;
- }
- }
- return null;
}
/**
@@ -1914,6 +1986,7 @@ public abstract class ResourceBundle {
cacheKey.callerHasProvider = Boolean.TRUE;
}
ResourceBundle bundle = provider.getBundle(baseName, locale);
+ trace("provider %s %s locale: %s bundle: %s%n", provider, baseName, locale, bundle);
if (bundle != null) {
return bundle;
}
@@ -3016,6 +3089,14 @@ public abstract class ResourceBundle {
* indicates that this method is being called because the previously
* loaded resource bundle has expired.
*
+ * @implSpec
+ *
+ * Resource bundles in named modules are subject to the encapsulation
+ * rules specified by {@link Module#getResourceAsStream Module.getResourceAsStream}.
+ * A resource bundle in a named module visible to the given class loader
+ * is accessible when the package of the resource file corresponding
+ * to the resource bundle is open unconditionally.
+ *
* The default implementation instantiates a
* ResourceBundle as follows.
*
@@ -3026,12 +3107,15 @@ public abstract class ResourceBundle {
* locale)}.
*
* If format is "java.class", the
- * {@link Class} specified by the bundle name is loaded by calling
- * {@link ClassLoader#loadClass(String)}. Then, a
- * ResourceBundle is instantiated by calling {@link
- * Class#newInstance()}. Note that the reload flag is
- * ignored for loading class-based resource bundles in this default
- * implementation.
+ * {@link Class} specified by the bundle name is loaded with the
+ * given class loader. If the {@code Class} is found and accessible
+ * then the ResourceBundle is instantiated. The
+ * resource bundle is accessible if the package of the bundle class file
+ * is open unconditionally; otherwise, {@code IllegalAccessException}
+ * will be thrown.
+ * Note that the reload flag is ignored for loading
+ * class-based resource bundles in this default implementation.
+ *
*
* If format is "java.properties",
* {@link #toResourceName(String, String) toResourceName(bundlename,
@@ -3105,7 +3189,6 @@ public abstract class ResourceBundle {
/*
* Legacy mechanism to locate resource bundle in unnamed module only
* that is visible to the given loader and accessible to the given caller.
- * This only finds resources on the class path but not in named modules.
*/
String bundleName = toBundleName(baseName, locale);
ResourceBundle bundle = null;
@@ -3117,18 +3200,15 @@ public abstract class ResourceBundle {
if (ResourceBundle.class.isAssignableFrom(c)) {
@SuppressWarnings("unchecked")
Class bundleClass = (Class)c;
+ Module m = bundleClass.getModule();
- // This doesn't allow unnamed modules to find bundles in
- // named modules other than via the service loader mechanism.
- // Otherwise, this will make the newBundle method to be
- // caller-sensitive in order to verify access check.
- // So migrating resource bundles to named module can't
- // just export the package (in general, legacy resource
- // bundles have split package if they are packaged separate
- // from the consumer.)
- if (bundleClass.getModule().isNamed()) {
- throw new IllegalAccessException("unnamed modules can't load " + bundleName
- + " in named module " + bundleClass.getModule().getName());
+ // To access a resource bundle in a named module,
+ // either class-based or properties-based, the resource
+ // bundle must be opened unconditionally,
+ // same rule as accessing a resource file.
+ if (m.isNamed() && !m.isOpen(bundleClass.getPackageName())) {
+ throw new IllegalAccessException("unnamed module can't load " +
+ bundleClass.getName() + " in " + m.toString());
}
try {
// bundle in a unnamed module
@@ -3502,4 +3582,173 @@ public abstract class ResourceBundle {
return null;
}
}
+
+ private static class ResourceBundleProviderHelper {
+ /**
+ * Returns a new ResourceBundle instance of the given bundleClass
+ */
+ static ResourceBundle newResourceBundle(Class extends ResourceBundle> bundleClass) {
+ try {
+ @SuppressWarnings("unchecked")
+ Constructor extends ResourceBundle> ctor =
+ bundleClass.getConstructor();
+ if (!Modifier.isPublic(ctor.getModifiers())) {
+ return null;
+ }
+ // java.base may not be able to read the bundleClass's module.
+ PrivilegedAction pa = () -> { ctor.setAccessible(true); return null;};
+ AccessController.doPrivileged(pa);
+ try {
+ return ctor.newInstance((Object[]) null);
+ } catch (InvocationTargetException e) {
+ uncheckedThrow(e);
+ } catch (InstantiationException | IllegalAccessException e) {
+ throw new InternalError(e);
+ }
+ } catch (NoSuchMethodException e) {
+ throw new InternalError(e);
+ }
+ return null;
+ }
+
+ /**
+ * Loads a {@code ResourceBundle} of the given {@code bundleName} local to
+ * the given {@code module}. If not found, search the bundle class
+ * that is visible from the module's class loader.
+ *
+ * The caller module is used for access check only.
+ */
+ static ResourceBundle loadResourceBundle(Module callerModule,
+ Module module,
+ String baseName,
+ Locale locale)
+ {
+ String bundleName = Control.INSTANCE.toBundleName(baseName, locale);
+ try {
+ PrivilegedAction> pa = () -> Class.forName(module, bundleName);
+ Class> c = AccessController.doPrivileged(pa, null, GET_CLASSLOADER_PERMISSION);
+ trace("local in %s %s caller %s: %s%n", module, bundleName, callerModule, c);
+
+ if (c == null) {
+ // if not found from the given module, locate resource bundle
+ // that is visible to the module's class loader
+ ClassLoader loader = getLoader(module);
+ if (loader != null) {
+ c = Class.forName(bundleName, false, loader);
+ } else {
+ c = BootLoader.loadClassOrNull(bundleName);
+ }
+ trace("loader for %s %s caller %s: %s%n", module, bundleName, callerModule, c);
+ }
+
+ if (c != null && ResourceBundle.class.isAssignableFrom(c)) {
+ @SuppressWarnings("unchecked")
+ Class bundleClass = (Class) c;
+ Module m = bundleClass.getModule();
+ if (!isAccessible(callerModule, m, bundleClass.getPackageName())) {
+ trace(" %s does not have access to %s/%s%n", callerModule,
+ m.getName(), bundleClass.getPackageName());
+ return null;
+ }
+
+ return newResourceBundle(bundleClass);
+ }
+ } catch (ClassNotFoundException e) {}
+ return null;
+ }
+
+ /**
+ * Tests if resources of the given package name from the given module are
+ * open to the caller module.
+ */
+ static boolean isAccessible(Module callerModule, Module module, String pn) {
+ if (!module.isNamed() || callerModule == module)
+ return true;
+
+ return module.isOpen(pn, callerModule);
+ }
+
+ /**
+ * Loads properties of the given {@code bundleName} local in the given
+ * {@code module}. If the .properties is not found or not open
+ * to the caller module to access, it will find the resource that
+ * is visible to the module's class loader.
+ *
+ * The caller module is used for access check only.
+ */
+ static ResourceBundle loadPropertyResourceBundle(Module callerModule,
+ Module module,
+ String baseName,
+ Locale locale)
+ throws IOException
+ {
+ String bundleName = Control.INSTANCE.toBundleName(baseName, locale);
+
+ PrivilegedAction pa = () -> {
+ try {
+ String resourceName = Control.INSTANCE
+ .toResourceName0(bundleName, "properties");
+ if (resourceName == null) {
+ return null;
+ }
+ trace("local in %s %s caller %s%n", module, resourceName, callerModule);
+
+ // if the package is in the given module but not opened
+ // locate it from the given module first.
+ String pn = toPackageName(bundleName);
+ trace(" %s/%s is accessible to %s : %s%n",
+ module.getName(), pn, callerModule,
+ isAccessible(callerModule, module, pn));
+ if (isAccessible(callerModule, module, pn)) {
+ InputStream in = module.getResourceAsStream(resourceName);
+ if (in != null) {
+ return in;
+ }
+ }
+
+ ClassLoader loader = module.getClassLoader();
+ trace("loader for %s %s caller %s%n", module, resourceName, callerModule);
+
+ try {
+ if (loader != null) {
+ return loader.getResourceAsStream(resourceName);
+ } else {
+ URL url = BootLoader.findResource(resourceName);
+ if (url != null) {
+ return url.openStream();
+ }
+ }
+ } catch (Exception e) {}
+ return null;
+
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ };
+
+ try (InputStream stream = AccessController.doPrivileged(pa)) {
+ if (stream != null) {
+ return new PropertyResourceBundle(stream);
+ } else {
+ return null;
+ }
+ } catch (UncheckedIOException e) {
+ throw e.getCause();
+ }
+ }
+
+ private static String toPackageName(String bundleName) {
+ int i = bundleName.lastIndexOf('.');
+ return i != -1 ? bundleName.substring(0, i) : "";
+ }
+
+ }
+
+ private static final boolean TRACE_ON = Boolean.valueOf(
+ GetPropertyAction.privilegedGetProperty("resource.bundle.debug", "false"));
+
+ private static void trace(String format, Object... params) {
+ if (TRACE_ON)
+ System.out.format(format, params);
+ }
}
diff --git a/jdk/src/java.base/share/classes/java/util/ServiceLoader.java b/jdk/src/java.base/share/classes/java/util/ServiceLoader.java
index 081b96158ff..0b437d44e2e 100644
--- a/jdk/src/java.base/share/classes/java/util/ServiceLoader.java
+++ b/jdk/src/java.base/share/classes/java/util/ServiceLoader.java
@@ -32,23 +32,28 @@ import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Layer;
+import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Module;
import java.net.URL;
import java.net.URLConnection;
-import java.security.AccessController;
import java.security.AccessControlContext;
+import java.security.AccessController;
import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
import jdk.internal.loader.BootLoader;
-import jdk.internal.loader.Loader;
-import jdk.internal.loader.LoaderPool;
import jdk.internal.misc.JavaLangAccess;
+import jdk.internal.misc.JavaLangReflectModuleAccess;
import jdk.internal.misc.SharedSecrets;
import jdk.internal.misc.VM;
import jdk.internal.module.ServicesCatalog;
import jdk.internal.module.ServicesCatalog.ServiceProvider;
-
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
@@ -76,21 +81,49 @@ import jdk.internal.reflect.Reflection;
* request together with code that can create the actual provider on demand.
* The details of provider classes tend to be highly service-specific; no
* single class or interface could possibly unify them, so no such type is
- * defined here. A requirement enforced by this facility is that each provider
- * class must have a {@code public} zero-argument constructor.
+ * defined here.
+ *
+ * Providers deployed as explicit modules on the module path are
+ * instantiated by a provider factory or directly via the provider's
+ * constructor. In the module declaration then the class name specified in the
+ * provides clause is a provider factory if it is public and defines a
+ * public static no-args method named "{@code provider}". The return type of
+ * the method must be assignable to the service type. If the class is
+ * not a provider factory then it is public with a public zero-argument
+ * constructor. The requirement that the provider factory or provider class
+ * be public helps to document the intent that the provider will be
+ * instantiated by the service-provider loading facility.
+ *
+ *
As an example, suppose a module declares the following:
+ *
+ *
{@code
+ * provides com.example.CodecSet with com.example.impl.StandardCodecs;
+ * provides com.example.CodecSet with com.example.impl.ExtendedCodecsFactory;
+ * }
+ *
+ * where {@code com.example.CodecSet} is the service type, {@code
+ * com.example.impl.StandardCodecs} is a provider class that is public with a
+ * public no-args constructor, {@code com.example.impl.ExtendedCodecsFactory}
+ * is a public class that defines a public static no-args method named
+ * "{@code provider}" with a return type that is {@code CodecSet} or a subtype
+ * of. For this example then {@code StandardCodecs}'s no-arg constructor will
+ * be used to instantiate {@code StandardCodecs}. {@code ExtendedCodecsFactory}
+ * will be treated as a provider factory and {@code
+ * ExtendedCodecsFactory.provider()} will be invoked to obtain the provider.
+ *
+ *
Providers deployed on the class path or as {@link
+ * java.lang.module.ModuleDescriptor#isAutomatic automatic-modules} on the
+ * module path must have a public zero-argument constructor.
*
*
An application or library using this loading facility and developed
- * and deployed as a named module must have an appropriate uses clause
- * in its module descriptor to declare that the module uses
+ * and deployed as an explicit module must have an appropriate uses
+ * clause in its module descriptor to declare that the module uses
* implementations of the service. A corresponding requirement is that a
* provider deployed as a named module must have an appropriate
* provides clause in its module descriptor to declare that the module
* provides an implementation of the service. The uses and
- * provides allow consumers of a service to be linked to
- * providers of the service. In the case of {@code load} methods that locate
- * service providers using a class loader, then provider modules defined to
- * that class loader, or a class loader reachable using {@link
- * ClassLoader#getParent() parent} delegation, will be located.
+ * provides allow consumers of a service to be linked to modules
+ * containing providers of the service.
*
*
A service provider that is packaged as a JAR file for the class path is
* identified by placing a provider-configuration file in the resource
@@ -114,27 +147,116 @@ import jdk.internal.reflect.Reflection;
*
*
Providers are located and instantiated lazily, that is, on demand. A
* service loader maintains a cache of the providers that have been loaded so
- * far. Each invocation of the {@link #iterator iterator} method returns an
- * iterator that first yields all of the elements of the cache, in
- * instantiation order, and then lazily locates and instantiates any remaining
- * providers, adding each one to the cache in turn. The cache can be cleared
+ * far. Each invocation of the {@link #iterator iterator} method returns an
+ * iterator that first yields all of the elements cached from previous
+ * iteration, in instantiation order, and then lazily locates and instantiates
+ * any remaining providers, adding each one to the cache in turn. Similarly,
+ * each invocation of the {@link #stream stream} method returns a stream that
+ * first processes all providers loaded by previous stream operations, in load
+ * order, and then lazily locates any remaining providers. Caches are cleared
* via the {@link #reload reload} method.
*
+ *
Locating providers
+ *
+ * The {@code load} methods locate providers using a class loader or module
+ * {@link Layer layer}. When locating providers using a class loader then
+ * providers in both named and unnamed modules may be located. When locating
+ * providers using a module layer then only providers in named modules in
+ * the layer (or parent layers) are located.
+ *
+ *
When locating providers using a class loader then any providers in named
+ * modules defined to the class loader, or any class loader that is reachable
+ * via parent delegation, are located. Additionally, providers in module layers
+ * other than the {@link Layer#boot() boot} layer, where the module layer
+ * contains modules defined to the class loader, or any class loader reachable
+ * via parent delegation, are also located. For example, suppose there is a
+ * module layer where each module is defined to its own class loader (see {@link
+ * Layer#defineModulesWithManyLoaders defineModulesWithManyLoaders}). If the
+ * {@code load} method is invoked to locate providers using any of these class
+ * loaders for this layer then it will locate all of the providers in that
+ * layer, irrespective of their defining class loader.
+ *
+ *
In the case of unnamed modules then the service configuration files are
+ * located using the class loader's {@link ClassLoader#getResources(String)
+ * ClassLoader.getResources(String)} method. Any providers listed should be
+ * visible via the class loader specified to the {@code load} method. If a
+ * provider in a named module is listed then it is ignored - this is to avoid
+ * duplicates that would otherwise arise when a module has both a
+ * provides clause and a service configuration file in {@code
+ * META-INF/services} that lists the same provider.
+ *
+ *
Ordering
+ *
+ * Service loaders created to locate providers using a {@code ClassLoader}
+ * locate providers as follows:
+ *
+ * - Providers in named modules are located before providers on the
+ * class path (or more generally, unnamed modules).
+ *
+ * - When locating providers in named modules then the service loader
+ * will locate providers in modules defined to the class loader, then its
+ * parent class loader, its parent parent, and so on to the bootstrap class
+ * loader. If a {@code ClassLoader}, or any class loader in the parent
+ * delegation chain, defines modules in a custom module {@link Layer} then
+ * all providers in that layer are located, irrespective of their class
+ * loader. The ordering of modules defined to the same class loader, or the
+ * ordering of modules in a layer, is not defined.
+ *
+ * - If a named module declares more than one provider then the providers
+ * are located in the order that they appear in the {@code provides} table of
+ * the {@code Module} class file attribute ({@code module-info.class}).
+ *
+ * - When locating providers in unnamed modules then the ordering is
+ * based on the order that the class loader's {@link
+ * ClassLoader#getResources(String) ClassLoader.getResources(String)}
+ * method finds the service configuration files.
+ *
+ *
+ * Service loaders created to locate providers in a module {@link Layer}
+ * will first locate providers in the layer, before locating providers in
+ * parent layers. Traversal of parent layers is depth-first with each layer
+ * visited at most once. For example, suppose L0 is the boot layer, L1 and
+ * L2 are custom layers with L0 as their parent. Now suppose that L3 is
+ * created with L1 and L2 as the parents (in that order). Using a service
+ * loader to locate providers with L3 as the content will locate providers
+ * in the following order: L3, L1, L0, L2. The ordering of modules in a layer
+ * is not defined.
+ *
+ *
Selection and filtering
+ *
+ * Selecting a provider or filtering providers will usually involve invoking
+ * a provider method. Where selection or filtering based on the provider class is
+ * needed then it can be done using a {@link #stream() stream}. For example, the
+ * following collects the providers that have a specific annotation:
+ *
{@code
+ * Set providers = ServiceLoader.load(CodecSet.class)
+ * .stream()
+ * .filter(p -> p.type().isAnnotationPresent(Managed.class))
+ * .map(Provider::get)
+ * .collect(Collectors.toSet());
+ * }
+ *
+ * Security
+ *
* Service loaders always execute in the security context of the caller
- * of the iterator methods and may also be restricted by the security
+ * of the iterator or stream methods and may also be restricted by the security
* context of the caller that created the service loader.
* Trusted system code should typically invoke the methods in this class, and
* the methods of the iterators which they return, from within a privileged
* security context.
*
+ *
Concurrency
+ *
* Instances of this class are not safe for use by multiple concurrent
* threads.
*
- *
Unless otherwise specified, passing a null argument to any
+ *
Null handling
+ *
+ * Unless otherwise specified, passing a {@code null} argument to any
* method in this class will cause a {@link NullPointerException} to be thrown.
*
- *
Example
- * Suppose we have a service type com.example.CodecSet which is
+ *
Example
+ * Suppose we have a service type com.example.CodecSet which is
* intended to represent sets of encoder/decoder pairs for some protocol. In
* this case it is an abstract class with two abstract methods:
*
@@ -218,11 +340,12 @@ import jdk.internal.reflect.Reflection;
public final class ServiceLoader
implements Iterable
{
- private static final String PREFIX = "META-INF/services/";
-
// The class or interface representing the service being loaded
private final Class service;
+ // The class of the service type
+ private final String serviceName;
+
// The module Layer used to locate providers; null when locating
// providers using a class loader
private final Layer layer;
@@ -234,75 +357,86 @@ public final class ServiceLoader
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
- // Cached providers, in instantiation order
- private List providers = new ArrayList<>();
+ // The lazy-lookup iterator for iterator operations
+ private Iterator> lookupIterator1;
+ private final List instantiatedProviders = new ArrayList<>();
- // The class names of the cached providers, only used when locating
- // service providers via a class loader
- private Set providerNames = new HashSet<>();
+ // The lazy-lookup iterator for stream operations
+ private Iterator> lookupIterator2;
+ private final List> loadedProviders = new ArrayList<>();
+ private boolean loadedAllProviders; // true when all providers loaded
// Incremented when reload is called
private int reloadCount;
- // the service iterator when locating services via a module layer
- private LayerLookupIterator layerLookupIterator;
-
- // The module services iterator when locating services in modules
- // defined to a class loader
- private ModuleServicesIterator moduleServicesIterator;
-
- // The current lazy-lookup iterator for locating legacy provider on the
- // class path via a class loader
- private LazyClassPathIterator lazyClassPathIterator;
-
-
- /**
- * Clear this loader's provider cache so that all providers will be
- * reloaded.
- *
- * After invoking this method, subsequent invocations of the {@link
- * #iterator() iterator} method will lazily look up and instantiate
- * providers from scratch, just as is done by a newly-created loader.
- *
- *
This method is intended for use in situations in which new providers
- * can be installed into a running Java virtual machine.
- */
- public void reload() {
- providers.clear();
-
- assert layer == null || loader == null;
- if (layer != null) {
- layerLookupIterator = new LayerLookupIterator();
- } else {
- providerNames.clear();
- moduleServicesIterator = new ModuleServicesIterator();
- lazyClassPathIterator = new LazyClassPathIterator();
- }
-
- reloadCount++;
+ private static JavaLangAccess LANG_ACCESS;
+ private static JavaLangReflectModuleAccess JLRM_ACCESS;
+ static {
+ LANG_ACCESS = SharedSecrets.getJavaLangAccess();
+ JLRM_ACCESS = SharedSecrets.getJavaLangReflectModuleAccess();
}
+ /**
+ * Represents a service provider located by {@code ServiceLoader}.
+ *
+ *
When using a loader's {@link ServiceLoader#stream() stream()} method
+ * then the elements are of type {@code Provider}. This allows processing
+ * to select or filter on the provider class without instantiating the
+ * provider.
+ *
+ * @param The service type
+ * @since 9
+ */
+ public static interface Provider extends Supplier {
+ /**
+ * Returns the provider type. There is no guarantee that this type is
+ * accessible or that it has a public no-args constructor. The {@link
+ * #get() get()} method should be used to obtain the provider instance.
+ *
+ * When a module declares that the provider class is created by a
+ * provider factory then this method returns the return type of its
+ * public static "{@code provider()}" method.
+ *
+ * @return The provider type
+ */
+ Class extends S> type();
+
+ /**
+ * Returns an instance of the provider.
+ *
+ * @return An instance of the provider.
+ *
+ * @throws ServiceConfigurationError
+ * If the service provider cannot be instantiated, or in the
+ * case of a provider factory, the public static
+ * "{@code provider()}" method returns {@code null} or throws
+ * an error or exception. The {@code ServiceConfigurationError}
+ * will carry an appropriate cause where possible.
+ */
+ @Override S get();
+ }
/**
* Initializes a new instance of this class for locating service providers
* in a module Layer.
*
* @throws ServiceConfigurationError
- * If {@code svc} is not accessible to {@code caller} or that the
- * caller's module does not declare that it uses the service type.
+ * If {@code svc} is not accessible to {@code caller} or the caller
+ * module does not use the service type.
*/
private ServiceLoader(Class> caller, Layer layer, Class svc) {
-
- checkModule(caller.getModule(), svc);
+ Objects.requireNonNull(caller);
+ Objects.requireNonNull(layer);
+ Objects.requireNonNull(svc);
+ checkCaller(caller, svc);
this.service = svc;
+ this.serviceName = svc.getName();
this.layer = layer;
this.loader = null;
this.acc = (System.getSecurityManager() != null)
? AccessController.getContext()
: null;
-
- reload();
}
/**
@@ -310,23 +444,23 @@ public final class ServiceLoader
* via a class loader.
*
* @throws ServiceConfigurationError
- * If {@code svc} is not accessible to {@code caller} or that the
- * caller's module does not declare that it uses the service type.
+ * If {@code svc} is not accessible to {@code caller} or the caller
+ * module does not use the service type.
*/
- private ServiceLoader(Module callerModule, Class svc, ClassLoader cl) {
+ private ServiceLoader(Class> caller, Class svc, ClassLoader cl) {
+ Objects.requireNonNull(svc);
+
if (VM.isBooted()) {
-
- checkModule(callerModule, svc);
-
+ checkCaller(caller, svc);
if (cl == null) {
cl = ClassLoader.getSystemClassLoader();
}
-
} else {
// if we get here then it means that ServiceLoader is being used
// before the VM initialization has completed. At this point then
// only code in the java.base should be executing.
+ Module callerModule = caller.getModule();
Module base = Object.class.getModule();
Module svcModule = svc.getModule();
if (callerModule != base || svcModule != base) {
@@ -338,39 +472,55 @@ public final class ServiceLoader
}
this.service = svc;
+ this.serviceName = svc.getName();
this.layer = null;
this.loader = cl;
this.acc = (System.getSecurityManager() != null)
? AccessController.getContext()
: null;
-
- reload();
}
- private ServiceLoader(Class> caller, Class svc, ClassLoader cl) {
- this(caller.getModule(), svc, cl);
+ /**
+ * Initializes a new instance of this class for locating service providers
+ * via a class loader.
+ *
+ * @apiNote For use by ResourceBundle
+ *
+ * @throws ServiceConfigurationError
+ * If the caller module does not use the service type.
+ */
+ private ServiceLoader(Module callerModule, Class svc, ClassLoader cl) {
+ if (!callerModule.canUse(svc)) {
+ fail(svc, callerModule + " does not declare `uses`");
+ }
+
+ this.service = Objects.requireNonNull(svc);
+ this.serviceName = svc.getName();
+ this.layer = null;
+ this.loader = cl;
+ this.acc = (System.getSecurityManager() != null)
+ ? AccessController.getContext()
+ : null;
}
-
-
/**
* Checks that the given service type is accessible to types in the given
- * module, and check that the module declare that it uses the service type.
+ * module, and check that the module declare that it uses the service type. ??
*/
- private static void checkModule(Module module, Class> svc) {
+ private static void checkCaller(Class> caller, Class> svc) {
+ Module callerModule = caller.getModule();
- // Check that the service type is in a package that is
- // exported to the caller.
- if (!Reflection.verifyModuleAccess(module, svc)) {
- fail(svc, "not accessible to " + module);
+ // Check access to the service type
+ int mods = svc.getModifiers();
+ if (!Reflection.verifyMemberAccess(caller, svc, null, mods)) {
+ fail(svc, "service type not accessible to " + callerModule);
}
// If the caller is in a named module then it should "uses" the
// service type
- if (!module.canUse(svc)) {
- fail(svc, "use not declared in " + module);
+ if (!callerModule.canUse(svc)) {
+ fail(svc, callerModule + " does not declare `uses`");
}
-
}
private static void fail(Class> service, String msg, Throwable cause)
@@ -392,310 +542,422 @@ public final class ServiceLoader
fail(service, u + ":" + line + ": " + msg);
}
- // Parse a single line from the given configuration file, adding the name
- // on the line to the names list.
- //
- private int parseLine(Class> service, URL u, BufferedReader r, int lc,
- List names)
- throws IOException, ServiceConfigurationError
- {
- String ln = r.readLine();
- if (ln == null) {
- return -1;
- }
- int ci = ln.indexOf('#');
- if (ci >= 0) ln = ln.substring(0, ci);
- ln = ln.trim();
- int n = ln.length();
- if (n != 0) {
- if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
- fail(service, u, lc, "Illegal configuration-file syntax");
- int cp = ln.codePointAt(0);
- if (!Character.isJavaIdentifierStart(cp))
- fail(service, u, lc, "Illegal provider-class name: " + ln);
- for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
- cp = ln.codePointAt(i);
- if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
- fail(service, u, lc, "Illegal provider-class name: " + ln);
- }
- if (!providerNames.contains(ln) && !names.contains(ln))
- names.add(ln);
- }
- return lc + 1;
- }
-
/**
- * Parse the content of the given URL as a provider-configuration file.
- *
- * @param service
- * The service type for which providers are being sought;
- * used to construct error detail strings
- *
- * @param u
- * The URL naming the configuration file to be parsed
- *
- * @return A (possibly empty) iterator that will yield the provider-class
- * names in the given configuration file that are not yet members
- * of the returned set
+ * Uses Class.forName to load a provider class in a module.
*
* @throws ServiceConfigurationError
- * If an I/O error occurs while reading from the given URL, or
- * if a configuration-file format error is detected
- *
+ * If the class cannot be loaded
*/
- private Iterator parse(Class> service, URL u)
- throws ServiceConfigurationError
- {
- ArrayList names = new ArrayList<>();
- try {
- URLConnection uc = u.openConnection();
- uc.setUseCaches(false);
- try (InputStream in = uc.getInputStream();
- BufferedReader r
- = new BufferedReader(new InputStreamReader(in, "utf-8")))
- {
- int lc = 1;
- while ((lc = parseLine(service, u, r, lc, names)) >= 0);
+ private Class> loadProviderInModule(Module module, String cn) {
+ Class> clazz = null;
+ if (acc == null) {
+ try {
+ clazz = Class.forName(module, cn);
+ } catch (LinkageError e) {
+ fail(service, "Unable to load " + cn, e);
}
- } catch (IOException x) {
- fail(service, "Error accessing configuration file", x);
- }
- return names.iterator();
- }
-
- /**
- * Returns the {@code Constructor} to instantiate the service provider.
- * The constructor has its accessible flag set so that the access check
- * is suppressed when instantiating the provider. This is necessary
- * because newInstance is a caller sensitive method and ServiceLoader
- * is instantiating the service provider on behalf of the service
- * consumer.
- */
- private static Constructor> checkAndGetConstructor(Class> c)
- throws NoSuchMethodException, IllegalAccessException
- {
- Constructor> ctor = c.getConstructor();
-
- // check class and no-arg constructor are public
- int modifiers = ctor.getModifiers();
- if (!Modifier.isPublic(Reflection.getClassAccessFlags(c) & modifiers)) {
- String cn = c.getName();
- throw new IllegalAccessException(cn + " is not public");
- }
-
- // return Constructor to create the service implementation
- PrivilegedAction action = new PrivilegedAction() {
- public Void run() { ctor.setAccessible(true); return null; }
- };
- AccessController.doPrivileged(action);
- return ctor;
- }
-
- /**
- * Uses Class.forName to load a class in a module.
- */
- private static Class> loadClassInModule(Module module, String cn) {
- SecurityManager sm = System.getSecurityManager();
- if (sm == null) {
- return Class.forName(module, cn);
} else {
- PrivilegedAction> pa = () -> Class.forName(module, cn);
- return AccessController.doPrivileged(pa);
+ PrivilegedExceptionAction> pa = () -> Class.forName(module, cn);
+ try {
+ clazz = AccessController.doPrivileged(pa);
+ } catch (PrivilegedActionException pae) {
+ Throwable x = pae.getCause();
+ fail(service, "Unable to load " + cn, x);
+ return null;
+ }
}
+ if (clazz == null)
+ fail(service, "Provider " + cn + " not found");
+ return clazz;
}
/**
- * An Iterator that runs the next and hasNext methods with permissions
- * restricted by the {@code AccessControlContext} obtained when the
- * ServiceLoader was created.
+ * A Provider implementation that supports invoking, with reduced
+ * permissions, the static factory to obtain the provider or the
+ * provider's no-arg constructor.
*/
- private abstract class RestrictedIterator
- implements Iterator
- {
- /**
- * Returns {@code true} if the iteration has more elements.
- */
- abstract boolean hasNextService();
+ private final static class ProviderImpl implements Provider {
+ final Class service;
+ final AccessControlContext acc;
+
+ final Method factoryMethod; // factory method or null
+ final Class extends S> type;
+ final Constructor extends S> ctor; // public no-args constructor or null
/**
- * Returns the next element in the iteration
+ * Creates a Provider.
+ *
+ * @param service
+ * The service type
+ * @param clazz
+ * The provider (or provider factory) class
+ * @param acc
+ * The access control context when running with security manager
+ *
+ * @throws ServiceConfigurationError
+ * If the class is not public; If the class defines a public
+ * static provider() method with a return type that is assignable
+ * to the service type or the class is not a provider class with
+ * a public no-args constructor.
*/
- abstract S nextService();
+ @SuppressWarnings("unchecked")
+ ProviderImpl(Class> service, Class> clazz, AccessControlContext acc) {
+ this.service = (Class) service;
+ this.acc = acc;
- public final boolean hasNext() {
- if (acc == null) {
- return hasNextService();
+ int mods = clazz.getModifiers();
+ if (!Modifier.isPublic(mods)) {
+ fail(service, clazz + " is not public");
+ }
+
+ // if the class is in an explicit module then see if it is
+ // a provider factory class
+ Method factoryMethod = null;
+ if (inExplicitModule(clazz)) {
+ factoryMethod = findStaticProviderMethod(clazz);
+ if (factoryMethod != null) {
+ Class> returnType = factoryMethod.getReturnType();
+ if (!service.isAssignableFrom(returnType)) {
+ fail(service, factoryMethod + " return type not a subtype");
+ }
+ }
+ }
+ this.factoryMethod = factoryMethod;
+
+ if (factoryMethod == null) {
+ // no factory method so must have a public no-args constructor
+ if (!service.isAssignableFrom(clazz)) {
+ fail(service, clazz.getName() + " not a subtype");
+ }
+ this.type = (Class extends S>) clazz;
+ this.ctor = (Constructor extends S>) getConstructor(clazz);
} else {
- PrivilegedAction action = new PrivilegedAction() {
- public Boolean run() { return hasNextService(); }
- };
- return AccessController.doPrivileged(action, acc);
+ this.type = (Class extends S>) factoryMethod.getReturnType();
+ this.ctor = null;
}
}
- public final S next() {
- if (acc == null) {
- return nextService();
+ @Override
+ public Class extends S> type() {
+ return type;
+ }
+
+ @Override
+ public S get() {
+ if (factoryMethod != null) {
+ return invokeFactoryMethod();
} else {
- PrivilegedAction action = new PrivilegedAction() {
- public S run() { return nextService(); }
- };
- return AccessController.doPrivileged(action, acc);
+ return newInstance();
}
}
+
+ /**
+ * Returns {@code true} if the provider is in an explicit module
+ */
+ private boolean inExplicitModule(Class> clazz) {
+ Module module = clazz.getModule();
+ return module.isNamed() && !module.getDescriptor().isAutomatic();
+ }
+
+ /**
+ * Returns the public static provider method if found.
+ *
+ * @throws ServiceConfigurationError if there is an error finding the
+ * provider method
+ */
+ private Method findStaticProviderMethod(Class> clazz) {
+ Method method = null;
+ try {
+ method = LANG_ACCESS.getMethodOrNull(clazz, "provider");
+ } catch (Throwable x) {
+ fail(service, "Unable to get public provider() method", x);
+ }
+ if (method != null) {
+ int mods = method.getModifiers();
+ if (Modifier.isStatic(mods)) {
+ assert Modifier.isPublic(mods);
+ Method m = method;
+ PrivilegedAction pa = () -> {
+ m.setAccessible(true);
+ return null;
+ };
+ AccessController.doPrivileged(pa);
+ return method;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the public no-arg constructor of a class.
+ *
+ * @throws ServiceConfigurationError if the class does not have
+ * public no-arg constructor
+ */
+ private Constructor> getConstructor(Class> clazz) {
+ PrivilegedExceptionAction> pa
+ = new PrivilegedExceptionAction<>() {
+ @Override
+ public Constructor> run() throws Exception {
+ Constructor> ctor = clazz.getConstructor();
+ if (inExplicitModule(clazz))
+ ctor.setAccessible(true);
+ return ctor;
+ }
+ };
+ Constructor> ctor = null;
+ try {
+ ctor = AccessController.doPrivileged(pa);
+ } catch (Throwable x) {
+ if (x instanceof PrivilegedActionException)
+ x = x.getCause();
+ String cn = clazz.getName();
+ fail(service, cn + " Unable to get public no-arg constructor", x);
+ }
+ return ctor;
+ }
+
+ /**
+ * Invokes the provider's "provider" method to instantiate a provider.
+ * When running with a security manager then the method runs with
+ * permissions that are restricted by the security context of whatever
+ * created this loader.
+ */
+ private S invokeFactoryMethod() {
+ Object result = null;
+ Throwable exc = null;
+ if (acc == null) {
+ try {
+ result = factoryMethod.invoke(null);
+ } catch (Throwable x) {
+ exc = x;
+ }
+ } else {
+ PrivilegedExceptionAction> pa = new PrivilegedExceptionAction<>() {
+ @Override
+ public Object run() throws Exception {
+ return factoryMethod.invoke(null);
+ }
+ };
+ // invoke factory method with permissions restricted by acc
+ try {
+ result = AccessController.doPrivileged(pa, acc);
+ } catch (PrivilegedActionException pae) {
+ exc = pae.getCause();
+ }
+ }
+ if (exc != null) {
+ if (exc instanceof InvocationTargetException)
+ exc = exc.getCause();
+ fail(service, factoryMethod + " failed", exc);
+ }
+ if (result == null) {
+ fail(service, factoryMethod + " returned null");
+ }
+ @SuppressWarnings("unchecked")
+ S p = (S) result;
+ return p;
+ }
+
+ /**
+ * Invokes Constructor::newInstance to instantiate a provider. When running
+ * with a security manager then the constructor runs with permissions that
+ * are restricted by the security context of whatever created this loader.
+ */
+ private S newInstance() {
+ S p = null;
+ Throwable exc = null;
+ if (acc == null) {
+ try {
+ p = ctor.newInstance();
+ } catch (Throwable x) {
+ exc = x;
+ }
+ } else {
+ PrivilegedExceptionAction pa = new PrivilegedExceptionAction<>() {
+ @Override
+ public S run() throws Exception {
+ return ctor.newInstance();
+ }
+ };
+ // invoke constructor with permissions restricted by acc
+ try {
+ p = AccessController.doPrivileged(pa, acc);
+ } catch (PrivilegedActionException pae) {
+ exc = pae.getCause();
+ }
+ }
+ if (exc != null) {
+ if (exc instanceof InvocationTargetException)
+ exc = exc.getCause();
+ String cn = ctor.getDeclaringClass().getName();
+ fail(service,
+ "Provider " + cn + " could not be instantiated", exc);
+ }
+ return p;
+ }
+
+ // For now, equals/hashCode uses the access control context to ensure
+ // that two Providers created with different contexts are not equal
+ // when running with a security manager.
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, acc);
+ }
+
+ @Override
+ public boolean equals(Object ob) {
+ if (!(ob instanceof ProviderImpl))
+ return false;
+ @SuppressWarnings("unchecked")
+ ProviderImpl> that = (ProviderImpl>)ob;
+ return this.type == that.type
+ && Objects.equals(this.acc, that.acc);
+ }
}
/**
* Implements lazy service provider lookup of service providers that
- * are provided by modules in a module Layer.
+ * are provided by modules in a module Layer (or parent layers)
*/
- private class LayerLookupIterator
- extends RestrictedIterator
+ private final class LayerLookupIterator
+ implements Iterator>
{
- final String serviceName;
- Layer currentLayer;
+ Deque stack = new ArrayDeque<>();
+ Set visited = new HashSet<>();
Iterator iterator;
- ServiceProvider nextProvider;
+ ServiceProvider next; // next provider to load
LayerLookupIterator() {
- serviceName = service.getName();
- currentLayer = layer;
-
- // need to get us started
- iterator = providers(currentLayer, serviceName);
+ visited.add(layer);
+ stack.push(layer);
}
- Iterator providers(Layer layer, String service) {
- ServicesCatalog catalog = SharedSecrets
- .getJavaLangReflectModuleAccess()
- .getServicesCatalog(layer);
-
+ private Iterator providers(Layer layer) {
+ ServicesCatalog catalog = JLRM_ACCESS.getServicesCatalog(layer);
return catalog.findServices(serviceName).iterator();
}
@Override
- boolean hasNextService() {
-
+ public boolean hasNext() {
// already have the next provider cached
- if (nextProvider != null)
+ if (next != null)
return true;
while (true) {
- // next provider
+ // next provider (or provider factory)
if (iterator != null && iterator.hasNext()) {
- nextProvider = iterator.next();
+ next = iterator.next();
return true;
}
- // next layer
- Layer parent = currentLayer.parent().orElse(null);
- if (parent == null)
+ // next layer (DFS order)
+ if (stack.isEmpty())
return false;
- currentLayer = parent;
- iterator = providers(currentLayer, serviceName);
+ Layer layer = stack.pop();
+ List parents = layer.parents();
+ for (int i = parents.size() - 1; i >= 0; i--) {
+ Layer parent = parents.get(i);
+ if (!visited.contains(parent)) {
+ visited.add(parent);
+ stack.push(parent);
+ }
+ }
+ iterator = providers(layer);
}
}
@Override
- S nextService() {
- if (!hasNextService())
+ public Provider next() {
+ if (!hasNext())
throw new NoSuchElementException();
- ServiceProvider provider = nextProvider;
- nextProvider = null;
+ // take next provider
+ ServiceProvider provider = next;
+ next = null;
+ // attempt to load provider
Module module = provider.module();
String cn = provider.providerName();
-
- // attempt to load the provider
- Class> c = loadClassInModule(module, cn);
- if (c == null)
- fail(service, "Provider " + cn + " not found");
- if (!service.isAssignableFrom(c))
- fail(service, "Provider " + cn + " not a subtype");
-
- // instantiate the provider
- S p = null;
- try {
- Constructor> ctor = checkAndGetConstructor(c);
- p = service.cast(ctor.newInstance());
- } catch (Throwable x) {
- if (x instanceof InvocationTargetException)
- x = x.getCause();
- fail(service,
- "Provider " + cn + " could not be instantiated", x);
- }
-
- // add to cached provider list
- providers.add(p);
-
- return p;
+ Class> clazz = loadProviderInModule(module, cn);
+ return new ProviderImpl(service, clazz, acc);
}
}
/**
* Implements lazy service provider lookup of service providers that
- * are provided by modules defined to a class loader.
+ * are provided by modules defined to a class loader or to modules in
+ * layers with a module defined to the class loader.
*/
- private class ModuleServicesIterator
- extends RestrictedIterator
+ private final class ModuleServicesLookupIterator
+ implements Iterator>
{
- final JavaLangAccess langAccess = SharedSecrets.getJavaLangAccess();
-
ClassLoader currentLoader;
Iterator iterator;
- ServiceProvider nextProvider;
+ ServiceProvider next; // next provider to load
- ModuleServicesIterator() {
+ ModuleServicesLookupIterator() {
this.currentLoader = loader;
this.iterator = iteratorFor(loader);
}
+ /**
+ * Returns iterator to iterate over the implementations of {@code
+ * service} in the given layer.
+ */
+ private List providers(Layer layer) {
+ ServicesCatalog catalog = JLRM_ACCESS.getServicesCatalog(layer);
+ return catalog.findServices(serviceName);
+ }
+
/**
* Returns an iterator to iterate over the implementations of {@code
- * service} in modules defined to the given class loader.
+ * service} in modules defined to the given class loader or in custom
+ * layers with a module defined to this class loader.
*/
private Iterator iteratorFor(ClassLoader loader) {
- // if the class loader is in a loader pool then return an Iterator
- // that iterates over all service providers in the pool that provide
- // an implementation of the service
- if (currentLoader instanceof Loader) {
- LoaderPool pool = ((Loader) loader).pool();
- if (pool != null) {
- return pool.loaders()
- .map(l -> langAccess.getServicesCatalog(l))
- .filter(sc -> sc != null)
- .map(sc -> sc.findServices(service.getName()))
- .flatMap(Set::stream)
- .iterator();
- }
- }
-
+ // modules defined to this class loader
ServicesCatalog catalog;
- if (currentLoader == null) {
+ if (loader == null) {
catalog = BootLoader.getServicesCatalog();
} else {
- catalog = langAccess.getServicesCatalog(currentLoader);
+ catalog = ServicesCatalog.getServicesCatalogOrNull(loader);
}
+ Stream stream1;
if (catalog == null) {
- return Collections.emptyIterator();
+ stream1 = Stream.empty();
} else {
- return catalog.findServices(service.getName()).iterator();
+ stream1 = catalog.findServices(serviceName).stream();
}
+
+ // modules in custom layers that define modules to the class loader
+ Stream stream2;
+ if (loader == null) {
+ stream2 = Stream.empty();
+ } else {
+ Layer bootLayer = Layer.boot();
+ stream2 = JLRM_ACCESS.layers(loader)
+ .filter(l -> (l != bootLayer))
+ .map(l -> providers(l))
+ .flatMap(List::stream);
+ }
+
+ return Stream.concat(stream1, stream2).iterator();
}
@Override
- boolean hasNextService() {
+ public boolean hasNext() {
// already have the next provider cached
- if (nextProvider != null)
+ if (next != null)
return true;
while (true) {
if (iterator.hasNext()) {
- nextProvider = iterator.next();
+ next = iterator.next();
return true;
}
@@ -710,138 +972,220 @@ public final class ServiceLoader
}
@Override
- S nextService() {
- if (!hasNextService())
+ public Provider next() {
+ if (!hasNext())
throw new NoSuchElementException();
- ServiceProvider provider = nextProvider;
- nextProvider = null;
+ // take next provider
+ ServiceProvider provider = next;
+ next = null;
- // attempt to load the provider
+ // attempt to load provider
Module module = provider.module();
String cn = provider.providerName();
-
- Class> c = loadClassInModule(module, cn);
- if (c == null) {
- fail(service,
- "Provider " + cn + " not found in " + module.getName());
- }
- if (!service.isAssignableFrom(c)) {
- fail(service, "Provider " + cn + " not a subtype");
- }
-
- // instantiate the provider
- S p = null;
- try {
- Constructor> ctor = checkAndGetConstructor(c);
- p = service.cast(ctor.newInstance());
- } catch (Throwable x) {
- if (x instanceof InvocationTargetException)
- x = x.getCause();
- fail(service,
- "Provider " + cn + " could not be instantiated", x);
- }
-
- // add to provider list
- providers.add(p);
-
- // record the class name of the service provider, this is
- // needed for cases where there a module has both a "uses"
- // and a services configuration file listing the same
- // provider
- providerNames.add(cn);
-
- return p;
+ Class> clazz = loadProviderInModule(module, cn);
+ return new ProviderImpl(service, clazz, acc);
}
}
/**
- * Implements lazy service provider lookup where the service providers
- * are configured via service configuration files.
+ * Implements lazy service provider lookup where the service providers are
+ * configured via service configuration files. Service providers in named
+ * modules are silently ignored by this lookup iterator.
*/
- private class LazyClassPathIterator
- extends RestrictedIterator
+ private final class LazyClassPathLookupIterator
+ implements Iterator>
{
+ static final String PREFIX = "META-INF/services/";
+
Enumeration configs;
Iterator pending;
- String nextName;
+ Class> nextClass;
+ String nextErrorMessage; // when hasNext fails with CNFE
- @Override
- boolean hasNextService() {
- if (nextName != null) {
+ LazyClassPathLookupIterator() { }
+
+ /**
+ * Parse a single line from the given configuration file, adding the
+ * name on the line to the names list.
+ */
+ private int parseLine(URL u, BufferedReader r, int lc, Set names)
+ throws IOException
+ {
+ String ln = r.readLine();
+ if (ln == null) {
+ return -1;
+ }
+ int ci = ln.indexOf('#');
+ if (ci >= 0) ln = ln.substring(0, ci);
+ ln = ln.trim();
+ int n = ln.length();
+ if (n != 0) {
+ if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
+ fail(service, u, lc, "Illegal configuration-file syntax");
+ int cp = ln.codePointAt(0);
+ if (!Character.isJavaIdentifierStart(cp))
+ fail(service, u, lc, "Illegal provider-class name: " + ln);
+ int start = Character.charCount(cp);
+ for (int i = start; i < n; i += Character.charCount(cp)) {
+ cp = ln.codePointAt(i);
+ if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
+ fail(service, u, lc, "Illegal provider-class name: " + ln);
+ }
+ names.add(ln);
+ }
+ return lc + 1;
+ }
+
+ /**
+ * Parse the content of the given URL as a provider-configuration file.
+ */
+ private Iterator parse(URL u) {
+ Set names = new LinkedHashSet<>(); // preserve insertion order
+ try {
+ URLConnection uc = u.openConnection();
+ uc.setUseCaches(false);
+ try (InputStream in = uc.getInputStream();
+ BufferedReader r
+ = new BufferedReader(new InputStreamReader(in, "utf-8")))
+ {
+ int lc = 1;
+ while ((lc = parseLine(u, r, lc, names)) >= 0);
+ }
+ } catch (IOException x) {
+ fail(service, "Error accessing configuration file", x);
+ }
+ return names.iterator();
+ }
+
+ private boolean hasNextService() {
+ if (nextClass != null || nextErrorMessage != null) {
return true;
}
- if (configs == null) {
+
+ Class> clazz = null;
+ do {
+ if (configs == null) {
+ try {
+ String fullName = PREFIX + service.getName();
+ if (loader == null)
+ configs = ClassLoader.getSystemResources(fullName);
+ else
+ configs = loader.getResources(fullName);
+ } catch (IOException x) {
+ fail(service, "Error locating configuration files", x);
+ }
+ }
+ while ((pending == null) || !pending.hasNext()) {
+ if (!configs.hasMoreElements()) {
+ return false;
+ }
+ pending = parse(configs.nextElement());
+ }
+ String cn = pending.next();
try {
- String fullName = PREFIX + service.getName();
- if (loader == null)
- configs = ClassLoader.getSystemResources(fullName);
- else
- configs = loader.getResources(fullName);
- } catch (IOException x) {
- fail(service, "Error locating configuration files", x);
+ clazz = Class.forName(cn, false, loader);
+ } catch (ClassNotFoundException x) {
+ // don't throw SCE here to long standing behavior
+ nextErrorMessage = "Provider " + cn + " not found";
+ return true;
}
- }
- while ((pending == null) || !pending.hasNext()) {
- if (!configs.hasMoreElements()) {
- return false;
- }
- pending = parse(service, configs.nextElement());
- }
- nextName = pending.next();
+
+ } while (clazz.getModule().isNamed()); // ignore if in named module
+
+ nextClass = clazz;
return true;
}
- @Override
- S nextService() {
+ private Provider nextService() {
if (!hasNextService())
throw new NoSuchElementException();
- String cn = nextName;
- nextName = null;
- Class> c = null;
- try {
- c = Class.forName(cn, false, loader);
- } catch (ClassNotFoundException x) {
- fail(service,
- "Provider " + cn + " not found");
+
+ // throw any SCE with error recorded by hasNext
+ if (nextErrorMessage != null) {
+ String msg = nextErrorMessage;
+ nextErrorMessage = null;
+ fail(service, msg);
}
- if (!service.isAssignableFrom(c)) {
- fail(service,
- "Provider " + cn + " not a subtype");
+
+ // return next provider
+ Class> clazz = nextClass;
+ nextClass = null;
+ return new ProviderImpl(service, clazz, acc);
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (acc == null) {
+ return hasNextService();
+ } else {
+ PrivilegedAction action = new PrivilegedAction<>() {
+ public Boolean run() { return hasNextService(); }
+ };
+ return AccessController.doPrivileged(action, acc);
}
- S p = null;
- try {
- @SuppressWarnings("deprecation")
- Object tmp = c.newInstance();
- p = service.cast(tmp);
- } catch (Throwable x) {
- fail(service,
- "Provider " + cn + " could not be instantiated",
- x);
+ }
+
+ @Override
+ public Provider next() {
+ if (acc == null) {
+ return nextService();
+ } else {
+ PrivilegedAction> action = new PrivilegedAction<>() {
+ public Provider run() { return nextService(); }
+ };
+ return AccessController.doPrivileged(action, acc);
}
- providers.add(p);
- providerNames.add(cn);
- return p;
}
}
/**
- * Lazily loads the available providers of this loader's service.
+ * Returns a new lookup iterator.
+ */
+ private Iterator> newLookupIterator() {
+ assert layer == null || loader == null;
+ if (layer != null) {
+ return new LayerLookupIterator<>();
+ } else {
+ Iterator> first = new ModuleServicesLookupIterator<>();
+ Iterator> second = new LazyClassPathLookupIterator<>();
+ return new Iterator>() {
+ @Override
+ public boolean hasNext() {
+ return (first.hasNext() || second.hasNext());
+ }
+ @Override
+ public Provider next() {
+ if (first.hasNext()) {
+ return first.next();
+ } else if (second.hasNext()) {
+ return second.next();
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+ };
+ }
+ }
+
+ /**
+ * Lazily load and instantiate the available providers of this loader's
+ * service.
*
* The iterator returned by this method first yields all of the
- * elements of the provider cache, in instantiation order. It then lazily
- * loads and instantiates any remaining providers, adding each one to the
- * cache in turn.
+ * elements of the provider cache, in the order that they were loaded.
+ * It then lazily loads and instantiates any remaining providers,
+ * adding each one to the cache in turn.
*
*
To achieve laziness the actual work of locating and instantiating
* providers must be done by the iterator itself. Its {@link
* java.util.Iterator#hasNext hasNext} and {@link java.util.Iterator#next
* next} methods can therefore throw a {@link ServiceConfigurationError}
- * if a provider class cannot be loaded, doesn't have the appropriate
- * constructor, can't be assigned to the service type or if any other kind
- * of exception or error is thrown as the next provider is located and
- * instantiated. To write robust code it is only necessary to catch {@link
- * ServiceConfigurationError} when using a service iterator.
+ * if a provider class cannot be loaded, doesn't have an appropriate static
+ * factory method or constructor, can't be assigned to the service type or
+ * if any other kind of exception or error is thrown as the next provider
+ * is located and instantiated. To write robust code it is only necessary
+ * to catch {@link ServiceConfigurationError} when using a service iterator.
*
*
If such an error is thrown then subsequent invocations of the
* iterator will make a best effort to locate and instantiate the next
@@ -856,7 +1200,7 @@ public final class ServiceLoader
* preferable to throw an error rather than try to recover or, even worse,
* fail silently.
*
- *
If this loader's provider cache is cleared by invoking the {@link
+ *
If this loader's provider caches are cleared by invoking the {@link
* #reload() reload} method then existing iterators for this service
* loader should be discarded.
* The {@link java.util.Iterator#hasNext() hasNext} and {@link
@@ -868,16 +1212,16 @@ public final class ServiceLoader
* Invoking its {@link java.util.Iterator#remove() remove} method will
* cause an {@link UnsupportedOperationException} to be thrown.
*
- * @implNote When adding providers to the cache, the {@link #iterator
- * Iterator} processes resources in the order that the {@link
- * java.lang.ClassLoader#getResources(java.lang.String)
- * ClassLoader.getResources(String)} method finds the service configuration
- * files.
- *
* @return An iterator that lazily loads providers for this loader's
* service
*/
public Iterator iterator() {
+
+ // create lookup iterator if needed
+ if (lookupIterator1 == null) {
+ lookupIterator1 = newLookupIterator();
+ }
+
return new Iterator() {
// record reload count
@@ -895,34 +1239,23 @@ public final class ServiceLoader
throw new ConcurrentModificationException();
}
+ @Override
public boolean hasNext() {
checkReloadCount();
- if (index < providers.size())
+ if (index < instantiatedProviders.size())
return true;
-
- if (layerLookupIterator != null) {
- return layerLookupIterator.hasNext();
- } else {
- return moduleServicesIterator.hasNext() ||
- lazyClassPathIterator.hasNext();
- }
+ return lookupIterator1.hasNext();
}
+ @Override
public S next() {
checkReloadCount();
S next;
- if (index < providers.size()) {
- next = providers.get(index);
+ if (index < instantiatedProviders.size()) {
+ next = instantiatedProviders.get(index);
} else {
- if (layerLookupIterator != null) {
- next = layerLookupIterator.next();
- } else {
- if (moduleServicesIterator.hasNext()) {
- next = moduleServicesIterator.next();
- } else {
- next = lazyClassPathIterator.next();
- }
- }
+ next = lookupIterator1.next().get();
+ instantiatedProviders.add(next);
}
index++;
return next;
@@ -931,6 +1264,108 @@ public final class ServiceLoader
};
}
+ /**
+ * Returns a stream that lazily loads the available providers of this
+ * loader's service. The stream elements are of type {@link Provider
+ * Provider}, the {@code Provider}'s {@link Provider#get() get} method
+ * must be invoked to get or instantiate the provider.
+ *
+ *
When processing the stream then providers that were previously
+ * loaded by stream operations are processed first, in load order. It then
+ * lazily loads any remaining providers. If a provider class cannot be
+ * loaded, can't be assigned to the service type, or some other error is
+ * thrown when locating the provider then it is wrapped with a {@code
+ * ServiceConfigurationError} and thrown by whatever method caused the
+ * provider to be loaded.
+ *
+ * If this loader's provider caches are cleared by invoking the {@link
+ * #reload() reload} method then existing streams for this service
+ * loader should be discarded.
+ *
+ * The following examples demonstrate usage. The first example
+ * creates a stream of providers, the second example is the same except
+ * that it sorts the providers by provider class name (and so locate all
+ * providers).
+ *
{@code
+ * Stream providers = ServiceLoader.load(CodecSet.class)
+ * .stream()
+ * .map(Provider::get);
+ *
+ * Stream providers = ServiceLoader.load(CodecSet.class)
+ * .stream()
+ * .sorted(Comparator.comparing(p -> p.type().getName()))
+ * .map(Provider::get);
+ * }
+ *
+ * @return A stream that lazily loads providers for this loader's service
+ *
+ * @since 9
+ */
+ public Stream> stream() {
+ // use cached providers as the source when all providers loaded
+ if (loadedAllProviders) {
+ return loadedProviders.stream();
+ }
+
+ // create lookup iterator if needed
+ if (lookupIterator2 == null) {
+ lookupIterator2 = newLookupIterator();
+ }
+
+ // use lookup iterator and cached providers as source
+ Spliterator> s = new ProviderSpliterator<>(lookupIterator2);
+ return StreamSupport.stream(s, false);
+ }
+
+ private class ProviderSpliterator implements Spliterator> {
+ final int expectedReloadCount = ServiceLoader.this.reloadCount;
+ final Iterator> iterator;
+ int index;
+
+ ProviderSpliterator(Iterator> iterator) {
+ this.iterator = iterator;
+ }
+
+ @Override
+ public Spliterator> trySplit() {
+ return null;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean tryAdvance(Consumer super Provider> action) {
+ if (ServiceLoader.this.reloadCount != expectedReloadCount)
+ throw new ConcurrentModificationException();
+ Provider next = null;
+ if (index < loadedProviders.size()) {
+ next = (Provider) loadedProviders.get(index++);
+ } else if (iterator.hasNext()) {
+ next = iterator.next();
+ } else {
+ loadedAllProviders = true;
+ }
+ if (next != null) {
+ action.accept(next);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int characteristics() {
+ // not IMMUTABLE as structural interference possible
+ // not NOTNULL so that the characteristics are a subset of the
+ // characteristics when all Providers have been located.
+ return Spliterator.ORDERED;
+ }
+
+ @Override
+ public long estimateSize() {
+ return Long.MAX_VALUE;
+ }
+ }
+
/**
* Creates a new service loader for the given service type, class
* loader, and caller.
@@ -977,7 +1412,7 @@ public final class ServiceLoader
*
* @throws ServiceConfigurationError
* if the service type is not accessible to the caller or the
- * caller is in a named module and its module descriptor does
+ * caller is in an explicit module and its module descriptor does
* not declare that it uses {@code service}
*/
@CallerSensitive
@@ -993,15 +1428,23 @@ public final class ServiceLoader
* context class loader}.
*
* An invocation of this convenience method of the form
- *
- *
- * ServiceLoader.load(service)
+ * {@code
+ * ServiceLoader.load(service)
+ * }
*
* is equivalent to
*
- *
- * ServiceLoader.load(service,
- * Thread.currentThread().getContextClassLoader())
+ * {@code
+ * ServiceLoader.load(service, Thread.currentThread().getContextClassLoader())
+ * }
+ *
+ * @apiNote Service loader objects obtained with this method should not be
+ * cached VM-wide. For example, different applications in the same VM may
+ * have different thread context class loaders. A lookup by one application
+ * may locate a service provider that is only visible via its thread
+ * context class loader and so is not suitable to be located by the other
+ * application. Memory leaks can also arise. A thread local may be suited
+ * to some applications.
*
* @param the class of the service type
*
@@ -1012,7 +1455,7 @@ public final class ServiceLoader
*
* @throws ServiceConfigurationError
* if the service type is not accessible to the caller or the
- * caller is in a named module and its module descriptor does
+ * caller is in an explicit module and its module descriptor does
* not declare that it uses {@code service}
*/
@CallerSensitive
@@ -1027,9 +1470,9 @@ public final class ServiceLoader
*
* This convenience method is equivalent to:
*
- *
- * ServiceLoader.load(service, ClassLoader.getPlatformClassLoader())
- *
+ * {@code
+ * ServiceLoader.load(service, ClassLoader.getPlatformClassLoader())
+ * }
*
* This method is intended for use when only installed providers are
* desired. The resulting service will only find and load providers that
@@ -1045,18 +1488,13 @@ public final class ServiceLoader
*
* @throws ServiceConfigurationError
* if the service type is not accessible to the caller or the
- * caller is in a named module and its module descriptor does
+ * caller is in an explicit module and its module descriptor does
* not declare that it uses {@code service}
*/
@CallerSensitive
public static ServiceLoader loadInstalled(Class service) {
- ClassLoader cl = ClassLoader.getSystemClassLoader();
- ClassLoader prev = null;
- while (cl != null) {
- prev = cl;
- cl = cl.getParent();
- }
- return new ServiceLoader<>(Reflection.getCallerClass(), service, prev);
+ ClassLoader cl = ClassLoader.getPlatformClassLoader();
+ return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}
/**
@@ -1080,16 +1518,71 @@ public final class ServiceLoader
*
* @throws ServiceConfigurationError
* if the service type is not accessible to the caller or the
- * caller is in a named module and its module descriptor does
+ * caller is in an explicit module and its module descriptor does
* not declare that it uses {@code service}
*
* @since 9
*/
@CallerSensitive
public static ServiceLoader load(Layer layer, Class service) {
- return new ServiceLoader<>(Reflection.getCallerClass(),
- Objects.requireNonNull(layer),
- Objects.requireNonNull(service));
+ return new ServiceLoader<>(Reflection.getCallerClass(), layer, service);
+ }
+
+ /**
+ * Load the first available provider of this loader's service. This
+ * convenience method is equivalent to invoking the {@link #iterator()
+ * iterator()} method and obtaining the first element. It therefore
+ * returns the first element from the provider cache if possible, it
+ * otherwise attempts to load and instantiate the first provider.
+ *
+ *
The following example loads the first available provider. If there
+ * are no providers deployed then it uses a default implementation.
+ *
{@code
+ * CodecSet provider =
+ * ServiceLoader.load(CodecSet.class).findFirst().orElse(DEFAULT_CODECSET);
+ * }
+ * @return The first provider or empty {@code Optional} if no providers
+ * are located
+ *
+ * @throws ServiceConfigurationError
+ * If a provider class cannot be loaded, doesn't have the
+ * appropriate static factory method or constructor, can't be
+ * assigned to the service type, or if any other kind of exception
+ * or error is thrown when locating or instantiating the provider.
+ *
+ * @since 9
+ */
+ public Optional findFirst() {
+ Iterator iterator = iterator();
+ if (iterator.hasNext()) {
+ return Optional.of(iterator.next());
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * Clear this loader's provider cache so that all providers will be
+ * reloaded.
+ *
+ * After invoking this method, subsequent invocations of the {@link
+ * #iterator() iterator} or {@link #stream() stream} methods will lazily
+ * look up providers (and instantiate in the case of {@code iterator})
+ * from scratch, just as is done by a newly-created loader.
+ *
+ *
This method is intended for use in situations in which new providers
+ * can be installed into a running Java virtual machine.
+ */
+ public void reload() {
+ lookupIterator1 = null;
+ instantiatedProviders.clear();
+
+ lookupIterator2 = null;
+ loadedProviders.clear();
+ loadedAllProviders = false;
+
+ // increment count to allow CME be thrown
+ reloadCount++;
}
/**
diff --git a/jdk/src/java.base/share/classes/java/util/spi/AbstractResourceBundleProvider.java b/jdk/src/java.base/share/classes/java/util/spi/AbstractResourceBundleProvider.java
index ba4cbdca200..06b6c266c89 100644
--- a/jdk/src/java.base/share/classes/java/util/spi/AbstractResourceBundleProvider.java
+++ b/jdk/src/java.base/share/classes/java/util/spi/AbstractResourceBundleProvider.java
@@ -25,37 +25,47 @@
package java.util.spi;
+import jdk.internal.misc.JavaUtilResourceBundleAccess;
+import jdk.internal.misc.SharedSecrets;
+
import java.io.IOException;
+import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.reflect.Module;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Locale;
+import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
-import sun.util.locale.provider.ResourceBundleProviderSupport;
import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION;
-
/**
- * {@code AbstractResourceBundleProvider} is an abstract class for helping
- * implement the {@link ResourceBundleProvider} interface.
+ * {@code AbstractResourceBundleProvider} is an abstract class that provides
+ * the basic support for a provider implementation class for
+ * {@link ResourceBundleProvider}.
*
*
- * Resource bundles can be packaged in a named module separated from
- * the caller module loading the resource bundle, i.e. the module
- * calling {@link ResourceBundle#getBundle(String)}. For the caller module
- * to load a resource bundle "{@code com.example.app.MyResources}"
- * from another module and a service interface named
- * "{@code com.example.app.MyResourcesProvider}",
- * the bundle provider module can provide the implementation class
+ * Resource bundles can be packaged in one or more
+ * named modules, bundle modules. The consumer of the
+ * resource bundle is the one calling {@link ResourceBundle#getBundle(String)}.
+ * In order for the consumer module to load a resource bundle
+ * "{@code com.example.app.MyResources}" provided by another module,
+ * it will use the {@linkplain java.util.ServiceLoader service loader}
+ * mechanism. A service interface named "{@code com.example.app.MyResourcesProvider}"
+ * must be defined and a bundle provider module will provide an
+ * implementation class of "{@code com.example.app.MyResourcesProvider}"
* as follows:
*
*
* import com.example.app.MyResourcesProvider;
* class MyResourcesProviderImpl extends AbstractResourceBundleProvider
* implements MyResourcesProvider
- * {
- * {@code @Override
+ * {
+ * protected String toBundleName(String baseName, Locale locale) {
+ * // return the bundle name per the naming of the resource bundle
+ * :
+ * }
+ *
* public ResourceBundle getBundle(String baseName, Locale locale) {
* // this module only provides bundles in french
* if (locale.equals(Locale.FRENCH)) {
@@ -63,7 +73,7 @@ import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION;
* }
* return null;
* }
- * }}
+ * }
*
* @see
* Resource Bundles in Named Modules
@@ -73,6 +83,9 @@ import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION;
* @since 9
*/
public abstract class AbstractResourceBundleProvider implements ResourceBundleProvider {
+ private static final JavaUtilResourceBundleAccess RB_ACCESS =
+ SharedSecrets.getJavaUtilResourceBundleAccess();
+
private static final String FORMAT_CLASS = "java.class";
private static final String FORMAT_PROPERTIES = "java.properties";
@@ -112,7 +125,23 @@ public abstract class AbstractResourceBundleProvider implements ResourceBundlePr
/**
* Returns the bundle name for the given {@code baseName} and {@code
- * locale}. This method is called from the default implementation of the
+ * locale} that this provider provides.
+ *
+ * @apiNote
+ * A resource bundle provider may package its resource bundles in the
+ * same package as the base name of the resource bundle if the package
+ * is not split among other named modules. If there are more than one
+ * bundle providers providing the resource bundle of a given base name,
+ * the resource bundles can be packaged with per-language grouping
+ * or per-region grouping to eliminate the split packages.
+ *
+ * For example, if {@code baseName} is {@code "p.resources.Bundle"} then + * the resource bundle name of {@code "p.resources.Bundle"} of + * {@code Locale("ja", "", "XX")} and {@code Locale("en")} + * could be {@code "p.resources.ja.Bundle_ja_ _XX"} and + * {@code p.resources.Bundle_en"} respectively + * + *
This method is called from the default implementation of the
* {@link #getBundle(String, Locale)} method.
*
* @implNote The default implementation of this method is the same as the
@@ -126,27 +155,28 @@ public abstract class AbstractResourceBundleProvider implements ResourceBundlePr
*/
protected String toBundleName(String baseName, Locale locale) {
return ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT)
- .toBundleName(baseName, locale);
+ .toBundleName(baseName, locale);
}
/**
* Returns a {@code ResourceBundle} for the given {@code baseName} and
- * {@code locale}. This method calls the
- * {@link #toBundleName(String, Locale) toBundleName} method to get the
- * bundle name for the {@code baseName} and {@code locale}. The formats
- * specified by the constructor will be searched to find the resource
- * bundle.
+ * {@code locale}.
*
* @implNote
- * The default implementation of this method will find the resource bundle
- * local to the module of this provider.
+ * The default implementation of this method calls the
+ * {@link #toBundleName(String, Locale) toBundleName} method to get the
+ * bundle name for the {@code baseName} and {@code locale} and finds the
+ * resource bundle of the bundle name local in the module of this provider.
+ * It will only search the formats specified when this provider was
+ * constructed.
*
* @param baseName the base bundle name of the resource bundle, a fully
* qualified class name.
* @param locale the locale for which the resource bundle should be instantiated
- * @return {@code ResourceBundle} of the given {@code baseName} and {@code locale},
- * or null if no resource bundle is found
- * @throws NullPointerException if {@code baseName} or {@code locale} is null
+ * @return {@code ResourceBundle} of the given {@code baseName} and
+ * {@code locale}, or {@code null} if no resource bundle is found
+ * @throws NullPointerException if {@code baseName} or {@code locale} is
+ * {@code null}
* @throws UncheckedIOException if any IO exception occurred during resource
* bundle loading
*/
@@ -159,13 +189,9 @@ public abstract class AbstractResourceBundleProvider implements ResourceBundlePr
for (String format : formats) {
try {
if (FORMAT_CLASS.equals(format)) {
- PrivilegedAction