# HG changeset patch # User alanb # Date 1480582673 0 # Node ID a60f280f803c7cc71f484248051b53711b99c7b8 # Parent 7a4a59859ac00400ea73768a25409ecb7b9c3a80 8169069: Module system implementation refresh (11/2016) Reviewed-by: plevart, chegar, psandoz, mchung, alanb, dfuchs, naoto, coffeys, weijun Contributed-by: alan.bateman@oracle.com, mandy.chung@oracle.com, claes.redestad@oracle.com, mark.reinhold@oracle.com diff -r 7a4a59859ac0 -r a60f280f803c jdk/make/data/jdwp/jdwp.spec --- a/jdk/make/data/jdwp/jdwp.spec Wed Nov 23 16:16:35 2016 +0000 +++ b/jdk/make/data/jdwp/jdwp.spec Thu Dec 01 08:57:53 2016 +0000 @@ -2709,22 +2709,6 @@ (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 -r 7a4a59859ac0 -r a60f280f803c jdk/make/launcher/Launcher-jdk.jconsole.gmk
--- a/jdk/make/launcher/Launcher-jdk.jconsole.gmk Wed Nov 23 16:16:35 2016 +0000
+++ b/jdk/make/launcher/Launcher-jdk.jconsole.gmk Thu Dec 01 08:57:53 2016 +0000
@@ -27,7 +27,8 @@
$(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 -r 7a4a59859ac0 -r a60f280f803c jdk/make/src/classes/build/tools/jigsaw/AddPackagesAttribute.java
--- a/jdk/make/src/classes/build/tools/jigsaw/AddPackagesAttribute.java Wed Nov 23 16:16:35 2016 +0000
+++ b/jdk/make/src/classes/build/tools/jigsaw/AddPackagesAttribute.java Thu Dec 01 08:57:53 2016 +0000
@@ -65,7 +65,7 @@
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 @@
ms.descriptor().uses().stream()
.sorted()
.forEach(s -> sb.append("uses ").append(s).append("
");
@@ -534,8 +534,8 @@
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 -r 7a4a59859ac0 -r a60f280f803c jdk/make/src/classes/build/tools/module/GenModuleInfoSource.java
--- a/jdk/make/src/classes/build/tools/module/GenModuleInfoSource.java Wed Nov 23 16:16:35 2016 +0000
+++ b/jdk/make/src/classes/build/tools/module/GenModuleInfoSource.java Thu Dec 01 08:57:53 2016 +0000
@@ -30,219 +30,593 @@
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.
+ *
+ * GenModuleInfoSource will be invoked for each module that has
+ * module-info.java.extra in the source directory.
*
- * The extra exports, uses, provides can be specified in module-info.java.extra
- * files and 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 @@ * 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 @@ 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 @@ 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 @@ */ @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 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 @@ // 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.
* *An automatic module is defined implicitly rather than explicitly @@ -933,8 +1148,6 @@ * * @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; @@ -950,6 +1163,33 @@ } /** + *
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.
* * @return A possibly-empty unmodifiable set of the fully qualified class @@ -962,24 +1202,14 @@ /** *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") + ** * @see * Resource Bundles in Named Modules @@ -73,6 +83,9 @@ * @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 @@ /** * 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. + * + *Example usage:
+ *{@code ModuleDescriptor descriptor = ModuleDescriptor.module("m1") + * .exports("p") * .requires("m2") - * .exports("p") * .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 Maprequires = 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 @@ /** * 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 @@ * 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 @@ * @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)); } /** @@ -1209,62 +1431,6 @@ } /** - * 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. * * @param e @@ -1273,18 +1439,90 @@ * @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 @@ * 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 @@ * 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 @@ } /** - * 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 @@ * 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 @@ * 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 @@ * 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 @@ * 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 @@ * * @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 @@ * * @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 @@ * * @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 @@ * * @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 @@ * * @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 @@ return this; } - /* package */ Builder synthetic(boolean v) { this.synthetic = v; return this; @@ -1609,16 +1988,24 @@ * @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 packages = new HashSet<>(conceals); - packages.addAll(exportedPackages()); + Set exports = new HashSet<>(this.exports.values()); + Set opens = new HashSet<>(this.opens.values()); + + Set provides = new HashSet<>(this.provides.values()); + return new ModuleDescriptor(name, + open, automatic, synthetic, requires, + exports, + opens, uses, - exports, provides, version, mainClass, @@ -1631,7 +2018,6 @@ } - /** * Compares this module descriptor to another. * @@ -1689,11 +2075,13 @@ return false; ModuleDescriptor that = (ModuleDescriptor)ob; return (name.equals(that.name) + && open == that.open && automatic == that.automatic && synthetic == that.synthetic && requires.equals(that.requires) + && exports.equals(that.exports) + && opens.equals(that.opens) && uses.equals(that.uses) - && exports.equals(that.exports) && provides.equals(that.provides) && Objects.equals(version, that.version) && Objects.equals(mainClass, that.mainClass) @@ -1720,11 +2108,13 @@ 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 + exports.hashCode(); + hc = hc * 43 + opens.hashCode(); hc = hc * 43 + uses.hashCode(); - hc = hc * 43 + exports.hashCode(); hc = hc * 43 + provides.hashCode(); hc = hc * 43 + Objects.hashCode(version); hc = hc * 43 + Objects.hashCode(mainClass); @@ -1748,36 +2138,101 @@ @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 @@ * * @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 @@ * 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 @@ * * @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 @@ } } + /** + * 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 @@ */ 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(Set ms, String source) { + return new Exports(ms, source, Collections.emptySet(), true); } @Override - public Exports newExports(String source, Set targets) { - return new Exports(source, targets, false); + public Exports newExports(Set ms, + String source, + Set targets) { + return new Exports(ms, source, targets, true); } @Override - public Exports newExports(String source) { - return new Exports(source, false); + public Opens newOpens(Set ms, + String source, + Set targets) { + return new Opens(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) { + 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 @@ @Override public ModuleDescriptor newModuleDescriptor(String name, + boolean open, boolean automatic, boolean synthetic, Set requires, + Set exports, + Set opens, Set uses, - Set exports, - Map provides, + 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, + exports, + opens, uses, - exports, provides, version, mainClass, @@ -1975,7 +2464,14 @@ osArch, osVersion, packages, - hashes); + hashes, + hashCode, + false); + } + + @Override + public Optional hashes(ModuleDescriptor descriptor) { + return descriptor.hashes(); } @Override @@ -1995,11 +2491,6 @@ } @Override - public Optional hashes(ModuleDescriptor descriptor) { - return descriptor.hashes(); - } - - @Override public ModuleFinder newModulePath(Runtime.Version version, boolean isLinkPhase, Path... entries) { diff -r 7a4a59859ac0 -r a60f280f803c jdk/src/java.base/share/classes/java/lang/module/ModuleFinder.java --- a/jdk/src/java.base/share/classes/java/lang/module/ModuleFinder.java Wed Nov 23 16:16:35 2016 +0000 +++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleFinder.java Thu Dec 01 08:57:53 2016 +0000 @@ -233,10 +233,11 @@ * 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 -r 7a4a59859ac0 -r a60f280f803c jdk/src/java.base/share/classes/java/lang/module/ModuleInfo.java --- a/jdk/src/java.base/share/classes/java/lang/module/ModuleInfo.java Wed Nov 23 16:16:35 2016 +0000 +++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleInfo.java Thu Dec 01 08:57:53 2016 +0000 @@ -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.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 @@ 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 @@ { 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 @@ { 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 @@ /** * 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 @@ { 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 @@ 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 @@ // 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 @@ 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 @@ } // 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 @@ 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 @@ "LineNumberTable", "LocalVariableTable", "LocalVariableTypeTable", - "RuntimeVisibleAnnotations", - "RuntimeInvisibleAnnotations", "RuntimeVisibleParameterAnnotations", "RuntimeInvisibleParameterAnnotations", "RuntimeVisibleTypeAnnotations", "RuntimeInvisibleTypeAnnotations", + "Synthetic", "AnnotationDefault", "BootstrapMethods", "MethodParameters"); @@ -495,7 +586,6 @@ private static volatile Set predefinedNotAllowed; - /** * The constant pool in a class file. */ @@ -628,6 +718,11 @@ 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 @@ 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 -r 7a4a59859ac0 -r a60f280f803c jdk/src/java.base/share/classes/java/lang/module/ModulePath.java --- a/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java Wed Nov 23 16:16:35 2016 +0000 +++ b/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java Thu Dec 01 08:57:53 2016 +0000 @@ -40,9 +40,10 @@ 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.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 @@ * * 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 @@ // 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 @@ 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 @@ // 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 @@ 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 @@ */ 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 @@ * 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 -r 7a4a59859ac0 -r a60f280f803c jdk/src/java.base/share/classes/java/lang/module/ModuleReference.java --- a/jdk/src/java.base/share/classes/java/lang/module/ModuleReference.java Wed Nov 23 16:16:35 2016 +0000 +++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleReference.java Thu Dec 01 08:57:53 2016 +0000 @@ -60,8 +60,8 @@ // 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 @@ } /** - * 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 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 -r 7a4a59859ac0 -r a60f280f803c jdk/src/java.base/share/classes/java/lang/module/ModuleReferences.java --- a/jdk/src/java.base/share/classes/java/lang/module/ModuleReferences.java Wed Nov 23 16:16:35 2016 +0000 +++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleReferences.java Thu Dec 01 08:57:53 2016 +0000 @@ -51,6 +51,7 @@ 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 @@ 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 @@ 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 @@ 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 -r 7a4a59859ac0 -r a60f280f803c jdk/src/java.base/share/classes/java/lang/module/Resolver.java --- a/jdk/src/java.base/share/classes/java/lang/module/Resolver.java Wed Nov 23 16:16:35 2016 +0000 +++ b/jdk/src/java.base/share/classes/java/lang/module/Resolver.java Thu Dec 01 08:57:53 2016 +0000 @@ -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 @@ /** * 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 @@ 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 @@ // 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 @@ // 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 @@ 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 @@ 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 @@ } candidateConsumers = resolve(q); - } while (!candidateConsumers.isEmpty()); return this; @@ -429,21 +450,21 @@ 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 @@ } } + 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<>(); - - // the "requires public" graph, contains requires public edges only - Map > g2 = new HashMap<>(); + Map > g1 = new HashMap<>(capacity); - - // need "requires public" from the modules in parent configurations as - // there may be selected modules that have a dependency on modules in - // the parent configuration. + // the "requires transitive" graph, contains requires transitive edges only + Map > g2; - 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); + // need "requires transitive" from the modules in parent configurations + // as there may be selected modules that have a dependency on modules in + // the parent configuration. + if (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 @@ 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 @@ // 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 @@ 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 @@ = 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 @@ // 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 @@ } // 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 @@ } + /** + * 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 @@ 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 -r 7a4a59859ac0 -r a60f280f803c jdk/src/java.base/share/classes/java/lang/module/SystemModuleFinder.java --- a/jdk/src/java.base/share/classes/java/lang/module/SystemModuleFinder.java Wed Nov 23 16:16:35 2016 +0000 +++ b/jdk/src/java.base/share/classes/java/lang/module/SystemModuleFinder.java Thu Dec 01 08:57:53 2016 +0000 @@ -39,6 +39,7 @@ 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.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 @@ * 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 @@ 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 @@ // 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 @@ 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 @@ 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 @@ * 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 @@ return new HashSupplier() { @Override - public String generate(String algorithm) { + public byte[] generate(String algorithm) { return hashes.get(name); } }; diff -r 7a4a59859ac0 -r a60f280f803c jdk/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java --- a/jdk/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java Wed Nov 23 16:16:35 2016 +0000 +++ b/jdk/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java Thu Dec 01 08:57:53 2016 +0000 @@ -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 @@ * 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 @@ *
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 @@ * @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 @@ 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; - 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); - } + // 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; + + // 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 -r 7a4a59859ac0 -r a60f280f803c jdk/src/java.base/share/classes/java/lang/reflect/Layer.java --- a/jdk/src/java.base/share/classes/java/lang/reflect/Layer.java Wed Nov 23 16:16:35 2016 +0000 +++ b/jdk/src/java.base/share/classes/java/lang/reflect/Layer.java Thu Dec 01 08:57:53 2016 +0000 @@ -27,23 +27,29 @@ 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 @@ * 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 @@ * 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 @@ * 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 @@ // 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 @@ 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, + Functionclf) { + 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 @@ *
* *
- + * configuration have the same package.
Overlapping packages: Two or more modules in the - * configuration have the same package (exported or concealed).
* * 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 @@ * * @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 @@ * * @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 @@ * * @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 @@ * * @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.
- @@ -328,26 +570,35 @@ * * @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
A module is mapped to a class loader that already has a * module of the same name defined to it.
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 @@ } 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 @@ } - 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 @@ /** - * 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 @@ /** * 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 @@ * 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 @@ * @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 @@ 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 @@ } 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 -r 7a4a59859ac0 -r a60f280f803c jdk/src/java.base/share/classes/java/lang/reflect/Module.java --- a/jdk/src/java.base/share/classes/java/lang/reflect/Module.java Wed Nov 23 16:16:35 2016 +0000 +++ b/jdk/src/java.base/share/classes/java/lang/reflect/Module.java Thu Dec 01 08:57:53 2016 +0000 @@ -27,15 +27,18 @@ 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 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 @@ * @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 @@ // 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 @@ 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 @@ // -- - // 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 @@ } // 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 @@ * 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 @@ } // add reflective read - transientReads.putIfAbsent(this, other, Boolean.TRUE); + reflectivelyReads.putIfAbsent(this, other, Boolean.TRUE); } - // -- exports -- + // -- exported and open packages -- + + // 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; - // the packages that are permanently exported - // (will be final when the modules are defined in reverse topology order) - private volatile Map > exports; + // 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 added at run-time - // this module (1st key), other module (2nd key), exported packages (value) + // 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 @@ * * @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); + 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 ((targets != null) - && (targets.contains(other) || targets.contains(EVERYONE_MODULE))) - 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 @@ /** * 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 @@ * 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,18 +664,66 @@ 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 @@ 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 @@ * @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 @@ } } - // add package name to transientExports if absent - transientExports + // add package name to reflectivelyExports if absent + Mapmap = 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 @@ * 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 @@ * @see ModuleDescriptor#uses() */ @CallerSensitive - public Module addUses(Class> st) { - Objects.requireNonNull(st); + public Module addUses(Class> service) { + Objects.requireNonNull(service); - if (isNamed()) { - + 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 @@ 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 @@ * 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 @@ // -- 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 @@ 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 @@ 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 @@ } 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(); - String sourceInternalForm = source.replace('.', '/'); - - if (export.isQualified()) { + // exports and opens + initExportsAndOpens(descriptor, nameToModule, m); + } - // 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); - addExports0(m, sourceInternalForm, m2); - } + // 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); } - if (!targets.isEmpty()) { - exports.put(source, targets); - } - - } else { - - // unqualified export - exports.put(source, EVERYONE); - addExportsToAll0(m, sourceInternalForm); + catalog.register(m); } } - m.exports = exports; + } + + // record that there is a layer with modules defined to the class loader + for (ClassLoader loader : loaders) { + layer.bindToLoader(loader); } - // 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); + 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; + } + + Map > openPackages = new HashMap<>(); + Map > exportedPackages = new HashMap<>(); + + // 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); + } } - catalog.register(m); + if (!targets.isEmpty()) { + openPackages.put(source, targets); + } + } else { + // unqualified opens + addExportsToAll0(m, sourceInternalForm); + openPackages.put(source, EVERYONE_SET); } } - return modules; + // 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()) { + exportedPackages.put(source, targets); + } + + } else { + // unqualified exports + addExportsToAll0(m, sourceInternalForm); + exportedPackages.put(source, EVERYONE_SET); + } + } + + 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 @@ /** - * 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. + * + * 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 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. + *
- 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 @@ * * @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 @@ // JVM_DefineModule private static native void defineModule0(Module module, + boolean isOpen, String version, String location, String[] pns); @@ -1098,15 +1526,31 @@ } @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 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 -r 7a4a59859ac0 -r a60f280f803c jdk/src/java.base/share/classes/java/util/ResourceBundle.java --- a/jdk/src/java.base/share/classes/java/util/ResourceBundle.java Wed Nov 23 16:16:35 2016 +0000 +++ b/jdk/src/java.base/share/classes/java/util/ResourceBundle.java Thu Dec 01 08:57:53 2016 +0000 @@ -42,6 +42,7 @@ 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.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 @@ * 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 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 @@ *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 @@ * 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 @@ * 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 @@ * 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 @@ // 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 @@ 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, + private static ResourceBundle getBundleImpl(Module callerModule, + Module module, + ClassLoader loader, + String baseName, Locale locale, - ClassLoader loader, - Module module, Control control) { if (locale == null || control == null) { throw new NullPointerException(); @@ -1661,7 +1702,8 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ * 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 @@ * locale)}.If + * {@link Class} specified by the bundle name is loaded with the + * given class loader. If the {@code Class} is found and accessible + * then theformat
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 thereload
flag is - * ignored for loading class-based resource bundles in this default - * implementation.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 thereload
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 @@ /* * 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 @@ if (ResourceBundle.class.isAssignableFrom(c)) { @SuppressWarnings("unchecked") ClassbundleClass = (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 @@ 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 -r 7a4a59859ac0 -r a60f280f803c jdk/src/java.base/share/classes/java/util/ServiceLoader.java --- a/jdk/src/java.base/share/classes/java/util/ServiceLoader.java Wed Nov 23 16:16:35 2016 +0000 +++ b/jdk/src/java.base/share/classes/java/util/ServiceLoader.java Thu Dec 01 08:57:53 2016 +0000 @@ -32,23 +32,28 @@ 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.AccessControlContext; import java.security.AccessController; -import java.security.AccessControlContext; 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 @@ * 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 @@ * *
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 @@ 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 Classservice; + // 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 @@ // The access control context taken when the ServiceLoader is created private final AccessControlContext acc; - // Cached providers, in instantiation order - private Listproviders = 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 SetproviderNames = 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; - + private static JavaLangAccess LANG_ACCESS; + private static JavaLangReflectModuleAccess JLRM_ACCESS; + static { + LANG_ACCESS = SharedSecrets.getJavaLangAccess(); + JLRM_ACCESS = SharedSecrets.getJavaLangReflectModuleAccess(); + } /** - * Clear this loader's provider cache so that all providers will be - * reloaded. + * 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.
* - *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. + * @param
The service type + * @since 9 */ - public void reload() { - providers.clear(); + public static interface Providerextends 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(); - assert layer == null || loader == null; - if (layer != null) { - layerLookupIterator = new LayerLookupIterator(); - } else { - providerNames.clear(); - moduleServicesIterator = new ModuleServicesIterator(); - lazyClassPathIterator = new LazyClassPathIterator(); - } - - reloadCount++; + /** + * 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 @@ * 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, Classsvc, ClassLoader cl) { - if (VM.isBooted()) { + private ServiceLoader(Class> caller, Classsvc, ClassLoader cl) { + Objects.requireNonNull(svc); - checkModule(callerModule, svc); - + if (VM.isBooted()) { + 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 @@ } 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, Classsvc, 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, Classsvc, 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 @@ 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, - Listnames) - 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); + } else { + 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; + } } - return names.iterator(); + if (clazz == null) + fail(service, "Provider " + cn + " not found"); + return clazz; } /** - * 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. + * A Provider implementation that supports invoking, with reduced + * permissions, the static factory to obtain the provider or the + * provider's no-arg constructor. */ - private static Constructor> checkAndGetConstructor(Class> c) - throws NoSuchMethodException, IllegalAccessException - { - Constructor> ctor = c.getConstructor(); + private final static class ProviderImpl implements Provider{ + final Classservice; + 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 + + /** + * 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. + */ + @SuppressWarnings("unchecked") + ProviderImpl(Class> service, Class> clazz, AccessControlContext acc) { + this.service = (Class) service; + this.acc = acc; - // 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"); + 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 { + this.type = (Class extends S>) factoryMethod.getReturnType(); + this.ctor = null; + } } - // return Constructor to create the service implementation - PrivilegedActionaction = 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); + @Override + public Class extends S> type() { + return type; } - } - /** - * An Iterator that runs the next and hasNext methods with permissions - * restricted by the {@code AccessControlContext} obtained when the - * ServiceLoader was created. - */ - private abstract class RestrictedIterator - implements Iterator- { - /** - * Returns {@code true} if the iteration has more elements. - */ - abstract boolean hasNextService(); - - /** - * Returns the next element in the iteration - */ - abstract S nextService(); - - public final boolean hasNext() { - if (acc == null) { - return hasNextService(); + @Override + public S get() { + if (factoryMethod != null) { + return invokeFactoryMethod(); } else { - PrivilegedActionaction = new PrivilegedAction () { - public Boolean run() { return hasNextService(); } - }; - return AccessController.doPrivileged(action, acc); + return newInstance(); } } - public final S next() { + /** + * 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) { - return nextService(); + try { + result = factoryMethod.invoke(null); + } catch (Throwable x) { + exc = x; + } } else { - PrivilegedAction action = new PrivilegedAction() { - public S run() { return nextService(); } + PrivilegedExceptionAction> pa = new PrivilegedExceptionAction<>() { + @Override + public Object run() throws Exception { + return factoryMethod.invoke(null); + } }; - return AccessController.doPrivileged(action, acc); + // 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 { + PrivilegedExceptionActionpa = 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 (loader == null) { + catalog = BootLoader.getServicesCatalog(); + } else { + catalog = ServicesCatalog.getServicesCatalogOrNull(loader); + } + Stream stream1; + if (catalog == null) { + stream1 = Stream.empty(); + } else { + stream1 = catalog.findServices(serviceName).stream(); } - ServicesCatalog catalog; - if (currentLoader == null) { - catalog = BootLoader.getServicesCatalog(); + // modules in custom layers that define modules to the class loader + Stream stream2; + if (loader == null) { + stream2 = Stream.empty(); } else { - catalog = langAccess.getServicesCatalog(currentLoader); + Layer bootLayer = Layer.boot(); + stream2 = JLRM_ACCESS.layers(loader) + .filter(l -> (l != bootLayer)) + .map(l -> providers(l)) + .flatMap(List::stream); } - if (catalog == null) { - return Collections.emptyIterator(); - } else { - return catalog.findServices(service.getName()).iterator(); - } + + 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 @@ } @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 + + LazyClassPathLookupIterator() { } - @Override - boolean hasNextService() { - if (nextName != null) { + /** + * 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"); - } - if (!service.isAssignableFrom(c)) { - fail(service, - "Provider " + cn + " not a subtype"); + + // throw any SCE with error recorded by hasNext + if (nextErrorMessage != null) { + String msg = nextErrorMessage; + nextErrorMessage = null; + fail(service, msg); } - 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); + + // 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); } - providers.add(p); - providerNames.add(cn); - return p; + } + + @Override + public Provider next() { + if (acc == null) { + return nextService(); + } else { + PrivilegedAction > action = new PrivilegedAction<>() { + public Provider run() { return nextService(); } + }; + return AccessController.doPrivileged(action, acc); + } } } /** - * 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 @@ * 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 @@ * 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 @@ 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; @@ -932,6 +1265,108 @@ } /** + * 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+ * + * @return A stream that lazily loads providers for this loader's service + * + * @since 9 + */ + public Streamproviders = 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); + * } > 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 @@ * * @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 @@ * 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. * * @paramthe class of the service type * @@ -1012,7 +1455,7 @@ * * @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 @@ * *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 @@ * * @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
ServiceLoaderloadInstalled(Classservice) { - 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 @@ * * @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 staticServiceLoaderload(Layer layer, Classservice) { - 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 OptionalfindFirst() { + Iteratoriterator = 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 -r 7a4a59859ac0 -r a60f280f803c jdk/src/java.base/share/classes/java/util/spi/AbstractResourceBundleProvider.java --- a/jdk/src/java.base/share/classes/java/util/spi/AbstractResourceBundleProvider.java Wed Nov 23 16:16:35 2016 +0000 +++ b/jdk/src/java.base/share/classes/java/util/spi/AbstractResourceBundleProvider.java Thu Dec 01 08:57:53 2016 +0000 @@ -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 @@ * } * return null; * } - * }}
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 @@
*/
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 @@
for (String format : formats) {
try {
if (FORMAT_CLASS.equals(format)) {
- PrivilegedAction