OpenJDK / bsd-port / jdk9 / jdk
changeset 16206:7b997de570ba
Merge
author | lana |
---|---|
date | Thu, 01 Dec 2016 21:39:49 +0000 |
parents | 46b340c4e9bf fdc2a054d947 |
children | 685512caa8bf |
files | src/java.base/share/classes/java/lang/module/Dependence.java src/java.base/share/classes/sun/util/locale/provider/ResourceBundleProviderSupport.java test/java/awt/Focus/DisposedWindow/DisposeDialogNotActivateOwnerTest/DisposeDialogNotActivateOwnerTest.html test/java/lang/Class/getResource/src/m3/module-info.java test/java/lang/Class/getResource/src/m3/p3/Main.java test/java/lang/ClassLoader/getResource/modules/src/m3/module-info.java test/java/lang/ClassLoader/getResource/modules/src/m3/p3/Main.java test/java/lang/reflect/Module/access/src/target/p/Exported.java test/java/lang/reflect/Module/access/src/target/p/Helper.java test/java/lang/reflect/Module/access/src/target/q/Internal.java test/java/net/Authenticator/B4933582.sh test/java/rmi/activation/rmidViaInheritedChannel/RmidViaInheritedChannel.java test/java/util/ServiceLoader/Basic.java test/java/util/ServiceLoader/FooProvider1.java test/java/util/ServiceLoader/FooProvider2.java test/java/util/ServiceLoader/FooProvider3.java test/java/util/ServiceLoader/FooService.java test/java/util/ServiceLoader/Load.java test/java/util/ServiceLoader/basic.sh test/java/util/ServiceLoader/modules/MiscTests.java test/java/util/ServiceLoader/modules/ServicesTest.java test/java/util/ServiceLoader/modules/src/bananascript/module-info.java test/java/util/ServiceLoader/modules/src/bananascript/org/banana/BananaScript.java test/java/util/ServiceLoader/modules/src/bananascript/org/banana/BananaScriptEngineFactory.java test/java/util/ServiceLoader/modules/src/test/module-info.java test/java/util/ServiceLoader/modules/src/test/test/Main.java |
diffstat | 637 files changed, 22660 insertions(+), 8641 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Thu Dec 01 21:01:53 2016 +0000 +++ b/.hgignore Thu Dec 01 21:39:49 2016 +0000 @@ -5,7 +5,6 @@ /nbproject/private/ ^make/netbeans/.*/build/ ^make/netbeans/.*/dist/ -^.hgtip .DS_Store .*/JTreport/.* .*/JTwork/.*
--- a/make/data/jdwp/jdwp.spec Thu Dec 01 21:01:53 2016 +0000 +++ b/make/data/jdwp/jdwp.spec Thu Dec 01 21:39:49 2016 +0000 @@ -2709,22 +2709,6 @@ (Error VM_DEAD) ) ) - (Command CanRead=3 - "Returns true if this module can read the source module; false otherwise." - "<p>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
--- a/make/launcher/Launcher-jdk.jconsole.gmk Thu Dec 01 21:01:53 2016 +0000 +++ b/make/launcher/Launcher-jdk.jconsole.gmk Thu Dec 01 21:39:49 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, \ ))
--- a/make/lib/Awt2dLibraries.gmk Thu Dec 01 21:01:53 2016 +0000 +++ b/make/lib/Awt2dLibraries.gmk Thu Dec 01 21:39:49 2016 +0000 @@ -287,9 +287,8 @@ $(JDK_TOPDIR)/src/java.desktop/share/native/common/awt/debug \ $(JDK_TOPDIR)/src/java.desktop/share/native/common/awt/utility \ $(JDK_TOPDIR)/src/java.desktop/share/native/common/font \ - $(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d/opengl \ - $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/java2d/opengl \ - $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/java2d/x11 \ + $(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d \ + $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/java2d \ $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/awt \ # @@ -516,24 +515,25 @@ LIBAWT_HEADLESS_DIRS := $(JDK_TOPDIR)/src/java.desktop/unix/native/libawt_headless/awt \ $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/awt \ - $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/java2d/opengl \ - $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/java2d/x11 \ - $(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d/opengl \ + $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/java2d \ + $(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d \ $(JDK_TOPDIR)/src/java.desktop/share/native/common/font \ # LIBAWT_HEADLESS_EXCLUDES := medialib LIBAWT_HEADLESS_CFLAGS := -I$(SUPPORT_OUTPUTDIR)/headers/java.desktop \ $(addprefix -I, $(LIBAWT_HEADLESS_DIRS)) \ + -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/awt/image \ + -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/awt/image/cvutils \ -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/java2d \ + -I$(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/libawt/java2d \ -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/java2d/loops \ - -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/awt/image/cvutils \ -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/java2d/pipe \ - -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/awt/image \ - -I$(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/libawt/java2d \ + -I$(JDK_TOPDIR)/src/java.desktop/share/native/common/awt/debug \ -I$(JDK_TOPDIR)/src/java.desktop/share/native/common/font \ - -I$(JDK_TOPDIR)/src/java.desktop/share/native/common/awt/debug \ -I$(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/font \ + -I$(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d/opengl \ + -I$(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/java2d/opengl \ -I$(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/libsunwjdga/ \ $(LIBJAVA_HEADER_FLAGS) \ # @@ -862,7 +862,7 @@ ifeq ($(OPENJDK_TARGET_OS), windows) LIBSPLASHSCREEN_DIRS += $(JDK_TOPDIR)/src/java.desktop/windows/native/common/awt/systemscale - endif + endif LIBSPLASHSCREEN_CFLAGS += -DSPLASHSCREEN -DPNG_NO_MMX_CODE -DPNG_ARM_NEON_OPT=0 \ $(addprefix -I, $(LIBSPLASHSCREEN_DIRS)) \ $(LIBJAVA_HEADER_FLAGS) \ @@ -952,26 +952,27 @@ $(JDK_TOPDIR)/src/java.desktop/macosx/native/libawt_lwawt \ $(JDK_TOPDIR)/src/java.desktop/unix/native/common/awt \ $(JDK_TOPDIR)/src/java.desktop/share/native/common/font \ - $(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d/opengl \ + $(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d \ # LIBAWT_LWAWT_CFLAGS := \ $(addprefix -I, $(LIBAWT_LWAWT_DIRS)) \ -I$(SUPPORT_OUTPUTDIR)/headers/java.desktop \ - -I$(JDK_TOPDIR)/src/java.desktop/macosx/native/include \ - -I$(JDK_TOPDIR)/src/java.desktop/share/native/include \ - -I$(JDK_TOPDIR)/src/java.desktop/macosx/native/libawt_lwawt/java2d/opengl \ -I$(JDK_TOPDIR)/src/java.desktop/macosx/native/libawt_lwawt/awt \ -I$(JDK_TOPDIR)/src/java.desktop/unix/native/libawt_xawt/awt \ -I$(JDK_TOPDIR)/src/java.desktop/macosx/native/libawt_lwawt/font \ + -I$(JDK_TOPDIR)/src/java.desktop/macosx/native/libawt_lwawt/java2d/opengl \ + -I$(JDK_TOPDIR)/src/java.desktop/share/native/common/awt/debug \ + -I$(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d/opengl \ + -I$(JDK_TOPDIR)/src/java.desktop/macosx/native/include \ + -I$(JDK_TOPDIR)/src/java.desktop/share/native/include \ -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/awt/image \ + -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/awt/image/cvutils \ -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/java2d \ -I$(JDK_TOPDIR)/src/java.desktop/unix/native/libawt/java2d \ - -I$(JDK_TOPDIR)/src/java.desktop/share/native/libmlib_image/ \ - -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/awt/image/cvutils \ -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/java2d/loops \ -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/java2d/pipe \ - -I$(JDK_TOPDIR)/src/java.desktop/share/native/common/awt/debug \ + -I$(JDK_TOPDIR)/src/java.desktop/share/native/libmlib_image/ \ -I$(JDK_TOPDIR)/src/java.desktop/macosx/native/libosxapp \ $(LIBJAVA_HEADER_FLAGS) \ #
--- a/make/src/classes/build/tools/jigsaw/AddPackagesAttribute.java Thu Dec 01 21:01:53 2016 +0000 +++ b/make/src/classes/build/tools/jigsaw/AddPackagesAttribute.java Thu Dec 01 21:39:49 2016 +0000 @@ -65,7 +65,7 @@ String mn = entry.getFileName().toString(); Optional<ModuleReference> omref = finder.find(mn); if (omref.isPresent()) { - Set<String> packages = omref.get().descriptor().conceals(); + Set<String> packages = omref.get().descriptor().packages(); addPackagesAttribute(mi, packages); } } @@ -77,7 +77,7 @@ byte[] bytes; try (InputStream in = Files.newInputStream(mi)) { ModuleInfoExtender extender = ModuleInfoExtender.newExtender(in); - extender.conceals(packages); + extender.packages(packages); ByteArrayOutputStream baos = new ByteArrayOutputStream(); extender.write(baos); bytes = baos.toByteArray();
--- a/make/src/classes/build/tools/jigsaw/GenGraphs.java Thu Dec 01 21:01:53 2016 +0000 +++ b/make/src/classes/build/tools/jigsaw/GenGraphs.java Thu Dec 01 21:39:49 2016 +0000 @@ -43,7 +43,7 @@ import java.util.TreeSet; import java.util.function.Function; import java.util.stream.Collectors; -import static java.lang.module.ModuleDescriptor.Requires.Modifier.PUBLIC; +import static java.lang.module.ModuleDescriptor.Requires.Modifier.TRANSITIVE; /** * Generate the DOT file for a module graph for each module in the JDK @@ -172,14 +172,14 @@ Graph<String> graph = gengraph(cf); descriptors.forEach(md -> { String mn = md.name(); - Set<String> requiresPublic = md.requires().stream() - .filter(d -> d.modifiers().contains(PUBLIC)) + Set<String> requiresTransitive = md.requires().stream() + .filter(d -> d.modifiers().contains(TRANSITIVE)) .map(d -> d.name()) .collect(Collectors.toSet()); graph.adjacentNodes(mn).forEach(dn -> { String attr = dn.equals("java.base") ? REQUIRES_BASE - : (requiresPublic.contains(dn) ? REEXPORTS : REQUIRES); + : (requiresTransitive.contains(dn) ? REEXPORTS : REQUIRES); int w = weightOf(mn, dn); if (w > 1) attr += "weight=" + w; @@ -194,8 +194,8 @@ /** * Returns a Graph of the given Configuration after transitive reduction. * - * Transitive reduction of requires public edge and requires edge have - * to be applied separately to prevent the requires public edges + * Transitive reduction of requires transitive edge and requires edge have + * to be applied separately to prevent the requires transitive edges * (e.g. U -> V) from being reduced by a path (U -> X -> Y -> V) * in which V would not be re-exported from U. */ @@ -208,21 +208,21 @@ .map(ResolvedModule::name) .forEach(target -> builder.addEdge(mn, target)); } - Graph<String> rpg = requiresPublicGraph(cf); + Graph<String> rpg = requiresTransitiveGraph(cf); return builder.build().reduce(rpg); } /** - * Returns a Graph containing only requires public edges + * Returns a Graph containing only requires transitive edges * with transitive reduction. */ - private Graph<String> requiresPublicGraph(Configuration cf) { + private Graph<String> requiresTransitiveGraph(Configuration cf) { Graph.Builder<String> builder = new Graph.Builder<>(); for (ResolvedModule resolvedModule : cf.modules()) { ModuleDescriptor descriptor = resolvedModule.reference().descriptor(); String mn = descriptor.name(); descriptor.requires().stream() - .filter(d -> d.modifiers().contains(PUBLIC)) + .filter(d -> d.modifiers().contains(TRANSITIVE)) .map(d -> d.name()) .forEach(d -> builder.addEdge(mn, d)); }
--- a/make/src/classes/build/tools/jigsaw/ModuleSummary.java Thu Dec 01 21:01:53 2016 +0000 +++ b/make/src/classes/build/tools/jigsaw/ModuleSummary.java Thu Dec 01 21:39:49 2016 +0000 @@ -299,7 +299,7 @@ static class HtmlDocument { final String title; final Map<String, ModuleSummary> modules; - boolean requiresPublicNote = false; + boolean requiresTransitiveNote = false; boolean aggregatorNote = false; boolean totalBytesNote = false; HtmlDocument(String title, Map<String, ModuleSummary> modules) { @@ -510,16 +510,16 @@ public String requiresColumn() { StringBuilder sb = new StringBuilder(); sb.append(String.format("<td>")); - 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("<a class=\"%s\" href=\"#%s\">%s</a>", sel, r.name(), r.name()); - if (!requiresPublicNote && requiresPublic) { - requiresPublicNote = true; + if (!requiresTransitiveNote && requiresTransitive) { + requiresTransitiveNote = true; req += "<sup>*</sup>"; } sb.append(req).append("\n").append("<br>"); @@ -534,8 +534,8 @@ sb.append("<br>"); sb.append("<i>+").append(indirectDeps).append(" transitive dependencies</i>"); } - if (footnote != requiresPublicNote) { - sb.append("<br><br>").append("<i>* bold denotes requires public</i>"); + if (footnote != requiresTransitiveNote) { + sb.append("<br><br>").append("<i>* bold denotes requires transitive</i>"); } sb.append("</td>"); return sb.toString(); @@ -558,11 +558,10 @@ ms.descriptor().uses().stream() .sorted() .forEach(s -> sb.append("uses ").append(s).append("<br>").append("\n")); - ms.descriptor().provides().entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .flatMap(e -> e.getValue().providers().stream() - .map(p -> String.format("provides %s<br> with %s", - e.getKey(), p))) + ms.descriptor().provides().stream() + .sorted(Comparator.comparing(Provides::service)) + .map(p -> String.format("provides %s<br> with %s", + p.service(), p.providers())) .forEach(p -> sb.append(p).append("<br>").append("\n")); sb.append("</td>"); return sb.toString();
--- a/make/src/classes/build/tools/module/GenModuleInfoSource.java Thu Dec 01 21:01:53 2016 +0000 +++ b/make/src/classes/build/tools/module/GenModuleInfoSource.java Thu Dec 01 21:39:49 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. * - * The extra exports, uses, provides can be specified in module-info.java.extra - * files and GenModuleInfoSource will be invoked for each module that has + * GenModuleInfoSource will be invoked for each module that has * module-info.java.extra in the source directory. + * + * The extra exports, opens, uses, provides can be specified + * in module-info.java.extra. + * Injecting platform-specific requires is not supported. + * + * @see build.tools.module.ModuleInfoExtraTest for basic testing */ public class GenModuleInfoSource { private final static String USAGE = - "Usage: GenModuleInfoSource [option] -o <output file> <module-info-java>\n" + - "Options are:\n" + - " -exports <package-name>\n" + - " -exports <package-name>[/<module-name>]\n" + - " -uses <service>\n" + - " -provides <service>/<provider-impl-classname>\n"; + "Usage: GenModuleInfoSource -o <output file> \n" + + " --source-file <module-info-java>\n" + + " --modules <module-name>[,<module-name>...]\n" + + " <module-info.java.extra> ...\n"; + static boolean verbose = false; public static void main(String... args) throws Exception { Path outfile = null; Path moduleInfoJava = null; - GenModuleInfoSource genModuleInfo = new GenModuleInfoSource(); - + Set<String> modules = Collections.emptySet(); + List<Path> extras = new ArrayList<>(); // validate input arguments for (int i = 0; i < args.length; i++){ String option = args[i]; - if (option.startsWith("-")) { - String arg = args[++i]; - if (option.equals("-exports")) { - int index = arg.indexOf('/'); - if (index > 0) { - String pn = arg.substring(0, index); - String mn = arg.substring(index + 1, arg.length()); - genModuleInfo.exportTo(pn, mn); - } else { - genModuleInfo.export(arg); - } - } else if (option.equals("-uses")) { - genModuleInfo.use(arg); - } else if (option.equals("-provides")) { - int index = arg.indexOf('/'); - if (index <= 0) { - throw new IllegalArgumentException("invalid -provide argument: " + arg); - } - String service = arg.substring(0, index); - String impl = arg.substring(index + 1, arg.length()); - genModuleInfo.provide(service, impl); - } else if (option.equals("-o")) { + String arg = i+1 < args.length ? args[i+1] : null; + switch (option) { + case "-o": outfile = Paths.get(arg); - } else { - throw new IllegalArgumentException("invalid option: " + option); - } - } else if (moduleInfoJava != null) { - throw new IllegalArgumentException("more than one module-info.java"); - } else { - moduleInfoJava = Paths.get(option); - if (Files.notExists(moduleInfoJava)) { - throw new IllegalArgumentException(option + " not exist"); - } + i++; + break; + case "--source-file": + moduleInfoJava = Paths.get(arg); + if (Files.notExists(moduleInfoJava)) { + throw new IllegalArgumentException(moduleInfoJava + " not exist"); + } + i++; + break; + case "--modules": + modules = Arrays.stream(arg.split(",")) + .collect(toSet()); + i++; + break; + case "-v": + verbose = true; + break; + default: + Path file = Paths.get(option); + if (Files.notExists(file)) { + throw new IllegalArgumentException(file + " not exist"); + } + extras.add(file); } } - if (moduleInfoJava == null || outfile == null) { + if (moduleInfoJava == null || outfile == null || + modules.isEmpty() || extras.isEmpty()) { System.err.println(USAGE); System.exit(-1); } + GenModuleInfoSource genModuleInfo = + new GenModuleInfoSource(moduleInfoJava, extras, modules); + // generate new module-info.java - genModuleInfo.generate(moduleInfoJava, outfile); + genModuleInfo.generate(outfile); } - private final Set<String> exports = new HashSet<>(); - private final Map<String, Set<String>> exportsTo = new HashMap<>(); - private final Set<String> uses = new HashSet<>(); - private final Map<String, Set<String>> provides = new HashMap<>(); - GenModuleInfoSource() { + final Path sourceFile; + final List<Path> extraFiles; + final ModuleInfo extras; + final Set<String> modules; + final ModuleInfo moduleInfo; + GenModuleInfoSource(Path sourceFile, List<Path> extraFiles, Set<String> modules) + throws IOException + { + this.sourceFile = sourceFile; + this.extraFiles = extraFiles; + this.modules = modules; + this.moduleInfo = new ModuleInfo(); + this.moduleInfo.parse(sourceFile); + + // parse module-info.java.extra + this.extras = new ModuleInfo(); + for (Path file : extraFiles) { + extras.parse(file); + } + + // merge with module-info.java.extra + moduleInfo.augmentModuleInfo(extras, modules); } - private void export(String p) { - Objects.requireNonNull(p); - if (exports.contains(p) || exportsTo.containsKey(p)) { - throw new RuntimeException("duplicated exports: " + p); + void generate(Path output) throws IOException { + List<String> lines = Files.readAllLines(sourceFile); + try (BufferedWriter bw = Files.newBufferedWriter(output); + PrintWriter writer = new PrintWriter(bw)) { + // write the copyright header and lines up to module declaration + for (String l : lines) { + writer.println(l); + if (l.trim().startsWith("module ")) { + writer.format(" // source file: %s%n", sourceFile); + for (Path file: extraFiles) { + writer.format(" // %s%n", file); + } + break; + } + } + + // requires + for (String l : lines) { + if (l.trim().startsWith("requires")) + writer.println(l); + } + + // write exports, opens, uses, and provides + moduleInfo.print(writer); + + // close + writer.println("}"); } - exports.add(p); - } - private void exportTo(String p, String mn) { - Objects.requireNonNull(p); - Objects.requireNonNull(mn); - if (exports.contains(p)) { - throw new RuntimeException("unqualified exports already exists: " + p); - } - exportsTo.computeIfAbsent(p, _k -> new HashSet<>()).add(mn); } - private void use(String service) { - uses.add(service); - } - private void provide(String s, String impl) { - provides.computeIfAbsent(s, _k -> new HashSet<>()).add(impl); - } + class ModuleInfo { + final Map<String, Statement> exports = new HashMap<>(); + final Map<String, Statement> opens = new HashMap<>(); + final Map<String, Statement> uses = new HashMap<>(); + final Map<String, Statement> provides = new HashMap<>(); - private void generate(Path sourcefile, Path outfile) throws IOException { - Path parent = outfile.getParent(); - if (parent != null) - Files.createDirectories(parent); + Statement getStatement(String directive, String name) { + switch (directive) { + case "exports": + if (moduleInfo.exports.containsKey(name) && + moduleInfo.exports.get(name).isUnqualified()) { + throw new IllegalArgumentException(sourceFile + + " already has " + directive + " " + name); + } + return exports.computeIfAbsent(name, + _n -> new Statement("exports", "to", name)); - List<String> lines = Files.readAllLines(sourcefile); - try (BufferedWriter bw = Files.newBufferedWriter(outfile); - PrintWriter writer = new PrintWriter(bw)) { - int lineNumber = 0; - for (String l : lines) { + case "opens": + if (moduleInfo.opens.containsKey(name) && + moduleInfo.opens.get(name).isUnqualified()) { + throw new IllegalArgumentException(sourceFile + + " already has " + directive + " " + name); + } + + if (moduleInfo.opens.containsKey(name)) { + throw new IllegalArgumentException(sourceFile + + " already has " + directive + " " + name); + } + return opens.computeIfAbsent(name, + _n -> new Statement("opens", "to", name)); + + case "uses": + return uses.computeIfAbsent(name, + _n -> new Statement("uses", "", name)); + + case "provides": + return provides.computeIfAbsent(name, + _n -> new Statement("provides", "with", name, true)); + + default: + throw new IllegalArgumentException(directive); + } + + } + + /* + * Augment this ModuleInfo with module-info.java.extra + */ + void augmentModuleInfo(ModuleInfo extraFiles, Set<String> modules) { + // API package exported in the original module-info.java + extraFiles.exports.entrySet() + .stream() + .filter(e -> exports.containsKey(e.getKey()) && + e.getValue().filter(modules)) + .forEach(e -> mergeExportsOrOpens(exports.get(e.getKey()), + e.getValue(), + modules)); + + // add exports that are not defined in the original module-info.java + extraFiles.exports.entrySet() + .stream() + .filter(e -> !exports.containsKey(e.getKey()) && + e.getValue().filter(modules)) + .forEach(e -> addTargets(getStatement("exports", e.getKey()), + e.getValue(), + modules)); + + // API package opened in the original module-info.java + extraFiles.opens.entrySet() + .stream() + .filter(e -> opens.containsKey(e.getKey()) && + e.getValue().filter(modules)) + .forEach(e -> mergeExportsOrOpens(opens.get(e.getKey()), + e.getValue(), + modules)); + + // add opens that are not defined in the original module-info.java + extraFiles.opens.entrySet() + .stream() + .filter(e -> !opens.containsKey(e.getKey()) && + e.getValue().filter(modules)) + .forEach(e -> addTargets(getStatement("opens", e.getKey()), + e.getValue(), + modules)); + + // provides + extraFiles.provides.keySet() + .stream() + .filter(service -> provides.containsKey(service)) + .forEach(service -> mergeProvides(service, + extraFiles.provides.get(service))); + extraFiles.provides.keySet() + .stream() + .filter(service -> !provides.containsKey(service)) + .forEach(service -> provides.put(service, + extraFiles.provides.get(service))); + + // uses + extraFiles.uses.keySet() + .stream() + .filter(service -> !uses.containsKey(service)) + .forEach(service -> uses.put(service, extraFiles.uses.get(service))); + } + + // add qualified exports or opens to known modules only + private void addTargets(Statement statement, + Statement extra, + Set<String> modules) + { + extra.targets.stream() + .filter(mn -> modules.contains(mn)) + .forEach(mn -> statement.addTarget(mn)); + } + + private void mergeExportsOrOpens(Statement statement, + Statement extra, + Set<String> modules) + { + String pn = statement.name; + if (statement.isUnqualified() && extra.isQualified()) { + throw new RuntimeException("can't add qualified exports to " + + "unqualified exports " + pn); + } + + Set<String> mods = extra.targets.stream() + .filter(mn -> statement.targets.contains(mn)) + .collect(toSet()); + if (mods.size() > 0) { + throw new RuntimeException("qualified exports " + pn + " to " + + mods.toString() + " already declared in " + sourceFile); + } + + // add qualified exports or opens to known modules only + addTargets(statement, extra, modules); + } + + private void mergeProvides(String service, Statement extra) { + Statement statement = provides.get(service); + + Set<String> mods = extra.targets.stream() + .filter(mn -> statement.targets.contains(mn)) + .collect(toSet()); + + if (mods.size() > 0) { + throw new RuntimeException("qualified exports " + service + " to " + + mods.toString() + " already declared in " + sourceFile); + } + + extra.targets.stream() + .forEach(mn -> statement.addTarget(mn)); + } + + + void print(PrintWriter writer) { + // print unqualified exports + exports.entrySet().stream() + .filter(e -> e.getValue().targets.isEmpty()) + .sorted(Map.Entry.comparingByKey()) + .forEach(e -> writer.println(e.getValue())); + + // print qualified exports + exports.entrySet().stream() + .filter(e -> !e.getValue().targets.isEmpty()) + .sorted(Map.Entry.comparingByKey()) + .forEach(e -> writer.println(e.getValue())); + + // print unqualified opens + opens.entrySet().stream() + .filter(e -> e.getValue().targets.isEmpty()) + .sorted(Map.Entry.comparingByKey()) + .forEach(e -> writer.println(e.getValue())); + + // print qualified opens + opens.entrySet().stream() + .filter(e -> !e.getValue().targets.isEmpty()) + .sorted(Map.Entry.comparingByKey()) + .forEach(e -> writer.println(e.getValue())); + + // uses and provides + writer.println(); + uses.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(e -> writer.println(e.getValue())); + provides.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(e -> writer.println(e.getValue())); + } + + private void parse(Path sourcefile) throws IOException { + List<String> lines = Files.readAllLines(sourcefile); + Statement statement = null; + boolean hasTargets = false; + + for (int lineNumber = 1; lineNumber <= lines.size(); ) { + String l = lines.get(lineNumber-1).trim(); + int index = 0; + + if (l.isEmpty()) { + lineNumber++; + continue; + } + + // comment block starts + if (l.startsWith("/*")) { + while (l.indexOf("*/") == -1) { // end comment block + l = lines.get(lineNumber++).trim(); + } + index = l.indexOf("*/") + 2; + if (index >= l.length()) { + lineNumber++; + continue; + } else { + // rest of the line + l = l.substring(index, l.length()).trim(); + index = 0; + } + } + + // skip comment and annotations + if (l.startsWith("//") || l.startsWith("@")) { + lineNumber++; + continue; + } + + int current = lineNumber; + int count = 0; + while (index < l.length()) { + if (current == lineNumber && ++count > 20) + throw new Error("Fail to parse line " + lineNumber + " " + sourcefile); + + int end = l.indexOf(';'); + if (end == -1) + end = l.length(); + String content = l.substring(0, end).trim(); + if (content.isEmpty()) { + index = end+1; + if (index < l.length()) { + // rest of the line + l = l.substring(index, l.length()).trim(); + index = 0; + } + continue; + } + + String[] s = content.split("\\s+"); + String keyword = s[0].trim(); + + String name = s.length > 1 ? s[1].trim() : null; + trace("%d: %s index=%d len=%d%n", lineNumber, l, index, l.length()); + switch (keyword) { + case "module": + case "requires": + case "}": + index = l.length(); // skip to the end + continue; + + case "exports": + case "opens": + case "provides": + case "uses": + // assume name immediately after exports, opens, provides, uses + statement = getStatement(keyword, name); + hasTargets = false; + + int i = l.indexOf(name, keyword.length()+1) + name.length() + 1; + l = i < l.length() ? l.substring(i, l.length()).trim() : ""; + index = 0; + + if (s.length >= 3) { + if (!s[2].trim().equals(statement.qualifier)) { + throw new RuntimeException(sourcefile + ", line " + + lineNumber + ", is malformed: " + s[2]); + } + } + + break; + + case "to": + case "with": + if (statement == null) { + throw new RuntimeException(sourcefile + ", line " + + lineNumber + ", is malformed"); + } + + hasTargets = true; + String qualifier = statement.qualifier; + i = l.indexOf(qualifier, index) + qualifier.length() + 1; + l = i < l.length() ? l.substring(i, l.length()).trim() : ""; + index = 0; + break; + } + + if (index >= l.length()) { + // skip to next line + continue; + } + + // comment block starts + if (l.startsWith("/*")) { + while (l.indexOf("*/") == -1) { // end comment block + l = lines.get(lineNumber++).trim(); + } + index = l.indexOf("*/") + 2; + if (index >= l.length()) { + continue; + } else { + // rest of the line + l = l.substring(index, l.length()).trim(); + index = 0; + } + } + + if (l.startsWith("//")) { + index = l.length(); + continue; + } + + if (statement == null) { + throw new RuntimeException(sourcefile + ", line " + + lineNumber + ": missing keyword?"); + } + + if (!hasTargets) { + continue; + } + + if (index >= l.length()) { + throw new RuntimeException(sourcefile + ", line " + + lineNumber + ": " + l); + } + + // parse the target module of exports, opens, or provides + Statement stmt = statement; + + int terminal = l.indexOf(';', index); + // determine up to which position to parse + int pos = terminal != -1 ? terminal : l.length(); + // parse up to comments + int pos1 = l.indexOf("//", index); + if (pos1 != -1 && pos1 < pos) { + pos = pos1; + } + int pos2 = l.indexOf("/*", index); + if (pos2 != -1 && pos2 < pos) { + pos = pos2; + } + // target module(s) for qualitifed exports or opens + // or provider implementation class(es) + String rhs = l.substring(index, pos).trim(); + index += rhs.length(); + trace("rhs: index=%d [%s] [line: %s]%n", index, rhs, l); + + String[] targets = rhs.split(","); + for (String t : targets) { + String n = t.trim(); + if (n.length() > 0) + stmt.addTarget(n); + } + + // start next statement + if (pos == terminal) { + statement = null; + hasTargets = false; + index = terminal + 1; + } + l = index < l.length() ? l.substring(index, l.length()).trim() : ""; + index = 0; + } + lineNumber++; - String[] s = l.trim().split("\\s+"); - String keyword = s[0].trim(); - int nextIndex = keyword.length(); - String exp = null; - int n = l.length(); - switch (keyword) { - case "exports": - boolean inExportsTo = false; - // assume package name immediately after exports - exp = s[1].trim(); - if (s.length >= 3) { - nextIndex = l.indexOf(exp, nextIndex) + exp.length(); - if (s[2].trim().equals("to")) { - inExportsTo = true; - n = l.indexOf("to", nextIndex) + "to".length(); - } else { - throw new RuntimeException(sourcefile + ", line " + - lineNumber + ", is malformed: " + s[2]); - } - } - - // inject the extra targets after "to" - if (inExportsTo) { - writer.println(injectExportTargets(exp, l, n)); - } else { - writer.println(l); - } - break; - case "to": - if (exp == null) { - throw new RuntimeException(sourcefile + ", line " + - lineNumber + ", is malformed"); - } - n = l.indexOf("to", nextIndex) + "to".length(); - writer.println(injectExportTargets(exp, l, n)); - break; - case "}": - doAugments(writer); - // fall through - default: - writer.println(l); - // reset exports - exp = null; - } } } } - private String injectExportTargets(String pn, String exp, int pos) { - Set<String> targets = exportsTo.remove(pn); - if (targets != null) { - StringBuilder sb = new StringBuilder(); - // inject the extra targets after the given pos - sb.append(exp.substring(0, pos)) - .append("\n\t") - .append(targets.stream() - .collect(Collectors.joining(",", "", ","))) - .append(" /* injected */"); - if (pos < exp.length()) { - // print the remaining statement followed "to" - sb.append("\n\t") - .append(exp.substring(pos+1, exp.length())); + static class Statement { + final String directive; + final String qualifier; + final String name; + final Set<String> targets = new LinkedHashSet<>(); + final boolean ordered; + + Statement(String directive, String qualifier, String name) { + this(directive, qualifier, name, false); + } + + Statement(String directive, String qualifier, String name, boolean ordered) { + this.directive = directive; + this.qualifier = qualifier; + this.name = name; + this.ordered = ordered; + } + + Statement addTarget(String mn) { + if (mn.isEmpty()) + throw new IllegalArgumentException("empty module name"); + targets.add(mn); + return this; + } + + boolean isQualified() { + return targets.size() > 0; + } + + boolean isUnqualified() { + return targets.isEmpty(); + } + + /** + * Returns true if this statement is unqualified or it has + * at least one target in the given names. + */ + boolean filter(Set<String> names) { + if (isUnqualified()) { + return true; + } else { + return targets.stream() + .filter(mn -> names.contains(mn)) + .findAny().isPresent(); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(" "); + sb.append(directive).append(" ").append(name); + if (targets.isEmpty()) { + sb.append(";"); + } else if (targets.size() == 1) { + sb.append(" ").append(qualifier) + .append(orderedTargets().collect(joining(",", " ", ";"))); + } else { + sb.append(" ").append(qualifier) + .append(orderedTargets() + .map(target -> String.format(" %s", target)) + .collect(joining(",\n", "\n", ";"))); } return sb.toString(); - } else { - return exp; + } + + public Stream<String> orderedTargets() { + return ordered ? targets.stream() + : targets.stream().sorted(); } } - private void doAugments(PrintWriter writer) { - if ((exports.size() + exportsTo.size() + uses.size() + provides.size()) == 0) - return; - - writer.println(" // augmented from module-info.java.extra"); - exports.stream() - .sorted() - .forEach(e -> writer.format(" exports %s;%n", e)); - // remaining injected qualified exports - exportsTo.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .map(e -> String.format(" exports %s to%n%s;", e.getKey(), - e.getValue().stream().sorted() - .map(mn -> String.format(" %s", mn)) - .collect(Collectors.joining(",\n")))) - .forEach(writer::println); - uses.stream().sorted() - .forEach(s -> writer.format(" uses %s;%n", s)); - provides.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .flatMap(e -> e.getValue().stream().sorted() - .map(impl -> String.format(" provides %s with %s;", - e.getKey(), impl))) - .forEach(writer::println); + static void trace(String fmt, Object... params) { + if (verbose) { + System.out.format(fmt, params); + } } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/make/src/classes/build/tools/module/ModuleInfoExtraTest.java Thu Dec 01 21:39:49 2016 +0000 @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package build.tools.module; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import build.tools.module.GenModuleInfoSource.Statement; + +/** + * Sanity test for GenModuleInfoSource tool + */ +public class ModuleInfoExtraTest { + private static final Path DIR = Paths.get("test"); + public static void main(String... args) throws Exception { + if (args.length != 0) + GenModuleInfoSource.verbose = true; + + ModuleInfoExtraTest test = new ModuleInfoExtraTest("m", "m1", "m2", "m3"); + test.run(); + } + + String[] moduleInfo = new String[] { + "exports p", + "to", + " // comment", + " /* comment */ m1", + ",", + "m2,m3", + " ;", + "exports q to m1;", + "provides s with /* ", + " comment */ impl ; // comment", + "provides s1", + " with ", + " impl1, impl2;" + }; + + String[] moduleInfoExtra = new String[] { + "exports q", + "to", + " m2 // comment", + " /* comment */;", + " ;", + "opens p.q ", + " to /* comment */ m3", + " , // m1", + " /* comment */, m4;", + "provides s1 with impl3;" + }; + + String[] test1 = new String[] { + "exports p1 to m1;", + "exports p2" + }; + + String[] test2 = new String[] { + "exports to m1;" + }; + + String[] test3 = new String[]{ + "exports p3 to m1;", + " m2, m3;" + }; + + String[] test4 = new String[]{ + "provides s with impl1;", // typo ; should be , + " impl2, impl3;" + }; + + String[] test5 = new String[]{ + "uses s3", + "provides s3 with impl1,", + " impl2, impl3;" + }; + + final Builder builder; + ModuleInfoExtraTest(String name, String... modules) { + this.builder = new Builder(name).modules(modules); + } + + void run() throws IOException { + testModuleInfo(); + errorCases(); + } + + + void testModuleInfo() throws IOException { + GenModuleInfoSource source = builder.sourceFile(moduleInfo).build(); + Set<String> targetsP = new HashSet<>(); + targetsP.add("m1"); + targetsP.add("m2"); + targetsP.add("m3"); + + Set<String> targetsQ = new HashSet<>(); + targetsQ.add("m1"); + + Set<String> providerS = new HashSet<>(); + providerS.add("impl"); + + Set<String> providerS1 = new HashSet<>(); + providerS1.add("impl1"); + providerS1.add("impl2"); + + Set<String> opensPQ = new HashSet<>(); + + check(source, targetsP, targetsQ, opensPQ, providerS, providerS1); + + // augment with extra + Path file = DIR.resolve("extra"); + Files.write(file, Arrays.asList(moduleInfoExtra)); + source = builder.build(file); + + targetsQ.add("m2"); + providerS1.add("impl3"); + + opensPQ.add("m3"); + check(source, targetsP, targetsQ, opensPQ, providerS, providerS1); + } + + void check(GenModuleInfoSource source, + Set<String> targetsP, + Set<String> targetsQ, + Set<String> opensPQ, + Set<String> providerS, + Set<String> providerS1) { + source.moduleInfo.print(new PrintWriter(System.out, true)); + Statement export = source.moduleInfo.exports.get("p"); + if (!export.targets.equals(targetsP)) { + throw new Error("unexpected: " + export); + } + + export = source.moduleInfo.exports.get("q"); + if (!export.targets.equals(targetsQ)) { + throw new Error("unexpected: " + export); + } + + Statement provides = source.moduleInfo.provides.get("s"); + if (!provides.targets.equals(providerS)) { + throw new Error("unexpected: " + provides); + } + + provides = source.moduleInfo.provides.get("s1"); + if (!provides.targets.equals(providerS1)) { + throw new Error("unexpected: " + provides); + } + } + + + + void errorCases() throws IOException { + fail(test1); + fail(test2); + fail(test3); + fail(test4); + fail(test5); + } + + void fail(String... extras) throws IOException { + Path file = DIR.resolve("test1"); + Files.write(file, Arrays.asList(extras)); + try { + builder.build(file); + } catch (RuntimeException e) { + if (!e.getMessage().matches("test/test1, line .* is malformed.*") && + !e.getMessage().matches("test/test1, line .* missing keyword.*")) { + throw e; + } + } + } + + static class Builder { + final String moduleName; + final Path sourceFile; + final Set<String> modules = new HashSet<>(); + public Builder(String name) { + this.moduleName = name; + this.sourceFile = DIR.resolve(name).resolve("module-info.java"); + } + + public Builder modules(String... names) { + Arrays.stream(names).forEach(modules::add); + return this; + } + + public Builder sourceFile(String... lines) throws IOException { + Files.createDirectories(sourceFile.getParent()); + try (BufferedWriter bw = Files.newBufferedWriter(sourceFile); + PrintWriter writer = new PrintWriter(bw)) { + writer.format("module %s {%n", moduleName); + for (String l : lines) { + writer.println(l); + } + writer.println("}"); + } + return this; + } + + public GenModuleInfoSource build() throws IOException { + return build(Collections.emptyList()); + } + + public GenModuleInfoSource build(Path extraFile) throws IOException { + return build(Collections.singletonList(extraFile)); + } + + public GenModuleInfoSource build(List<Path> extraFiles) throws IOException { + return new GenModuleInfoSource(sourceFile, extraFiles, modules); + } + } + +}
--- a/src/java.base/share/classes/com/sun/java/util/jar/pack/intrinsic.properties Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/com/sun/java/util/jar/pack/intrinsic.properties Thu Dec 01 21:39:49 2016 +0000 @@ -15,12 +15,12 @@ pack.class.attribute.CompilationID = RUH # Module attributes, supported by the tool and not JSR-200 -pack.class.attribute.Module = NH[RUHFH]NH[RUHNH[RUH]]NH[RCH]NH[RCHRCH] -pack.class.attribute.ConcealedPackages = NH[RUH] -pack.class.attribute.Version = RUH -pack.class.attribute.MainClass = RCH -pack.class.attribute.TargetPlatform = RUHRUHRUH -pack.class.attribute.Hashes = RUHNH[RUHRUH] +pack.class.attribute.Module = RUHFHNH[RUHFH]NH[RUHFHNH[RUH]]NH[RUHFHNH[RUH]]NH[RCH]NH[RCHNH[RCH]] +pack.class.attribute.ModulePackages = NH[RUH] +pack.class.attribute.ModuleVersion = RUH +pack.class.attribute.ModuleMainClass = RCH +pack.class.attribute.ModuleTarget = RUHRUHRUH +pack.class.attribute.ModuleHashes = RUHNH[RUHNH[B]] # Note: Zero-length ("marker") attributes do not need to be specified here.
--- a/src/java.base/share/classes/java/io/FilePermission.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/io/FilePermission.java Thu Dec 01 21:39:49 2016 +0000 @@ -216,19 +216,28 @@ GetPropertyAction.privilegedGetProperty("user.dir")); /** - * A private constructor like a clone, only npath2 is not touched. + * A private constructor that clones some and updates some, + * always with a different name. * @param input */ - private FilePermission(String name, FilePermission input) { + private FilePermission(String name, + FilePermission input, + Path npath, + Path npath2, + int mask, + String actions) { super(name); - this.npath = input.npath; - this.actions = input.actions; + // Customizables + this.npath = npath; + this.npath2 = npath2; + this.actions = actions; + this.mask = mask; + // Cloneds this.allFiles = input.allFiles; this.invalid = input.invalid; this.recursive = input.recursive; this.directory = input.directory; this.cpath = input.cpath; - this.mask = input.mask; } /** @@ -261,10 +270,12 @@ // different than the original so that when one is // added to a FilePermissionCollection it will not // be merged with the original one. - FilePermission np = new FilePermission( - input.getName()+"#plus", input); - np.npath2 = npath2; - return np; + return new FilePermission(input.getName() + "#plus", + input, + input.npath, + npath2, + input.mask, + input.actions); } } return input; @@ -274,10 +285,12 @@ Path npath2 = altPath(input.npath); if (npath2 != null) { // New name, see above. - FilePermission np = new FilePermission( - input.getName()+"#using", input); - np.npath = npath2; - return np; + return new FilePermission(input.getName() + "#using", + input, + npath2, + null, + input.mask, + input.actions); } } return null; @@ -981,6 +994,20 @@ s.defaultReadObject(); init(getMask(actions)); } + + /** + * Create a cloned FilePermission with a different actions. + * @param effective the new actions + * @return a new object + */ + FilePermission withNewActions(int effective) { + return new FilePermission(this.getName(), + this, + this.npath, + this.npath2, + effective, + null); + } } /** @@ -1048,8 +1075,7 @@ FilePermission fp = (FilePermission)permission; // Add permission to map if it is absent, or replace with new - // permission if applicable. NOTE: cannot use lambda for - // remappingFunction parameter until JDK-8076596 is fixed. + // permission if applicable. perms.merge(fp.getName(), fp, new java.util.function.BiFunction<>() { @Override @@ -1063,7 +1089,8 @@ return newVal; } if (effective != oldMask) { - return new FilePermission(fp.getName(), effective); + return ((FilePermission)newVal) + .withNewActions(effective); } } return existingVal;
--- a/src/java.base/share/classes/java/lang/Class.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/Class.java Thu Dec 01 21:39:49 2016 +0000 @@ -26,46 +26,55 @@ package java.lang; import java.lang.annotation.Annotation; +import java.lang.module.ModuleDescriptor.Version; +import java.lang.module.ModuleFinder; import java.lang.module.ModuleReader; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Array; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.GenericDeclaration; -import java.lang.reflect.Member; -import java.lang.reflect.Field; -import java.lang.reflect.Executable; -import java.lang.reflect.Method; -import java.lang.reflect.Module; -import java.lang.reflect.Constructor; -import java.lang.reflect.Modifier; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.AnnotatedType; -import java.lang.reflect.Proxy; import java.lang.ref.SoftReference; import java.io.IOException; import java.io.InputStream; import java.io.ObjectStreamField; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Layer; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Module; +import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Set; -import java.util.Map; -import java.util.HashMap; -import java.util.Objects; import java.util.StringJoiner; +import java.util.stream.Collectors; + import jdk.internal.HotSpotIntrinsicCandidate; import jdk.internal.loader.BootLoader; import jdk.internal.loader.BuiltinClassLoader; +import jdk.internal.loader.ResourceHelper; +import jdk.internal.misc.SharedSecrets; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; +import jdk.internal.module.ModuleHashes; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.ConstantPool; import jdk.internal.reflect.Reflection; @@ -442,21 +451,10 @@ PrivilegedAction<ClassLoader> pa = module::getClassLoader; ClassLoader cl = AccessController.doPrivileged(pa); - if (module.isNamed() && cl != null) { - return cl.loadLocalClass(module, name); - } - - final Class<?> c; if (cl != null) { - c = cl.loadLocalClass(name); + return cl.loadClass(module, name); } else { - c = BootLoader.loadClassOrNull(name); - } - - if (c != null && c.getModule() == module) { - return c; - } else { - return null; + return BootLoader.loadClass(module, name); } } @@ -979,7 +977,7 @@ } // cached package name - private String packageName; + private transient String packageName; /** * Returns the interfaces directly implemented by the class or interface @@ -1976,6 +1974,22 @@ return method; } + /** + * Returns a {@code Method} object that reflects the specified public + * member method of the class or interface represented by this + * {@code Class} object. + * + * @param name the name of the method + * @param parameterTypes the list of parameters + * @return the {@code Method} object that matches the specified + * {@code name} and {@code parameterTypes}; {@code null} + * if the method is not found or the name is + * "<init>"or "<clinit>". + */ + Method getMethodOrNull(String name, Class<?>... parameterTypes) { + return getMethod0(name, parameterTypes, true); + } + /** * Returns a {@code Constructor} object that reflects the specified @@ -2367,12 +2381,17 @@ } /** - * Finds a resource with a given name. If this class is in a named {@link - * Module Module}, and the caller of this method is in the same module, - * then this method will attempt to find the resource in that module. - * Otherwise, the rules for searching resources - * associated with a given class are implemented by the defining - * {@linkplain ClassLoader class loader} of the class. This method + * Finds a resource with a given name. + * + * <p> If this class is in a named {@link Module Module} then this method + * will attempt to find the resource in the module by means of the absolute + * resource name, subject to the rules for encapsulation specified in the + * {@code Module} {@link Module#getResourceAsStream getResourceAsStream} + * method. + * + * <p> Otherwise, if this class is not in a named module then the rules for + * searching resources associated with a given class are implemented by the + * defining {@linkplain ClassLoader class loader} of the class. This method * delegates to this object's class loader. If this object was loaded by * the bootstrap class loader, the method delegates to {@link * ClassLoader#getSystemResourceAsStream}. @@ -2400,8 +2419,11 @@ * </ul> * * @param name name of the desired resource - * @return A {@link java.io.InputStream} object or {@code null} if - * no resource with this name is found + * @return A {@link java.io.InputStream} object; {@code null} if no + * resource with this name is found, the resource is in a package + * that is not {@link Module#isOpen(String, Module) open} to at + * least the caller module, or access to the resource is denied + * by the security manager. * @throws NullPointerException If {@code name} is {@code null} * @since 1.1 */ @@ -2409,35 +2431,41 @@ public InputStream getResourceAsStream(String name) { name = resolveName(name); - // if this Class and the caller are in the same named module - // then attempt to get an input stream to the resource in the - // module Module module = getModule(); if (module.isNamed()) { - Class<?> caller = Reflection.getCallerClass(); - if (caller != null && caller.getModule() == module) { - ClassLoader cl = getClassLoader0(); - String mn = module.getName(); - try { - - // special-case built-in class loaders to avoid the - // need for a URL connection - if (cl == null) { - return BootLoader.findResourceAsStream(mn, name); - } else if (cl instanceof BuiltinClassLoader) { - return ((BuiltinClassLoader) cl).findResourceAsStream(mn, name); - } else { - URL url = cl.findResource(mn, name); - return (url != null) ? url.openStream() : null; + if (!ResourceHelper.isSimpleResource(name)) { + Module caller = Reflection.getCallerClass().getModule(); + if (caller != module) { + Set<String> packages = module.getDescriptor().packages(); + String pn = ResourceHelper.getPackageName(name); + if (packages.contains(pn) && !module.isOpen(pn, caller)) { + // resource is in package not open to caller + return null; } - - } catch (IOException | SecurityException e) { - return null; } } + + String mn = module.getName(); + ClassLoader cl = getClassLoader0(); + try { + + // special-case built-in class loaders to avoid the + // need for a URL connection + if (cl == null) { + return BootLoader.findResourceAsStream(mn, name); + } else if (cl instanceof BuiltinClassLoader) { + return ((BuiltinClassLoader) cl).findResourceAsStream(mn, name); + } else { + URL url = cl.findResource(mn, name); + return (url != null) ? url.openStream() : null; + } + + } catch (IOException | SecurityException e) { + return null; + } } - // this Class and caller not in the same named module + // unnamed module ClassLoader cl = getClassLoader0(); if (cl == null) { return ClassLoader.getSystemResourceAsStream(name); @@ -2447,12 +2475,17 @@ } /** - * Finds a resource with a given name. If this class is in a named {@link - * Module Module}, and the caller of this method is in the same module, - * then this method will attempt to find the resource in that module. - * Otherwise, the rules for searching resources - * associated with a given class are implemented by the defining - * {@linkplain ClassLoader class loader} of the class. This method + * Finds a resource with a given name. + * + * <p> If this class is in a named {@link Module Module} then this method + * will attempt to find the resource in the module by means of the absolute + * resource name, subject to the rules for encapsulation specified in the + * {@code Module} {@link Module#getResourceAsStream getResourceAsStream} + * method. + * + * <p> Otherwise, if this class is not in a named module then the rules for + * searching resources associated with a given class are implemented by the + * defining {@linkplain ClassLoader class loader} of the class. This method * delegates to this object's class loader. If this object was loaded by * the bootstrap class loader, the method delegates to {@link * ClassLoader#getSystemResource}. @@ -2479,35 +2512,46 @@ * </ul> * * @param name name of the desired resource - * @return A {@link java.net.URL} object; {@code null} if no - * resource with this name is found or the resource cannot - * be located by a URL. + * @return A {@link java.net.URL} object; {@code null} if no resource with + * this name is found, the resource cannot be located by a URL, the + * resource is in a package that is not + * {@link Module#isOpen(String, Module) open} to at least the caller + * module, or access to the resource is denied by the security + * manager. + * @throws NullPointerException If {@code name} is {@code null} * @since 1.1 */ @CallerSensitive public URL getResource(String name) { name = resolveName(name); - // if this Class and the caller are in the same named module - // then attempt to get URL to the resource in the module Module module = getModule(); if (module.isNamed()) { - Class<?> caller = Reflection.getCallerClass(); - if (caller != null && caller.getModule() == module) { - String mn = getModule().getName(); - ClassLoader cl = getClassLoader0(); - try { - if (cl == null) { - return BootLoader.findResource(mn, name); - } else { - return cl.findResource(mn, name); + if (!ResourceHelper.isSimpleResource(name)) { + Module caller = Reflection.getCallerClass().getModule(); + if (caller != module) { + Set<String> packages = module.getDescriptor().packages(); + String pn = ResourceHelper.getPackageName(name); + if (packages.contains(pn) && !module.isOpen(pn, caller)) { + // resource is in package not open to caller + return null; } - } catch (IOException ioe) { - return null; } } + String mn = getModule().getName(); + ClassLoader cl = getClassLoader0(); + try { + if (cl == null) { + return BootLoader.findResource(mn, name); + } else { + return cl.findResource(mn, name); + } + } catch (IOException ioe) { + return null; + } } + // unnamed module ClassLoader cl = getClassLoader0(); if (cl == null) { return ClassLoader.getSystemResource(name); @@ -2632,9 +2676,6 @@ * if name is absolute */ private String resolveName(String name) { - if (name == null) { - return name; - } if (!name.startsWith("/")) { Class<?> c = this; while (c.isArray()) {
--- a/src/java.base/share/classes/java/lang/ClassLoader.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/ClassLoader.java Thu Dec 01 21:39:49 2016 +0000 @@ -42,15 +42,14 @@ import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; -import java.util.HashSet; import java.util.Hashtable; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; import java.util.Spliterator; import java.util.Spliterators; import java.util.Stack; -import java.util.NoSuchElementException; import java.util.Vector; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; @@ -59,7 +58,6 @@ import java.util.stream.StreamSupport; import jdk.internal.perf.PerfCounter; -import jdk.internal.module.ServicesCatalog; import jdk.internal.loader.BootLoader; import jdk.internal.loader.ClassLoaders; import jdk.internal.misc.SharedSecrets; @@ -411,7 +409,6 @@ this(checkCreateClassLoader(), null, parent); } - /** * Creates a new class loader using the <tt>ClassLoader</tt> returned by * the method {@link #getSystemClassLoader() @@ -431,7 +428,6 @@ this(checkCreateClassLoader(), null, getSystemClassLoader()); } - /** * Returns the name of this class loader or {@code null} if * this class loader is not named. @@ -581,7 +577,7 @@ * @return The resulting {@code Class} object in a module defined by * this class loader, or {@code null} if the class could not be found. */ - final Class<?> loadLocalClass(Module module, String name) { + final Class<?> loadClass(Module module, String name) { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); @@ -597,34 +593,6 @@ } /** - * Loads the class with the specified <a href="#name">binary name</a> - * defined by this class loader. This method returns {@code null} - * if the class could not be found. - * - * @apiNote This method does not delegate to the parent class loader. - * - * @param name - * The <a href="#name">binary name</a> of the class - * - * @return The resulting {@code Class} object in a module defined by - * this class loader, or {@code null} if the class could not be found. - */ - final Class<?> loadLocalClass(String name) { - synchronized (getClassLoadingLock(name)) { - // First, check if the class has already been loaded - Class<?> c = findLoadedClass(name); - if (c == null) { - try { - return findClass(name); - } catch (ClassNotFoundException e) { - // ignore - } - } - return c; - } - } - - /** * Returns the lock object for class loading operations. * For backward compatibility, the default implementation of this method * behaves as follows. If this ClassLoader object is registered as @@ -724,12 +692,17 @@ * should override this method. * * @apiNote This method returns {@code null} rather than throwing - * {@code ClassNotFoundException} if the class could not be found + * {@code ClassNotFoundException} if the class could not be found. * - * @implSpec The default implementation returns {@code null}. + * @implSpec The default implementation attempts to find the class by + * invoking {@link #findClass(String)} when the {@code moduleName} is + * {@code null}. It otherwise returns {@code null}. * * @param moduleName - * The module name + * The module name; or {@code null} to find the class in the + * {@linkplain #getUnnamedModule() unnamed module} for this + * class loader + * @param name * The <a href="#name">binary name</a> of the class * @@ -739,6 +712,11 @@ * @since 9 */ protected Class<?> findClass(String moduleName, String name) { + if (moduleName == null) { + try { + return findClass(name); + } catch (ClassNotFoundException ignore) { } + } return null; } @@ -1286,10 +1264,20 @@ * Class loader implementations that support the loading from modules * should override this method. * - * @implSpec The default implementation returns {@code null}. + * @apiNote This method is the basis for the {@code Class} {@link + * Class#getResource getResource} and {@link Class#getResourceAsStream + * getResourceAsStream} methods. It is not subject to the rules for + * encapsulation specified by {@code Module} {@link + * Module#getResourceAsStream getResourceAsStream}. + * + * @implSpec The default implementation attempts to find the resource by + * invoking {@link #findResource(String)} when the {@code moduleName} is + * {@code null}. It otherwise returns {@code null}. * * @param moduleName - * The module name + * The module name; or {@code null} to find a resource in the + * {@linkplain #getUnnamedModule() unnamed module} for this + * class loader * @param name * The resource name * @@ -1306,7 +1294,11 @@ * @since 9 */ protected URL findResource(String moduleName, String name) throws IOException { - return null; + if (moduleName == null) { + return findResource(name); + } else { + return null; + } } /** @@ -1314,9 +1306,6 @@ * (images, audio, text, etc) that can be accessed by class code in a way * that is independent of the location of the code. * - * Resources in a named module are private to that module. This method does - * not find resource in named modules. - * * <p> The name of a resource is a '<tt>/</tt>'-separated path name that * identifies the resource. * @@ -1325,16 +1314,31 @@ * built-in to the virtual machine is searched. That failing, this method * will invoke {@link #findResource(String)} to find the resource. </p> * - * @apiNote When overriding this method it is recommended that an - * implementation ensures that any delegation is consistent with the {@link + * <p> Resources in named modules are subject to the encapsulation rules + * specified by {@link Module#getResourceAsStream Module.getResourceAsStream}. + * Additionally, and except for the special case where the resource has a + * name ending with "{@code .class}", this method will only find resources in + * packages of named modules when the package is {@link Module#isOpen(String) + * opened} unconditionally (even if the caller of this method is in the + * same module as the resource). </p> + * + * @apiNote Where several modules are defined to the same class loader, + * and where more than one module contains a resource with the given name, + * then the ordering that modules are searched is not specified and may be + * very unpredictable. + * When overriding this method it is recommended that an implementation + * ensures that any delegation is consistent with the {@link * #getResources(java.lang.String) getResources(String)} method. * * @param name * The resource name * - * @return A <tt>URL</tt> object for reading the resource, or - * <tt>null</tt> if the resource could not be found or the invoker - * doesn't have adequate privileges to get the resource. + * @return {@code URL} object for reading the resource; {@code null} if + * the resource could not be found, a {@code URL} could not be + * constructed to locate the resource, the resource is in a package + * that is not opened unconditionally, or access to the resource is + * denied by the security manager. + * * @throws NullPointerException If {@code name} is {@code null} * * @since 1.1 @@ -1358,16 +1362,24 @@ * (images, audio, text, etc) that can be accessed by class code in a way * that is independent of the location of the code. * - * Resources in a named module are private to that module. This method does - * not find resources in named modules. - * - * <p>The name of a resource is a <tt>/</tt>-separated path name that + * <p> The name of a resource is a <tt>/</tt>-separated path name that * identifies the resource. * - * <p> The search order is described in the documentation for {@link - * #getResource(String)}. </p> + * <p> The delegation order for searching is described in the documentation + * for {@link #getResource(String)}. </p> * - * @apiNote When overriding this method it is recommended that an + * <p> Resources in named modules are subject to the encapsulation rules + * specified by {@link Module#getResourceAsStream Module.getResourceAsStream}. + * Additionally, and except for the special case where the resource has a + * name ending with "{@code .class}", this method will only find resources in + * packages of named modules when the package is {@link Module#isOpen(String) + * opened} unconditionally (even if the caller of this method is in the + * same module as the resource).</p> + * + * @apiNote Where several modules are defined to the same class loader, + * and where more than one module contains a resource with the given name, + * then the ordering is not specified and may be very unpredictable. + * When overriding this method it is recommended that an * implementation ensures that any delegation is consistent with the {@link * #getResource(java.lang.String) getResource(String)} method. This should * ensure that the first element returned by the Enumeration's @@ -1378,9 +1390,11 @@ * The resource name * * @return An enumeration of {@link java.net.URL <tt>URL</tt>} objects for - * the resource. If no resources could be found, the enumeration - * will be empty. Resources that the class loader doesn't have - * access to will not be in the enumeration. + * the resource. If no resources could be found, the enumeration + * will be empty. Resources for which a {@code URL} cannot be + * constructed, are in package that is not opened unconditionally, + * or access to the resource is denied by the security manager, + * are not returned in the enumeration. * * @throws IOException * If I/O errors occur @@ -1410,9 +1424,6 @@ * can be accessed by class code in a way that is independent of the * location of the code. * - * Resources in a named module are private to that module. This method does - * not find resources in named modules. - * * <p> The name of a resource is a {@code /}-separated path name that * identifies the resource. * @@ -1424,6 +1435,14 @@ * exception is wrapped in an {@link UncheckedIOException} that is then * thrown. * + * <p> Resources in named modules are subject to the encapsulation rules + * specified by {@link Module#getResourceAsStream Module.getResourceAsStream}. + * Additionally, and except for the special case where the resource has a + * name ending with "{@code .class}", this method will only find resources in + * packages of named modules when the package is {@link Module#isOpen(String) + * opened} unconditionally (even if the caller of this method is in the + * same module as the resource). </p> + * * @apiNote When overriding this method it is recommended that an * implementation ensures that any delegation is consistent with the {@link * #getResource(java.lang.String) getResource(String)} method. This should @@ -1434,9 +1453,10 @@ * The resource name * * @return A stream of resource {@link java.net.URL URL} objects. If no - * resources could be found, the stream will be empty. Resources - * that the class loader doesn't have access to will not be in the - * stream. + * resources could be found, the stream will be empty. Resources + * for which a {@code URL} cannot be constructed, are in a package + * that is not opened unconditionally, or access to the resource + * is denied by the security manager, will not be in the stream. * * @throws NullPointerException If {@code name} is {@code null} * @@ -1462,14 +1482,21 @@ * Finds the resource with the given name. Class loader implementations * should override this method to specify where to find resources. * - * Resources in a named module are private to that module. This method does - * not find resources in named modules defined to this class loader. + * <p> For resources in named modules then the method must implement the + * rules for encapsulation specified in the {@code Module} {@link + * Module#getResourceAsStream getResourceAsStream} method. Additionally, + * it must not find non-"{@code .class}" resources in packages of named + * modules unless the package is {@link Module#isOpen(String) opened} + * unconditionally. </p> * * @param name * The resource name * - * @return A <tt>URL</tt> object for reading the resource, or - * <tt>null</tt> if the resource could not be found + * @return {@code URL} object for reading the resource; {@code null} if + * the resource could not be found, a {@code URL} could not be + * constructed to locate the resource, the resource is in a package + * that is not opened unconditionally, or access to the resource is + * denied by the security manager. * * @since 1.2 */ @@ -1483,14 +1510,22 @@ * implementations should override this method to specify where to load * resources from. * - * Resources in a named module are private to that module. This method does - * not find resources in named modules defined to this class loader. + * <p> For resources in named modules then the method must implement the + * rules for encapsulation specified in the {@code Module} {@link + * Module#getResourceAsStream getResourceAsStream} method. Additionally, + * it must not find non-"{@code .class}" resources in packages of named + * modules unless the package is {@link Module#isOpen(String) opened} + * unconditionally. </p> * * @param name * The resource name * * @return An enumeration of {@link java.net.URL <tt>URL</tt>} objects for - * the resources + * the resource. If no resources could be found, the enumeration + * will be empty. Resources for which a {@code URL} cannot be + * constructed, are in a package that is not opened unconditionally, + * or access to the resource is denied by the security manager, + * are not returned in the enumeration. * * @throws IOException * If I/O errors occur @@ -1498,7 +1533,7 @@ * @since 1.2 */ protected Enumeration<URL> findResources(String name) throws IOException { - return java.util.Collections.emptyEnumeration(); + return Collections.emptyEnumeration(); } /** @@ -1549,14 +1584,21 @@ * classes. This method locates the resource through the system class * loader (see {@link #getSystemClassLoader()}). * - * Resources in a named module are private to that module. This method does - * not find resources in named modules. + * <p> Resources in named modules are subject to the encapsulation rules + * specified by {@link Module#getResourceAsStream Module.getResourceAsStream}. + * Additionally, and except for the special case where the resource has a + * name ending with "{@code .class}", this method will only find resources in + * packages of named modules when the package is {@link Module#isOpen(String) + * opened} unconditionally. </p> * * @param name * The resource name * - * @return A {@link java.net.URL <tt>URL</tt>} object for reading the - * resource, or <tt>null</tt> if the resource could not be found + * @return A {@link java.net.URL <tt>URL</tt>} to the resource; {@code + * null} if the resource could not be found, a URL could not be + * constructed to locate the resource, the resource is in a package + * that is not opened unconditionally or access to the resource is + * denied by the security manager. * * @since 1.1 */ @@ -1570,17 +1612,25 @@ * {@link java.util.Enumeration <tt>Enumeration</tt>} of {@link * java.net.URL <tt>URL</tt>} objects. * - * Resources in a named module are private to that module. This method does - * not find resources in named modules. - * * <p> The search order is described in the documentation for {@link * #getSystemResource(String)}. </p> * + * <p> Resources in named modules are subject to the encapsulation rules + * specified by {@link Module#getResourceAsStream Module.getResourceAsStream}. + * Additionally, and except for the special case where the resource has a + * name ending with "{@code .class}", this method will only find resources in + * packages of named modules when the package is {@link Module#isOpen(String) + * opened} unconditionally. </p> + * * @param name * The resource name * - * @return An enumeration of resource {@link java.net.URL <tt>URL</tt>} - * objects + * @return An enumeration of {@link java.net.URL <tt>URL</tt>} objects for + * the resource. If no resources could be found, the enumeration + * will be empty. Resources for which a {@code URL} cannot be + * constructed, are in a package that is not opened unconditionally, + * or access to the resource is denied by the security manager, + * are not returned in the enumeration. * * @throws IOException * If I/O errors occur @@ -1596,17 +1646,24 @@ /** * Returns an input stream for reading the specified resource. * - * Resources in a named module are private to that module. This method does - * not find resources in named modules. - * * <p> The search order is described in the documentation for {@link * #getResource(String)}. </p> * + * <p> Resources in named modules are subject to the encapsulation rules + * specified by {@link Module#getResourceAsStream Module.getResourceAsStream}. + * Additionally, and except for the special case where the resource has a + * name ending with "{@code .class}", this method will only find resources in + * packages of named modules when the package is {@link Module#isOpen(String) + * opened} unconditionally. </p> + * * @param name * The resource name * - * @return An input stream for reading the resource, or <tt>null</tt> - * if the resource could not be found + * @return An input stream for reading the resource; {@code null} if the + * resource could not be found, the resource is in a package that + * is not opened unconditionally, or access to the resource is + * denied by the security manager. + * * @throws NullPointerException If {@code name} is {@code null} * * @since 1.1 @@ -1626,14 +1683,20 @@ * used to load classes. This method locates the resource through the * system class loader (see {@link #getSystemClassLoader()}). * - * Resources in a named module are private to that module. This method does - * not find resources in named modules. + * <p> Resources in named modules are subject to the encapsulation rules + * specified by {@link Module#getResourceAsStream Module.getResourceAsStream}. + * Additionally, and except for the special case where the resource has a + * name ending with "{@code .class}", this method will only find resources in + * packages of named modules when the package is {@link Module#isOpen(String) + * opened} unconditionally. </p> * * @param name * The resource name * - * @return An input stream for reading the resource, or <tt>null</tt> - * if the resource could not be found + * @return An input stream for reading the resource; {@code null} if the + * resource could not be found, the resource is in a package that + * is not opened unconditionally, or access to the resource is + * denied by the security manager. * * @since 1.1 */ @@ -2719,33 +2782,7 @@ private static native AssertionStatusDirectives retrieveDirectives(); - /** - * Returns the ServiceCatalog for modules defined to this class loader - * or {@code null} if this class loader does not have a services catalog. - */ - ServicesCatalog getServicesCatalog() { - return servicesCatalog; - } - - /** - * Returns the ServiceCatalog for modules defined to this class loader, - * creating it if it doesn't already exist. - */ - ServicesCatalog createOrGetServicesCatalog() { - ServicesCatalog catalog = servicesCatalog; - if (catalog == null) { - catalog = ServicesCatalog.create(); - boolean set = trySetObjectField("servicesCatalog", catalog); - if (!set) { - // beaten by someone else - catalog = servicesCatalog; - } - } - return catalog; - } - - // the ServiceCatalog for modules associated with this class loader. - private volatile ServicesCatalog servicesCatalog; + // -- Misc -- /** * Returns the ConcurrentHashMap used as a storage for ClassLoaderValue(s)
--- a/src/java.base/share/classes/java/lang/Deprecated.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/Deprecated.java Thu Dec 01 21:39:49 2016 +0000 @@ -40,6 +40,11 @@ * annotation on a local variable declaration or on a parameter declaration * or a package declaration has no effect on the warnings issued by a compiler. * + * <p>When a module is deprecated, the use of that module in {@code + * requires}, but not in {@code exports} or {@code opens} clauses causes + * a warning to be issued. A module being deprecated does <em>not</em> cause + * warnings to be issued for uses of types within the module. + * * <p>This annotation type has a string-valued element {@code since}. The value * of this element indicates the version in which the annotated program element * was first deprecated. @@ -74,7 +79,7 @@ */ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) +@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE}) public @interface Deprecated { /** * Returns the version in which the annotated element became deprecated.
--- a/src/java.base/share/classes/java/lang/Iterable.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/Iterable.java Thu Dec 01 21:39:49 2016 +0000 @@ -53,10 +53,13 @@ /** * Performs the given action for each element of the {@code Iterable} * until all elements have been processed or the action throws an - * exception. Unless otherwise specified by the implementing class, - * actions are performed in the order of iteration (if an iteration order - * is specified). Exceptions thrown by the action are relayed to the + * exception. Actions are performed in the order of iteration, if that + * order is specified. Exceptions thrown by the action are relayed to the * caller. + * <p> + * The behavior of this method is unspecified if the action performs + * side-effects that modify the underlying source of elements, unless an + * overriding class has specified a concurrent modification policy. * * @implSpec * <p>The default implementation behaves as if:
--- a/src/java.base/share/classes/java/lang/Package.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/Package.java Thu Dec 01 21:39:49 2016 +0000 @@ -398,10 +398,16 @@ if (packageInfo == null) { // find package-info.class defined by loader String cn = packageName() + ".package-info"; - PrivilegedAction<ClassLoader> pa = module()::getClassLoader; + Module module = module(); + PrivilegedAction<ClassLoader> pa = module::getClassLoader; ClassLoader loader = AccessController.doPrivileged(pa); - Class<?> c = loader != null ? loader.loadLocalClass(cn) - : BootLoader.loadClassOrNull(cn); + Class<?> c; + if (loader != null) { + c = loader.loadClass(module, cn); + } else { + c = BootLoader.loadClass(module, cn); + } + if (c != null) { packageInfo = c; } else {
--- a/src/java.base/share/classes/java/lang/SuppressWarnings.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/SuppressWarnings.java Thu Dec 01 21:39:49 2016 +0000 @@ -35,6 +35,9 @@ * a superset of the warnings suppressed in all containing elements. For * example, if you annotate a class to suppress one warning and annotate a * method to suppress another, both warnings will be suppressed in the method. + * However, note that if a warning is suppressed in a {@code + * module-info} file, the suppression applies to elements within the + * file and <em>not</em> to types contained within the module. * * <p>As a matter of style, programmers should always use this annotation * on the most deeply nested element where it is effective. If you want to @@ -49,7 +52,7 @@ * @jls 5.5.2 Checked Casts and Unchecked Casts * @jls 9.6.4.5 @SuppressWarnings */ -@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) +@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { /**
--- a/src/java.base/share/classes/java/lang/System.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/System.java Thu Dec 01 21:39:49 2016 +0000 @@ -38,6 +38,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Layer; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Module; import java.net.URL; @@ -69,7 +70,6 @@ import jdk.internal.logger.LocalizedLoggerWrapper; import jdk.internal.module.ModuleBootstrap; -import jdk.internal.module.ServicesCatalog; /** * The <code>System</code> class contains several useful class fields @@ -1987,7 +1987,10 @@ private static void setJavaLangAccess() { // Allow privileged classes outside of java.lang - SharedSecrets.setJavaLangAccess(new JavaLangAccess(){ + SharedSecrets.setJavaLangAccess(new JavaLangAccess() { + public Method getMethodOrNull(Class<?> klass, String name, Class<?>... parameterTypes) { + return klass.getMethodOrNull(name, parameterTypes); + } public jdk.internal.reflect.ConstantPool getConstantPool(Class<?> klass) { return klass.getConstantPool(); } @@ -2031,12 +2034,6 @@ public Layer getBootLayer() { return bootLayer; } - public ServicesCatalog getServicesCatalog(ClassLoader cl) { - return cl.getServicesCatalog(); - } - public ServicesCatalog createOrGetServicesCatalog(ClassLoader cl) { - return cl.createOrGetServicesCatalog(); - } public ConcurrentHashMap<?, ?> createOrGetClassLoaderValueMap(ClassLoader cl) { return cl.createOrGetClassLoaderValueMap(); }
--- a/src/java.base/share/classes/java/lang/VersionProps.java.template Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/VersionProps.java.template Thu Dec 01 21:39:49 2016 +0000 @@ -123,27 +123,25 @@ /** * In case you were wondering this method is called by java -version. - * Sad that it prints to stderr; would be nicer if default printed on - * stdout. */ - public static void print() { - print(System.err); + public static void print(boolean err) { + print(err, false); } /** * This is the same as print except that it adds an extra line-feed * at the end, typically used by the -showversion in the launcher */ - public static void println() { - print(System.err); - System.err.println(); + public static void println(boolean err) { + print(err, true); } /** - * Give a stream, it will print version info on it. + * Print version info. */ - public static void print(PrintStream ps) { + private static void print(boolean err, boolean newln) { boolean isHeadless = false; + PrintStream ps = err ? System.err : System.out; /* Report that we're running headless if the property is true */ String headless = System.getProperty("java.awt.headless"); @@ -152,10 +150,14 @@ } /* First line: platform version. */ - ps.println(launcher_name + " version \"" + java_version + "\""); + if (err) { + ps.println(launcher_name + " version \"" + java_version + "\""); + } else { + /* Use a format more in line with GNU conventions */ + ps.println(launcher_name + " " + java_version); + } /* Second line: runtime version (ie, libraries). */ - String jdk_debug_level = System.getProperty("jdk.debug", "release"); /* Debug level is not printed for "release" builds */ if ("release".equals(jdk_debug_level)) {
--- a/src/java.base/share/classes/java/lang/annotation/ElementType.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/annotation/ElementType.java Thu Dec 01 21:39:49 2016 +0000 @@ -37,10 +37,10 @@ * <em>type contexts</em> , where annotations apply to types used in * declarations and expressions. * - * <p>The constants {@link #ANNOTATION_TYPE} , {@link #CONSTRUCTOR} , {@link - * #FIELD} , {@link #LOCAL_VARIABLE} , {@link #METHOD} , {@link #PACKAGE} , - * {@link #PARAMETER} , {@link #TYPE} , and {@link #TYPE_PARAMETER} correspond - * to the declaration contexts in JLS 9.6.4.1. + * <p>The constants {@link #ANNOTATION_TYPE}, {@link #CONSTRUCTOR}, {@link + * #FIELD}, {@link #LOCAL_VARIABLE}, {@link #METHOD}, {@link #PACKAGE}, {@link + * #MODULE}, {@link #PARAMETER}, {@link #TYPE}, and {@link #TYPE_PARAMETER} + * correspond to the declaration contexts in JLS 9.6.4.1. * * <p>For example, an annotation whose type is meta-annotated with * {@code @Target(ElementType.FIELD)} may only be written as a modifier for a @@ -107,5 +107,12 @@ * * @since 1.8 */ - TYPE_USE + TYPE_USE, + + /** + * Module declaration. + * + * @since 9 + */ + MODULE }
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java Thu Dec 01 21:39:49 2016 +0000 @@ -1147,12 +1147,14 @@ static MethodHandle bindCaller(MethodHandle mh, Class<?> hostClass) { - // Do not use this function to inject calls into system classes. + // Code in the the boot layer should now be careful while creating method handles or + // functional interface instances created from method references to @CallerSensitive methods, + // it needs to be ensured the handles or interface instances are kept safe and are not passed + // from the boot layer to untrusted code. if (hostClass == null || (hostClass.isArray() || hostClass.isPrimitive() || - hostClass.getName().startsWith("java.") || - hostClass.getName().startsWith("sun."))) { + hostClass.getName().startsWith("java.lang.invoke."))) { throw new InternalError(); // does not happen, and should not anyway } // For simplicity, convert mh to a varargs-like method.
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java Thu Dec 01 21:39:49 2016 +0000 @@ -42,6 +42,7 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Module; import java.lang.reflect.ReflectPermission; import java.nio.ByteOrder; import java.util.ArrayList; @@ -142,6 +143,59 @@ } /** + * Returns a {@link Lookup lookup object} with full capabilities to emulate all + * supported bytecode behaviors, including <a href="MethodHandles.Lookup.html#privacc"> + * private access</a>, on a target class. + * This method checks that a caller, specified as a {@code Lookup} object, is allowed to + * do <em>deep reflection</em> on the target class. If {@code m1} is the module containing + * the {@link Lookup#lookupClass() lookup class}, and {@code m2} is the module containing + * the target class, then this check ensures that + * <ul> + * <li>{@code m1} {@link Module#canRead reads} {@code m2}.</li> + * <li>{@code m2} {@link Module#isOpen(String,Module) opens} the package containing + * the target class to at least {@code m1}.</li> + * <li>The lookup has the {@link Lookup#MODULE MODULE} lookup mode.</li> + * </ul> + * <p> + * If there is a security manager, its {@code checkPermission} method is called to + * check {@code ReflectPermission("suppressAccessChecks")}. + * @apiNote The {@code MODULE} lookup mode serves to authenticate that the lookup object + * was created by code in the caller module (or derived from a lookup object originally + * created by the caller). A lookup object with the {@code MODULE} lookup mode can be + * shared with trusted parties without giving away {@code PRIVATE} and {@code PACKAGE} + * access to the caller. + * @param targetClass the target class + * @param lookup the caller lookup object + * @return a lookup object for the target class, with private access + * @throws IllegalArgumentException if {@code targetClass} is a primitve type or array class + * @throws NullPointerException if {@code targetClass} or {@code caller} is {@code null} + * @throws IllegalAccessException if the access check specified above fails + * @throws SecurityException if denied by the security manager + * @since 9 + */ + public static Lookup privateLookupIn(Class<?> targetClass, Lookup lookup) throws IllegalAccessException { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) sm.checkPermission(ACCESS_PERMISSION); + if (targetClass.isPrimitive()) + throw new IllegalArgumentException(targetClass + " is a primitive class"); + if (targetClass.isArray()) + throw new IllegalArgumentException(targetClass + " is an array class"); + Module targetModule = targetClass.getModule(); + Module callerModule = lookup.lookupClass().getModule(); + if (callerModule != targetModule && targetModule.isNamed()) { + if (!callerModule.canRead(targetModule)) + throw new IllegalAccessException(callerModule + " does not read " + targetModule); + String pn = targetClass.getPackageName(); + assert pn != null && pn.length() > 0 : "unnamed package cannot be in named module"; + if (!targetModule.isOpen(pn, callerModule)) + throw new IllegalAccessException(targetModule + " does not open " + pn + " to " + callerModule); + } + if ((lookup.lookupModes() & Lookup.MODULE) == 0) + throw new IllegalAccessException("lookup does not have MODULE lookup mode"); + return new Lookup(targetClass); + } + + /** * Performs an unchecked "crack" of a * <a href="MethodHandleInfo.html#directmh">direct method handle</a>. * The result is as if the user had obtained a lookup object capable enough @@ -1807,7 +1861,12 @@ return callerClass; } - private boolean hasPrivateAccess() { + /** + * Returns {@code true} if this lookup has {@code PRIVATE} access. + * @return {@code true} if this lookup has {@code PRIVATE} acesss. + * @since 9 + */ + public boolean hasPrivateAccess() { return (allowedModes & PRIVATE) != 0; }
--- a/src/java.base/share/classes/java/lang/module/Configuration.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/module/Configuration.java Thu Dec 01 21:39:49 2016 +0000 @@ -26,14 +26,20 @@ package java.lang.module; import java.io.PrintStream; +import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; +import java.util.Deque; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * The configuration that is the result of resolution or resolution with @@ -46,14 +52,14 @@ * dependences expressed by {@code requires} clauses. * * The <em>dependence graph</em> is augmented with edges that take account of - * implicitly declared dependences ({@code requires public}) to create a + * implicitly declared dependences ({@code requires transitive}) to create a * <em>readability graph</em>. A {@code Configuration} encapsulates the * resulting graph of {@link ResolvedModule resolved modules}. * * <p> Suppose we have the following observable modules: </p> * <pre> {@code * module m1 { requires m2; } - * module m2 { requires public m3; } + * module m2 { requires transitive m3; } * module m3 { } * module m4 { } * } </pre> @@ -70,8 +76,10 @@ * <p> Resolution is an additive process. When computing the transitive closure * then the dependence relation may include dependences on modules in parent * configurations. The result is a <em>relative configuration</em> that is - * relative to a parent configuration and where the readability graph may have - * edges from modules in the configuration to modules in a parent configuration. + * relative to one or more parent configurations and where the readability graph + * may have edges from modules in the configuration to modules in parent + * configurations. + * * </p> * * <p> Suppose we have the following observable modules: </p> @@ -96,9 +104,9 @@ * <p> {@link ModuleDescriptor#isAutomatic() Automatic} modules receive special * treatment during resolution. Each automatic module is resolved so that it * reads all other modules in the configuration and all parent configurations. - * Each automatic module is also resolved as if it {@code requires public} all - * other automatic modules in the configuration (and all automatic modules in - * parent configurations). </p> + * Each automatic module is also resolved as if it {@code requires transitive} + * all other automatic modules in the configuration (and all automatic modules + * in parent configurations). </p> * <h2><a name="servicebinding">Service binding</a></h2> * @@ -171,54 +179,157 @@ // @see Configuration#empty() private static final Configuration EMPTY_CONFIGURATION = new Configuration(); - private final Configuration parent; + // parent configurations, in search order + private final List<Configuration> parents; private final Map<ResolvedModule, Set<ResolvedModule>> graph; private final Set<ResolvedModule> modules; private final Map<String, ResolvedModule> nameToModule; private Configuration() { - this.parent = null; + this.parents = Collections.emptyList(); this.graph = Collections.emptyMap(); this.modules = Collections.emptySet(); this.nameToModule = Collections.emptyMap(); } - private Configuration(Configuration parent, + private Configuration(List<Configuration> parents, Resolver resolver, boolean check) { Map<ResolvedModule, Set<ResolvedModule>> g = resolver.finish(this, check); - Map<String, ResolvedModule> nameToModule = new HashMap<>(); + @SuppressWarnings(value = {"rawtypes", "unchecked"}) + Entry<String, ResolvedModule>[] nameEntries + = (Entry<String, ResolvedModule>[])new Entry[g.size()]; + ResolvedModule[] moduleArray = new ResolvedModule[g.size()]; + int i = 0; for (ResolvedModule resolvedModule : g.keySet()) { - nameToModule.put(resolvedModule.name(), resolvedModule); + moduleArray[i] = resolvedModule; + nameEntries[i] = Map.entry(resolvedModule.name(), resolvedModule); + i++; } - this.parent = parent; + this.parents = Collections.unmodifiableList(parents); this.graph = g; - this.modules = Collections.unmodifiableSet(g.keySet()); - this.nameToModule = Collections.unmodifiableMap(nameToModule); + this.modules = Set.of(moduleArray); + this.nameToModule = Map.ofEntries(nameEntries); } /** * Resolves a collection of root modules, with this configuration as its - * parent, to create a new configuration. + * parent, to create a new configuration. This method works exactly as + * specified by the static {@link + * #resolveRequires(ModuleFinder,List,ModuleFinder,Collection) resolveRequires} + * method when invoked with this configuration as the parent. In other words, + * if this configuration is {@code cf} then this method is equivalent to + * invoking: + * <pre> {@code + * Configuration.resolveRequires(before, List.of(cf), after, roots); + * }</pre> + * + * @param before + * The <em>before</em> module finder to find modules + * @param after + * The <em>after</em> module finder to locate modules when a + * module cannot be located by the {@code before} module finder + * and the module is not in this configuration + * @param roots + * The possibly-empty collection of module names of the modules + * to resolve + * + * @return The configuration that is the result of resolving the given + * root modules + * + * @throws ResolutionException + * If resolution or the post-resolution checks fail + * @throws SecurityException + * If locating a module is denied by the security manager + */ + public Configuration resolveRequires(ModuleFinder before, + ModuleFinder after, + Collection<String> roots) + { + return resolveRequires(before, List.of(this), after, roots); + } + + + /** + * Resolves a collection of root modules, with service binding, and with + * this configuration as its parent, to create a new configuration. + * This method works exactly as specified by the static {@link + * #resolveRequiresAndUses(ModuleFinder,List,ModuleFinder,Collection) + * resolveRequiresAndUses} method when invoked with this configuration + * as the parent. In other words, if this configuration is {@code cf} then + * this method is equivalent to invoking: + * <pre> {@code + * Configuration.resolveRequiresAndUses(before, List.of(cf), after, roots); + * }</pre> + * + * + * @param before + * The <em>before</em> module finder to find modules + * @param after + * The <em>after</em> module finder to locate modules when not + * located by the {@code before} module finder and this + * configuration + * @param roots + * The possibly-empty collection of module names of the modules + * to resolve + * + * @return The configuration that is the result of resolving the given + * root modules + * + * @throws ResolutionException + * If resolution or the post-resolution checks fail + * @throws SecurityException + * If locating a module is denied by the security manager + */ + public Configuration resolveRequiresAndUses(ModuleFinder before, + ModuleFinder after, + Collection<String> roots) + { + return resolveRequiresAndUses(before, List.of(this), after, roots); + } + + + /** + * Resolves a collection of root modules, with service binding, and with + * the empty configuration as its parent. The post resolution checks + * are optionally run. + * + * This method is used to create the configuration for the boot layer. + */ + static Configuration resolveRequiresAndUses(ModuleFinder finder, + Collection<String> roots, + boolean check, + PrintStream traceOutput) + { + List<Configuration> parents = List.of(empty()); + Resolver resolver = new Resolver(finder, parents, ModuleFinder.of(), traceOutput); + resolver.resolveRequires(roots).resolveUses(); + + return new Configuration(parents, resolver, check); + } + + + /** + * Resolves a collection of root modules to create a configuration. * * <p> Each root module is located using the given {@code before} module * finder. If a module is not found then it is located in the parent * configuration as if by invoking the {@link #findModule(String) - * findModule} method. If not found then the module is located using the - * given {@code after} module finder. The same search order is used to - * locate transitive dependences. Root modules or dependences that are - * located in a parent configuration are resolved no further and are not - * included in the resulting configuration. </p> + * findModule} method on each parent in iteration order. If not found then + * the module is located using the given {@code after} module finder. The + * same search order is used to locate transitive dependences. Root modules + * or dependences that are located in a parent configuration are resolved + * no further and are not included in the resulting configuration. </p> * * <p> When all modules have been resolved then the resulting dependency * graph is checked to ensure that it does not contain cycles. A - * readability graph is constructed and then, in conjunction with the - * module exports and service use, checked for consistency. </p> + * readability graph is constructed and in conjunction with the module + * exports and service use, checked for consistency. </p> * * <p> Resolution and the (post-resolution) consistency checks may fail for * following reasons: </p> @@ -262,6 +373,8 @@ * * @param before * The <em>before</em> module finder to find modules + * @param parents + * The list parent configurations in search order * @param after * The <em>after</em> module finder to locate modules when not * located by the {@code before} module finder or in parent @@ -274,31 +387,37 @@ * root modules * * @throws ResolutionException - * If resolution or the post-resolution checks fail for any of the - * reasons listed + * If resolution or the post-resolution checks fail + * @throws IllegalArgumentException + * If the list of parents is empty * @throws SecurityException * If locating a module is denied by the security manager */ - public Configuration resolveRequires(ModuleFinder before, - ModuleFinder after, - Collection<String> roots) + public static Configuration resolveRequires(ModuleFinder before, + List<Configuration> parents, + ModuleFinder after, + Collection<String> roots) { Objects.requireNonNull(before); Objects.requireNonNull(after); Objects.requireNonNull(roots); - Resolver resolver = new Resolver(before, this, after, null); + List<Configuration> parentList = new ArrayList<>(parents); + if (parentList.isEmpty()) + throw new IllegalArgumentException("'parents' is empty"); + + Resolver resolver = new Resolver(before, parentList, after, null); resolver.resolveRequires(roots); - return new Configuration(this, resolver, true); + return new Configuration(parentList, resolver, true); } - /** - * Resolves a collection of root modules, with service binding, and with - * this configuration as its parent, to create a new configuration. + * Resolves a collection of root modules, with service binding, to create + * configuration. * - * <p> This method works exactly as specified by {@link #resolveRequires + * <p> This method works exactly as specified by {@link + * #resolveRequires(ModuleFinder,List,ModuleFinder,Collection) * resolveRequires} except that the graph of resolved modules is augmented * with modules induced by the service-use dependence relation. </p> * @@ -319,6 +438,8 @@ * * @param before * The <em>before</em> module finder to find modules + * @param parents + * The list parent configurations in search order * @param after * The <em>after</em> module finder to locate modules when not * located by the {@code before} module finder or in parent @@ -331,51 +452,35 @@ * root modules * * @throws ResolutionException - * If resolution or the post-resolution checks fail for any of the - * reasons listed + * If resolution or the post-resolution checks fail + * @throws IllegalArgumentException + * If the list of parents is empty * @throws SecurityException * If locating a module is denied by the security manager */ - public Configuration resolveRequiresAndUses(ModuleFinder before, - ModuleFinder after, - Collection<String> roots) + public static Configuration resolveRequiresAndUses(ModuleFinder before, + List<Configuration> parents, + ModuleFinder after, + Collection<String> roots) { Objects.requireNonNull(before); Objects.requireNonNull(after); Objects.requireNonNull(roots); - Resolver resolver = new Resolver(before, this, after, null); + List<Configuration> parentList = new ArrayList<>(parents); + if (parentList.isEmpty()) + throw new IllegalArgumentException("'parents' is empty"); + + Resolver resolver = new Resolver(before, parentList, after, null); resolver.resolveRequires(roots).resolveUses(); - return new Configuration(this, resolver, true); + return new Configuration(parentList, resolver, true); } /** - * Resolves a collection of root modules, with service binding, and with - * the empty configuration as its parent. The post resolution checks - * are optionally run. - * - * This method is used to create the configuration for the boot layer. - */ - static Configuration resolveRequiresAndUses(ModuleFinder finder, - Collection<String> roots, - boolean check, - PrintStream traceOutput) - { - Configuration parent = empty(); - - Resolver resolver - = new Resolver(finder, parent, ModuleFinder.of(), traceOutput); - resolver.resolveRequires(roots).resolveUses(); - - return new Configuration(parent, resolver, check); - } - - - /** - * Returns the <em>empty</em> configuration. The empty configuration does - * not contain any modules and does not have a parent. + * Returns the <em>empty</em> configuration. There are no modules in the + * empty configuration. It has no parents. * * @return The empty configuration */ @@ -385,13 +490,14 @@ /** - * Returns this configuration's parent unless this is the {@linkplain #empty - * empty configuration}, which has no parent. + * Returns an unmodifiable list of this configuration's parents, in search + * order. If this is the {@linkplain #empty empty configuration} then an + * empty list is returned. * - * @return This configuration's parent + * @return A possibly-empty unmodifiable list of this parent configurations */ - public Optional<Configuration> parent() { - return Optional.ofNullable(parent); + public List<Configuration> parents() { + return parents; } @@ -408,23 +514,35 @@ /** * Finds a resolved module in this configuration, or if not in this - * configuration, the {@linkplain #parent parent} configurations. + * configuration, the {@linkplain #parents parent} configurations. + * Finding a module in parent configurations is equivalent to invoking + * {@code findModule} on each parent, in search order, until the module + * is found or all parents have been searched. In a <em>tree of + * configurations</em> then this is equivalent to a depth-first search. * * @param name * The module name of the resolved module to find * * @return The resolved module with the given name or an empty {@code * Optional} if there isn't a module with this name in this - * configuration or any parent configuration + * configuration or any parent configurations */ public Optional<ResolvedModule> findModule(String name) { Objects.requireNonNull(name); - if (parent == null) - return Optional.empty(); ResolvedModule m = nameToModule.get(name); if (m != null) return Optional.of(m); - return parent().flatMap(x -> x.findModule(name)); + + if (!parents.isEmpty()) { + return configurations() + .skip(1) // skip this configuration + .map(cf -> cf.nameToModule) + .filter(map -> map.containsKey(name)) + .map(map -> map.get(name)) + .findFirst(); + } + + return Optional.empty(); } @@ -444,9 +562,46 @@ } /** + * Returns an ordered stream of configurations. The first element is this + * configuration, the remaining elements are the parent configurations + * in DFS order. + * + * @implNote For now, the assumption is that the number of elements will + * be very low and so this method does not use a specialized spliterator. + */ + Stream<Configuration> configurations() { + List<Configuration> allConfigurations = this.allConfigurations; + if (allConfigurations == null) { + allConfigurations = new ArrayList<>(); + Set<Configuration> visited = new HashSet<>(); + Deque<Configuration> stack = new ArrayDeque<>(); + visited.add(this); + stack.push(this); + while (!stack.isEmpty()) { + Configuration layer = stack.pop(); + allConfigurations.add(layer); + + // push in reverse order + for (int i = layer.parents.size() - 1; i >= 0; i--) { + Configuration parent = layer.parents.get(i); + if (!visited.contains(parent)) { + visited.add(parent); + stack.push(parent); + } + } + } + this.allConfigurations = Collections.unmodifiableList(allConfigurations); + } + return allConfigurations.stream(); + } + + private volatile List<Configuration> allConfigurations; + + + /** * Returns a string describing this configuration. * - * @return A string describing this configuration + * @return A possibly empty string describing this configuration */ @Override public String toString() {
--- a/src/java.base/share/classes/java/lang/module/Dependence.java Thu Dec 01 21:01:53 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package java.lang.module; - -import java.util.*; -import java.util.stream.*; - - -class Dependence { - - private Dependence() { } - - static <T> Stream<String> toStringStream(Set<T> s) { - return s.stream().map(e -> e.toString().toLowerCase()); - } - - static <M> String toString(Set<M> mods, String what) { - return (Stream.concat(toStringStream(mods), Stream.of(what))) - .collect(Collectors.joining(" ")); - } - -}
--- a/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java Thu Dec 01 21:39:49 2016 +0000 @@ -45,6 +45,8 @@ import java.util.Optional; import java.util.Set; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static jdk.internal.module.Checks.*; import static java.util.Objects.*; @@ -57,9 +59,11 @@ * A module descriptor. * * <p> A {@code ModuleDescriptor} is typically created from the binary form - * of a module declaration. The associated {@link ModuleDescriptor.Builder} - * class can also be used to create a {@code ModuleDescriptor} from its - * components. </p> + * of a module declaration. Alternatively, the {@link ModuleDescriptor.Builder} + * class can be used to create a {@code ModuleDescriptor} from its components. + * The {@link #module module}, {@link #openModule openModule}, and {@link + * #automaticModule automaticModule} methods create builders for building + * different kinds of modules. </p> * * <p> {@code ModuleDescriptor} objects are immutable and safe for use by * multiple concurrent threads.</p> @@ -95,7 +99,13 @@ * module</i> 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(Set<Modifier> ms, String mn) { - this(ms, mn, true); + if (ms.isEmpty()) { + ms = Collections.emptySet(); + } else { + ms = Collections.unmodifiableSet(EnumSet.copyOf(ms)); + } + this.mods = ms; + this.name = mn; } - private Requires(Set<Modifier> ms, String mn, boolean check) { - if (ms == null || ms.isEmpty()) { - mods = Collections.emptySet(); - } else { - mods = check ? Collections.unmodifiableSet(EnumSet.copyOf(ms)) - : ms; - } - this.name = check ? requireModuleName(mn) : mn; + + private Requires(Set<Modifier> ms, String mn, boolean unused) { + this.mods = ms; + this.name = mn; } /** @@ -223,9 +235,8 @@ */ @Override public String toString() { - return Dependence.toString(mods, name); + return ModuleDescriptor.toString(mods, name); } - } @@ -239,36 +250,61 @@ public final static class Exports { + /** + * A modifier on a module export. + * + * @since 9 + */ + public static enum Modifier { + + /** + * The export was not explicitly or implicitly declared in the + * source of the module declaration. + */ + SYNTHETIC, + + /** + * The export was implicitly declared in the source of the module + * declaration. + */ + MANDATED; + + } + + private final Set<Modifier> mods; private final String source; private final Set<String> targets; // empty if unqualified export /** - * Constructs a qualified export. + * Constructs an export */ - private Exports(String source, Set<String> targets) { - this(source, targets, true); + private Exports(Set<Modifier> ms, String source, Set<String> targets) { + if (ms.isEmpty()) { + ms = Collections.emptySet(); + } else { + ms = Collections.unmodifiableSet(EnumSet.copyOf(ms)); + } + this.mods = ms; + this.source = source; + this.targets = emptyOrUnmodifiableSet(targets); } - private Exports(String source, Set<String> targets, boolean check) { - this.source = check ? requirePackageName(source) : source; - targets = check ? Collections.unmodifiableSet(new HashSet<>(targets)) - : Collections.unmodifiableSet(targets); - if (targets.isEmpty()) - throw new IllegalArgumentException("Empty target set"); - if (check) - targets.stream().forEach(Checks::requireModuleName); + private Exports(Set<Modifier> ms, + String source, + Set<String> targets, + boolean unused) { + this.mods = ms; + this.source = source; this.targets = targets; } /** - * Constructs an unqualified export. + * Returns the set of modifiers. + * + * @return A possibly-empty unmodifiable set of modifiers */ - private Exports(String source) { - this(source, true); - } - private Exports(String source, boolean check) { - this.source = check ? requirePackageName(source) : source; - this.targets = Collections.emptySet(); + public Set<Modifier> modifiers() { + return mods; } /** @@ -304,25 +340,27 @@ /** * Computes a hash code for this module export. * - * <p> The 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. + * <p> 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. * * <p> 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. - * </p> + * 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. </p> * * <p> This method satisfies the general contract of the {@link * java.lang.Object#equals(Object) Object.equals} method. </p> @@ -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; + } + } + + + /** + * <p> Represents a module <em>opens</em> directive, may be qualified or + * unqualified. </p> + * + * <p> The <em>opens</em> 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. </p> + * + * @see ModuleDescriptor#opens() + * @since 9 + */ + + public final static class Opens { + + /** + * A modifier on a module <em>opens</em> 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 Set<Modifier> mods; + private final String source; + private final Set<String> targets; // empty if unqualified export + + /** + * Constructs an Opens + */ + private Opens(Set<Modifier> ms, String source, Set<String> targets) { + if (ms.isEmpty()) { + ms = Collections.emptySet(); + } else { + ms = Collections.unmodifiableSet(EnumSet.copyOf(ms)); + } + this.mods = ms; + this.source = source; + this.targets = emptyOrUnmodifiableSet(targets); + } + + private Opens(Set<Modifier> ms, + String source, + Set<String> targets, + boolean unused) { + this.mods = ms; + this.source = source; + this.targets = targets; + } + + /** + * Returns the set of modifiers. + * + * @return A possibly-empty unmodifiable set of modifiers + */ + public Set<Modifier> modifiers() { + return mods; + } + + /** + * Returns {@code true} if this is a qualified opens. + * + * @return {@code true} if this is a qualified opens + */ + public boolean isQualified() { + return !targets.isEmpty(); + } + + /** + * Returns the package name. + * + * @return The package name + */ + public String source() { + return source; + } + + /** + * For a qualified opens, returns the non-empty and immutable set + * of the module names to which the package is open. For an + * unqualified opens, returns an empty set. + * + * @return The set of target module names or for an unqualified + * opens, an empty set + */ + public Set<String> targets() { + return targets; + } + + /** + * Computes a hash code for this module opens. + * + * <p> The 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. + * + * <p> 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. </p> + * + * <p> This method satisfies the general contract of the {@link + * java.lang.Object#equals(Object) Object.equals} method. </p> + * + * @param ob + * the object to which this object is to be compared + * + * @return {@code true} if, and only if, the given object is a module + * 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; + } } - /** * <p> A service that a module provides one or more implementations of. </p> @@ -369,21 +570,15 @@ public final static class Provides { private final String service; - private final Set<String> providers; + private final List<String> providers; - private Provides(String service, Set<String> providers) { - this(service, providers, true); + private Provides(String service, List<String> providers) { + this.service = service; + this.providers = Collections.unmodifiableList(providers); } - private Provides(String service, Set<String> providers, boolean check) { - this.service = check ? requireServiceTypeName(service) : service; - providers = check - ? Collections.unmodifiableSet(new LinkedHashSet<>(providers)) - : Collections.unmodifiableSet(providers); - if (providers.isEmpty()) - throw new IllegalArgumentException("Empty providers set"); - if (check) - providers.forEach(Checks::requireServiceProviderName); + private Provides(String service, List<String> providers, boolean unused) { + this.service = service; this.providers = providers; } @@ -395,12 +590,13 @@ public String service() { return service; } /** - * Returns the set of the fully qualified class names of the providers. + * Returns the list of the fully qualified class names of the providers + * or provider factories. * - * @return A non-empty and unmodifiable set of the fully qualified class - * names of the providers. + * @return A non-empty and unmodifiable list of the fully qualified class + * names of the providers or provider factories */ - public Set<String> providers() { return providers; } + public List<String> providers() { return providers; } /** * Computes a hash code for this provides. @@ -413,7 +609,7 @@ */ @Override public int hashCode() { - return hash(service, providers); + return service.hashCode() * 43 + providers.hashCode(); } /** @@ -421,7 +617,7 @@ * * <p> If 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. </p> + * service type is equal and the list of providers is equal. </p> * * <p> This method satisfies the general contract of the {@link * java.lang.Object#equals(Object) Object.equals} method. </p> @@ -774,10 +970,7 @@ // From module declarations private final String name; - private final Set<Requires> requires; - private final Set<Exports> exports; - private final Set<String> uses; - private final Map<String, Provides> provides; + private final boolean open; // Indicates if synthesised for a JAR file found on the module path private final boolean automatic; @@ -785,6 +978,12 @@ // Not generated from a module-info.java private final boolean synthetic; + private final Set<Requires> requires; + private final Set<Exports> exports; + private final Set<Opens> opens; + private final Set<String> uses; + private final Set<Provides> provides; + // "Extended" information, added post-compilation by tools private final Version version; private final String mainClass; @@ -796,12 +995,14 @@ private ModuleDescriptor(String name, + boolean open, boolean automatic, boolean synthetic, - Map<String, Requires> requires, + Set<Requires> requires, + Set<Exports> exports, + Set<Opens> opens, Set<String> uses, - Map<String, Exports> exports, - Map<String, Provides> provides, + Set<Provides> provides, Version version, String mainClass, String osName, @@ -812,31 +1013,24 @@ { this.name = name; + this.open = open; this.automatic = automatic; this.synthetic = synthetic; - Set<Requires> rqs = new HashSet<>(requires.values()); - assert (rqs.stream().map(Requires::name).sorted().distinct().count() - == rqs.size()) - : "Module " + name + " has duplicate requires"; - this.requires = emptyOrUnmodifiableSet(rqs); + assert (requires.stream().map(Requires::name).distinct().count() + == requires.size()); + this.requires = emptyOrUnmodifiableSet(requires); - Set<Exports> exs = new HashSet<>(exports.values()); - assert (exs.stream().map(Exports::source).sorted().distinct().count() - == exs.size()) - : "Module " + name + " has duplicate exports"; - this.exports = emptyOrUnmodifiableSet(exs); - + this.exports = emptyOrUnmodifiableSet(exports); + this.opens = emptyOrUnmodifiableSet(opens); this.uses = emptyOrUnmodifiableSet(uses); - this.provides = emptyOrUnmodifiableMap(provides); - + this.provides = emptyOrUnmodifiableSet(provides); this.version = version; this.mainClass = mainClass; this.osName = osName; this.osArch = osArch; this.osVersion = osVersion; this.hashes = hashes; - this.packages = emptyOrUnmodifiableSet(packages); } @@ -845,11 +1039,13 @@ */ ModuleDescriptor(ModuleDescriptor md, Set<String> pkgs) { this.name = md.name; + this.open = md.open; this.automatic = md.automatic; this.synthetic = md.synthetic; this.requires = md.requires; this.exports = md.exports; + this.opens = md.opens; this.uses = md.uses; this.provides = md.provides; @@ -866,38 +1062,44 @@ } /** - * Creates a module descriptor from its components. This method is intended - * for use by the jlink plugin. + * Creates a module descriptor from its components. + * The arguments are pre-validated and sets are unmodifiable sets. */ ModuleDescriptor(String name, + boolean open, boolean automatic, boolean synthetic, Set<Requires> requires, + Set<Exports> exports, + Set<Opens> opens, Set<String> uses, - Set<Exports> exports, - Map<String, Provides> provides, + Set<Provides> provides, Version version, String mainClass, String osName, String osArch, String osVersion, Set<String> packages, - ModuleHashes hashes) { + ModuleHashes hashes, + int hashCode, + boolean unused) { this.name = name; + this.open = open; this.automatic = automatic; this.synthetic = synthetic; - this.requires = Collections.unmodifiableSet(requires); - this.exports = Collections.unmodifiableSet(exports); - this.uses = Collections.unmodifiableSet(uses); - this.provides = Collections.unmodifiableMap(provides); - this.packages = Collections.unmodifiableSet(packages); - + this.requires = requires; + this.exports = exports; + this.opens = opens; + this.uses = uses; + this.provides = provides; + this.packages = packages; this.version = version; this.mainClass = mainClass; this.osName = osName; this.osArch = osArch; this.osVersion = osVersion; this.hashes = hashes; + this.hash = hashCode; } /** @@ -910,6 +1112,19 @@ } /** + * <p> Returns {@code true} if this is an open module. </p> + * + * <p> 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. </p> + * + * @return {@code true} if this is an open module + */ + public boolean isOpen() { + return open; + } + + /** * <p> Returns {@code true} if this is an automatic module. </p> * * <p> 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 @@ } /** + * <p> The module exports. </p> + * + * @return A possibly-empty unmodifiable set of exported packages + */ + public Set<Exports> exports() { + return exports; + } + + /** + * <p> The module <em>opens</em> directives. </p> + * + * <p> 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. </p> + * + * <p> This method returns an empty set when invoked on {@link #isOpen() + * open} module. </p> + * + * @return A possibly-empty unmodifiable set of open packages + */ + public Set<Opens> opens() { + return opens; + } + + /** * <p> The service dependences of this module. </p> * * @return A possibly-empty unmodifiable set of the fully qualified class @@ -962,24 +1202,14 @@ /** * <p> The services that this module provides. </p> * - * @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 Map<String, Provides> provides() { + public Set<Provides> provides() { return provides; } /** - * <p> The module exports. </p> - * - * @return A possibly-empty unmodifiable set of exported packages - */ - public Set<Exports> exports() { - return exports; - } - - /** * Returns this module's version. * * @return This module's version @@ -1046,22 +1276,9 @@ } /** - * Returns the names of the packages defined in, but not exported by, this - * module. + * Returns the names of all packages in this module. * - * @return A possibly-empty unmodifiable set of the concealed packages - */ - public Set<String> conceals() { - Set<String> conceals = new HashSet<>(packages); - exports.stream().map(Exports::source).forEach(conceals::remove); - return emptyOrUnmodifiableSet(conceals); - } - - /** - * Returns the names of all the packages defined in this module, whether - * exported or concealed. - * - * @return A possibly-empty unmodifiable set of the all packages + * @return A possibly-empty unmodifiable set of all packages in the module */ public Set<String> packages() { return packages; @@ -1078,31 +1295,39 @@ /** * A builder used for building {@link ModuleDescriptor} objects. * + * <p> {@code ModuleDescriptor} defines the {@link #module module}, {@link + * #openModule openModule}, and {@link #automaticModule automaticModule} + * methods to create builders for building different kinds of modules. </p> + * * <p> Example usage: </p> - * - * <pre>{@code - * ModuleDescriptor descriptor = new ModuleDescriptor.Builder("m1") + * <pre>{@code ModuleDescriptor descriptor = ModuleDescriptor.module("m1") + * .exports("p") * .requires("m2") - * .exports("p") * .build(); * }</pre> * - * @apiNote A {@code Builder} cannot be used to create an {@link - * ModuleDescriptor#isAutomatic() automatic} or a {@link - * ModuleDescriptor#isSynthetic() synthetic} module. + * @apiNote A {@code Builder} checks the components and invariants as + * components are added to the builder. The rational for this is to detect + * errors as early as possible and not defer all validation to the + * {@link #build build} method. A {@code Builder} cannot be used to create + * a {@link ModuleDescriptor#isSynthetic() synthetic} module. * * @since 9 */ public static final class Builder { - final String name; + final boolean strict; // true if module names are checked + boolean open; boolean automatic; boolean synthetic; final Map<String, Requires> requires = new HashMap<>(); + + final Map<String, Exports> exports = new HashMap<>(); + final Map<String, Opens> opens = new HashMap<>(); + final Set<String> concealedPackages = new HashSet<>(); + final Set<String> uses = new HashSet<>(); - final Map<String, Exports> exports = new HashMap<>(); final Map<String, Provides> provides = new HashMap<>(); - Set<String> 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<Requires.Modifier> 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<Requires.Modifier> 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<Exports.Modifier> ms, + String pn, + Set<String> 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<Exports.Modifier> 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<String> 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 <em>opens</em> 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 <em>opens</em> 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<Opens.Modifier> ms, + String pn, + Set<String> 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 <em>opens</em> 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<Opens.Modifier> ms, String pn) { + Opens e = new Opens(ms, requirePackageName(pn), Collections.emptySet()); + return opens(e); + } + + /** + * Adds an <em>opens</em> 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<String> targets) { + return opens(Collections.emptySet(), pn, targets); + } + + /** + * Adds an <em>opens</em> 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<String> exportedPackages() { - return exports.keySet(); + /* package */ Set<String> exportedAndOpenPackages() { + if (opens.isEmpty()) + return exports.keySet(); + Set<String> 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<String> pcs) { - if (provides.containsKey(st)) + public Builder provides(String service, List<String> 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<String> 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<String> pns) { - pns.forEach(this::conceals); + public Builder contains(Set<String> 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> requires = new HashSet<>(this.requires.values()); - Set<String> packages = new HashSet<>(conceals); - packages.addAll(exportedPackages()); + Set<String> packages = new HashSet<>(exportedAndOpenPackages()); + packages.addAll(concealedPackages); + + Set<Exports> exports = new HashSet<>(this.exports.values()); + Set<Opens> opens = new HashSet<>(this.opens.values()); + + Set<Provides> 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<String, Provides> 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. + * + * <p> As an example, the following creates a module descriptor for an open + * name "{@code m}" containing two packages, one of which is exported. </p> + * <pre>{@code + * ModuleDescriptor descriptor = ModuleDescriptor.openModule("m") + * .requires("java.base") + * .exports("p") + * .contains("q") + * .build(); + * }</pre> + * + * @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. * * <p> 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. </p> + * 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. </p> * * <p> 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. * * <p> 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. </p> + * set of packages then the {@code packageFinder} will be invoked. </p> * * <p> 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 <M> String toString(Set<M> 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<Requires.Modifier> ms, String mn) { - return new Requires(ms, mn, false); + return new Requires(ms, mn, true); } @Override - public Exports newExports(String source, Set<String> targets) { - return new Exports(source, targets, false); + public Exports newExports(Set<Exports.Modifier> ms, String source) { + return new Exports(ms, source, Collections.emptySet(), true); } @Override - public Exports newExports(String source) { - return new Exports(source, false); + public Exports newExports(Set<Exports.Modifier> ms, + String source, + Set<String> targets) { + return new Exports(ms, source, targets, true); } @Override - public Provides newProvides(String service, Set<String> providers) { - return new Provides(service, providers, false); + public Opens newOpens(Set<Opens.Modifier> ms, + String source, + Set<String> targets) { + return new Opens(ms, source, targets, true); + } + + @Override + public Opens newOpens(Set<Opens.Modifier> ms, String source) { + return new Opens(ms, source, Collections.emptySet(), true); + } + + @Override + public Provides newProvides(String service, List<String> 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> requires, + Set<Exports> exports, + Set<Opens> opens, Set<String> uses, - Set<Exports> exports, - Map<String, Provides> provides, + Set<Provides> provides, Version version, String mainClass, String osName, String osArch, String osVersion, Set<String> 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<ModuleHashes> hashes(ModuleDescriptor descriptor) { + return descriptor.hashes(); } @Override @@ -1995,11 +2491,6 @@ } @Override - public Optional<ModuleHashes> hashes(ModuleDescriptor descriptor) { - return descriptor.hashes(); - } - - @Override public ModuleFinder newModulePath(Runtime.Version version, boolean isLinkPhase, Path... entries) {
--- a/src/java.base/share/classes/java/lang/module/ModuleFinder.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/module/ModuleFinder.java Thu Dec 01 21:39:49 2016 +0000 @@ -233,10 +233,11 @@ * ModuleDescriptor.Version} and ignored if it cannot be parsed as * a {@code Version}. </p></li> * - * <li><p> 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. </p></li> + * <li><p> 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. </p></li> * * <li><p> As an example, a JAR file named {@code foo-bar.jar} will * derive a module name {@code foo.bar} and no version. A JAR file
--- a/src/java.base/share/classes/java/lang/module/ModuleInfo.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/module/ModuleInfo.java Thu Dec 01 21:39:49 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<Set<String>> 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<Set<String>> 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<String> attributes = new HashSet<>(); + Builder builder = null; + Set<String> 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<String> 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<requires_count; i++) { int index = in.readUnsignedShort(); int flags = in.readUnsignedShort(); - String dn = cpool.getUtf8(index); - Set<Modifier> mods; + String dn = cpool.getUtf8AsBinaryName(index); + Set<Requires.Modifier> 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<exports_count; i++) { int index = in.readUnsignedShort(); - String pkg = cpool.getUtf8(index).replace('/', '.'); + String pkg = cpool.getUtf8AsBinaryName(index); + + Set<Exports.Modifier> 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<String> targets = new HashSet<>(exports_to_count); for (int j=0; j<exports_to_count; j++) { int exports_to_index = in.readUnsignedShort(); - targets.add(cpool.getUtf8(exports_to_index)); + targets.add(cpool.getUtf8AsBinaryName(exports_to_index)); } - builder.exports(pkg, targets); + builder.exports(mods, pkg, targets); } else { - builder.exports(pkg); + builder.exports(mods, pkg); + } + } + } + + int opens_count = in.readUnsignedShort(); + if (opens_count > 0) { + if (open) { + throw invalidModuleDescriptor("The opens table for an open" + + " module must be 0 length"); + } + for (int i=0; i<opens_count; i++) { + int index = in.readUnsignedShort(); + String pkg = cpool.getUtf8AsBinaryName(index); + + Set<Opens.Modifier> 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<String> targets = new HashSet<>(open_to_count); + for (int j=0; j<open_to_count; j++) { + int opens_to_index = in.readUnsignedShort(); + targets.add(cpool.getUtf8AsBinaryName(opens_to_index)); + } + builder.opens(mods, pkg, targets); + } else { + builder.opens(mods, pkg); } } } @@ -330,111 +419,114 @@ if (uses_count > 0) { for (int i=0; i<uses_count; i++) { int index = in.readUnsignedShort(); - String sn = cpool.getClassName(index).replace('/', '.'); + String sn = cpool.getClassNameAsBinaryName(index); builder.uses(sn); } } int provides_count = in.readUnsignedShort(); if (provides_count > 0) { - Map<String, Set<String>> pm = new HashMap<>(); for (int i=0; i<provides_count; i++) { int index = in.readUnsignedShort(); - int with_index = in.readUnsignedShort(); - String sn = cpool.getClassName(index).replace('/', '.'); - String cn = cpool.getClassName(with_index).replace('/', '.'); - // computeIfAbsent - Set<String> 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<String> providers = new ArrayList<>(with_count); + for (int j=0; j<with_count; j++) { + index = in.readUnsignedShort(); + String pn = cpool.getClassNameAsBinaryName(index); + providers.add(pn); } - providers.add(cn); - } - for (Map.Entry<String, Set<String>> 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<String> readModulePackagesAttribute(DataInput in, ConstantPool cpool) throws IOException { int package_count = in.readUnsignedShort(); + Set<String> packages = new HashSet<>(package_count); for (int i=0; i<package_count; i++) { int index = in.readUnsignedShort(); - String pn = cpool.getUtf8(index).replace('/', '.'); - builder.conceals(pn); + String pn = cpool.getUtf8AsBinaryName(index); + packages.add(pn); } + return packages; } /** - * Reads the Version attribute + * Reads the ModuleVersion attribute */ - private void readVersionAttribute(DataInput in, ConstantPool cpool) + private String readModuleVersionAttribute(DataInput in, ConstantPool cpool) throws IOException { int index = in.readUnsignedShort(); - builder.version(cpool.getUtf8(index)); + return cpool.getUtf8(index); } /** - * Reads the MainClass attribute + * Reads the ModuleMainClass attribute */ - private void readMainClassAttribute(DataInput in, ConstantPool cpool) + private String readModuleMainClassAttribute(DataInput in, ConstantPool cpool) throws IOException { int index = in.readUnsignedShort(); - builder.mainClass(cpool.getClassName(index).replace('/', '.')); + return cpool.getClassNameAsBinaryName(index); } /** - * Reads the TargetPlatform attribute + * Reads the ModuleTarget attribute */ - private void readTargetPlatformAttribute(DataInput in, ConstantPool cpool) + private String[] readModuleTargetAttribute(DataInput in, ConstantPool cpool) throws IOException { + String[] values = new String[3]; + int name_index = in.readUnsignedShort(); if (name_index != 0) - builder.osName(cpool.getUtf8(name_index)); + values[0] = cpool.getUtf8(name_index); int arch_index = in.readUnsignedShort(); if (arch_index != 0) - builder.osArch(cpool.getUtf8(arch_index)); + values[1] = cpool.getUtf8(arch_index); int version_index = in.readUnsignedShort(); if (version_index != 0) - builder.osVersion(cpool.getUtf8(version_index)); + values[2] = cpool.getUtf8(version_index); + + return values; } /** - * Reads the Hashes attribute - * - * @apiNote For now the hash is stored in base64 as a UTF-8 string, this - * should be changed to be an array of u1. + * Reads the ModuleHashes attribute */ - private void readHashesAttribute(DataInput in, ConstantPool cpool) + private ModuleHashes readModuleHashesAttribute(DataInput in, ConstantPool cpool) throws IOException { - int index = in.readUnsignedShort(); - String algorithm = cpool.getUtf8(index); + int algorithm_index = in.readUnsignedShort(); + String algorithm = cpool.getUtf8(algorithm_index); int hash_count = in.readUnsignedShort(); - - Map<String, String> map = new HashMap<>(hash_count); + Map<String, byte[]> map = new HashMap<>(hash_count); for (int i=0; i<hash_count; i++) { - index = in.readUnsignedShort(); - String dn = cpool.getUtf8(index); - index = in.readUnsignedShort(); - String hash = cpool.getUtf8(index); - map.put(dn, hash); + int module_name_index = in.readUnsignedShort(); + String mn = cpool.getUtf8AsBinaryName(module_name_index); + int hash_length = in.readUnsignedShort(); + if (hash_length == 0) { + throw invalidModuleDescriptor("hash_length == 0"); + } + byte[] hash = new byte[hash_length]; + in.readFully(hash); + map.put(mn, hash); } - builder.hashes(new ModuleHashes(algorithm, map)); + return new ModuleHashes(algorithm, map); } @@ -447,11 +539,11 @@ if (name.equals(MODULE) || name.equals(SOURCE_FILE) || name.equals(SDE) || - name.equals(CONCEALED_PACKAGES) || - name.equals(VERSION) || - name.equals(MAIN_CLASS) || - name.equals(TARGET_PLATFORM) || - name.equals(HASHES)) + name.equals(MODULE_PACKAGES) || + name.equals(MODULE_VERSION) || + name.equals(MODULE_MAIN_CLASS) || + name.equals(MODULE_TARGET) || + name.equals(MODULE_HASHES)) return true; return false; @@ -461,8 +553,8 @@ * Return true if the given attribute name is the name of a pre-defined * attribute that is not allowed in the class file. * - * Except for Module, InnerClasses, Synthetic, SourceFile, SourceDebugExtension, - * and Deprecated, none of the pre-defined attributes in JVMS 4.7 may appear. + * Except for Module, InnerClasses, SourceFile, SourceDebugExtension, and + * Deprecated, none of the pre-defined attributes in JVMS 4.7 may appear. */ private static boolean isAttributeDisallowed(String name) { Set<String> 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<String> 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");
--- a/src/java.base/share/classes/java/lang/module/ModulePath.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/module/ModulePath.java Thu Dec 01 21:39:49 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<String> resources = map.get(Boolean.FALSE); Set<String> 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<String> serviceNames = configFiles.stream() @@ -472,7 +470,7 @@ // parse each service configuration file for (String sn : serviceNames) { JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn); - Set<String> providerClasses = new LinkedHashSet<>(); + List<String> 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(".");
--- a/src/java.base/share/classes/java/lang/module/ModuleReference.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/module/ModuleReference.java Thu Dec 01 21:39:49 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;
--- a/src/java.base/share/classes/java/lang/module/ModuleReferences.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/module/ModuleReferences.java Thu Dec 01 21:39:49 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<ModuleReader> 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<ModuleReader> 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); }
--- a/src/java.base/share/classes/java/lang/module/Resolver.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/module/Resolver.java Thu Dec 01 21:39:49 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<Configuration> parents; private final ModuleFinder afterFinder; private final PrintStream traceOutput; @@ -61,11 +67,11 @@ Resolver(ModuleFinder beforeFinder, - Configuration parent, + List<Configuration> 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<ModuleReference> providers = availableProviders.get(sn); if (providers == null) { @@ -191,19 +209,23 @@ Deque<ModuleDescriptor> q = new ArrayDeque<>(); // the initial set of modules that may use services - Set<ModuleDescriptor> candidateConsumers = new HashSet<>(); - Configuration p = parent; - while (p != null) { - candidateConsumers.addAll(p.descriptors()); - p = p.parent().orElse(null); + Set<ModuleDescriptor> 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<ModuleDescriptor> 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<ResolvedModule, Set<ResolvedModule>> makeGraph(Configuration cf) { + // initial capacity of maps to avoid resizing + int capacity = 1 + (4 * nameToReference.size())/ 3; + // the "reads" graph starts as a module dependence graph and // is iteratively updated to be the readability graph - Map<ResolvedModule, Set<ResolvedModule>> g1 = new HashMap<>(); + Map<ResolvedModule, Set<ResolvedModule>> g1 = new HashMap<>(capacity); - // the "requires public" graph, contains requires public edges only - Map<ResolvedModule, Set<ResolvedModule>> g2 = new HashMap<>(); + // the "requires transitive" graph, contains requires transitive edges only + Map<ResolvedModule, Set<ResolvedModule>> g2; - - // need "requires public" from the modules in parent configurations as - // there may be selected modules that have a dependency on modules in + // need "requires transitive" from the modules in parent configurations + // as there may be selected modules that have a dependency on modules in // the parent configuration. - - Configuration p = parent; - while (p != null) { - for (ModuleDescriptor descriptor : p.descriptors()) { - String name = descriptor.name(); - ResolvedModule m1 = p.findModule(name) - .orElseThrow(() -> new InternalError(name + " not found")); - for (ModuleDescriptor.Requires requires : descriptor.requires()) { - if (requires.modifiers().contains(Modifier.PUBLIC)) { - String dn = requires.name(); - ResolvedModule m2 = p.findModule(dn) - .orElseThrow(() -> new InternalError(dn + " not found")); - g2.computeIfAbsent(m1, k -> new HashSet<>()).add(m2); - } - } - } - - p = p.parent().orElse(null); + if (Layer.boot() == null) { + g2 = new HashMap<>(capacity); + } else { + g2 = parents.stream() + .flatMap(Configuration::configurations) + .distinct() + .flatMap(c -> + c.modules().stream().flatMap(m1 -> + m1.descriptor().requires().stream() + .filter(r -> r.modifiers().contains(Modifier.TRANSITIVE)) + .flatMap(r -> { + Optional<ResolvedModule> m2 = c.findModule(r.name()); + assert m2.isPresent() + || r.modifiers().contains(Modifier.STATIC); + return m2.stream(); + }) + .map(m2 -> Map.entry(m1, m2)) + ) + ) + // stream of m1->m2 + .collect(Collectors.groupingBy(Map.Entry::getKey, + HashMap::new, + Collectors.mapping(Map.Entry::getValue, Collectors.toSet()) + )); } // populate g1 and g2 with the dependences from the selected modules - Map<String, ResolvedModule> nameToResolved = new HashMap<>(); + Map<String, ResolvedModule> 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<ResolvedModule> reads = new HashSet<>(); - Set<ResolvedModule> requiresPublic = new HashSet<>(); + Set<ResolvedModule> 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<ResolvedModule> toAdd = new HashSet<>(); + List<ResolvedModule> toAdd = new ArrayList<>(); do { changed = false; for (Set<ResolvedModule> m1Reads : g1.values()) { for (ResolvedModule m2 : m1Reads) { - Set<ResolvedModule> m2RequiresPublic = g2.get(m2); - if (m2RequiresPublic != null) { - for (ResolvedModule m3 : m2RequiresPublic) { + Set<ResolvedModule> 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<String, ModuleDescriptor.Provides> 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<ResolvedModule> 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<ModuleReference> 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;
--- a/src/java.base/share/classes/java/lang/module/SystemModuleFinder.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/module/SystemModuleFinder.java Thu Dec 01 21:39:49 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<ModuleReference> mods = new HashSet<>(n); - Map<String, ModuleReference> map = new HashMap<>(n); + ModuleReference[] mods = new ModuleReference[n]; + + @SuppressWarnings(value = {"rawtypes", "unchecked"}) + Entry<String, ModuleReference>[] map + = (Entry<String, ModuleReference>[])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<String, String> hashes = new HashMap<>(); + static Map<String, byte[]> hashes = new HashMap<>(); static void add(ModuleDescriptor descriptor) { Optional<ModuleHashes> 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); } };
--- a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java Thu Dec 01 21:39:49 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 @@ * <p>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. * * <p>If there is a security manager, its * {@code checkPermission} method is first called with a @@ -126,8 +128,10 @@ * <p>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. * * <p>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; + // package is exported to caller and class/member is public + boolean isExported = declaringModule.isExported(pn, callerModule); + boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers()); + int modifiers; + if (this instanceof Executable) { + modifiers = ((Executable) this).getModifiers(); + } else { + modifiers = ((Field) this).getModifiers(); } + boolean isMemberPublic = Modifier.isPublic(modifiers); + if (isExported && isClassPublic && isMemberPublic) + return; - if (declaringClass == Module.class - || declaringClass == AccessibleObject.class) { - int modifiers; - if (this instanceof Executable) { - modifiers = ((Executable) this).getModifiers(); - } else { - modifiers = ((Field) this).getModifiers(); - } - if (!Modifier.isPublic(modifiers)) { - String msg = "Cannot make a non-public member of " - + declaringClass + " accessible"; - Reflection.throwInaccessibleObjectException(msg); - } - } + // not accessible + String msg = "Unable to make "; + if (this instanceof Field) + msg += "field "; + msg += this + " accessible: " + declaringModule + " does not \""; + if (isClassPublic && isMemberPublic) + msg += "exports"; + else + msg += "opens"; + msg += " " + pn + "\" to " + callerModule; + Reflection.throwInaccessibleObjectException(msg); } /**
--- a/src/java.base/share/classes/java/lang/reflect/Layer.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/reflect/Layer.java Thu Dec 01 21:39:49 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}. </p> + * #empty() empty} layer, has at least one {@link #parents() parent}. </p> * * <p> 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. </p> + * 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. </p> * * <p> 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 <a href="../ClassLoader.html#builtinLoaders"> * built-in</a> into the Java virtual machine. The boot layer will often be - * the {@link #parent() parent} when creating additional layers. </p> + * the {@link #parents() parent} when creating additional layers. </p> * * <p> 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<Layer> parents; // maps module name to jlr.Module private final Map<String, Module> nameToModule; - /** * Creates a new Layer from the modules in the given configuration. */ private Layer(Configuration cf, - Layer parent, + List<Layer> parents, Function<String, ClassLoader> clf) { this.cf = cf; - this.parent = parent; + this.parents = parents; // no need to do defensive copy Map<String, Module> 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 <em>Read edges</em> added by this method are <em>weak</em> + * 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: + * <pre> {@code + * Layer.defineModulesWithOneLoader(cf, List.of(thisLayer), parentLoader).layer(); + * }</pre> + * + * @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: + * <pre> {@code + * Layer.defineModulesWithManyLoaders(cf, List.of(thisLayer), parentLoader).layer(); + * }</pre> + * + * @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: + * <pre> {@code + * Layer.defineModules(cf, List.of(thisLayer), clf).layer(); + * }</pre> + * + * @param cf + * The configuration for the layer + * @param clf + * The function to map a module name to a class loader + * + * @return The newly created layer + * + * @throws IllegalArgumentException + * If the parent of the given configuration is not the configuration + * for this layer + * @throws LayerInstantiationException + * If creating the {@code Layer} fails for any of the reasons + * listed above, the layer cannot be created because the + * configuration contains a module named "{@code java.base}", + * a module with a package name starting with "{@code java.}" is + * mapped to a class loader other than the {@link + * ClassLoader#getPlatformClassLoader() platform class loader}, + * or the function to map a module name to a class loader returns + * {@code null} + * @throws SecurityException + * If {@code RuntimePermission("getClassLoader")} is denied by + * the security manager + */ + public Layer defineModules(Configuration cf, + Function<String, ClassLoader> clf) { + return defineModules(cf, List.of(this), clf).layer(); + } + + /** + * Creates a new layer by defining the modules in the given {@code + * Configuration} to the Java virtual machine. This method creates one + * class loader and defines all modules to that class loader. * * <p> The class loader created by this method implements <em>direct * delegation</em> when loading types from modules. When its {@link @@ -180,7 +407,7 @@ * <ul> * * <li><p> <em>Overlapping packages</em>: Two or more modules in the - * configuration have the same package (exported or concealed). </p></li> + * configuration have the same package. </p></li> * * <li><p> <em>Split delegation</em>: 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<Layer> parentLayers, + ClassLoader parentLoader) { - checkConfiguration(cf); + List<Layer> 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. * * <p> The class loaders created by this method implement <em>direct * delegation</em> 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<Layer> parentLayers, + ClassLoader parentLoader) { - checkConfiguration(cf); + List<Layer> 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. * * <p> Creating a {@code Layer} can fail for the following reasons: </p> * * <ul> * - * <li><p> Two or more modules with the same package (exported or - * concealed) are mapped to the same class loader. </p></li> + * <li><p> Two or more modules with the same package are mapped to the + * same class loader. </p></li> * * <li><p> A module is mapped to a class loader that already has a * module of the same name defined to it. </p></li> @@ -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<String, ClassLoader> clf) + public static Controller defineModules(Configuration cf, + List<Layer> parentLayers, + Function<String, ClassLoader> clf) { - checkConfiguration(cf); + List<Layer> 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<Layer> parentLayers) + { Objects.requireNonNull(cf); - Optional<Configuration> oparent = cf.parent(); - if (!oparent.isPresent() || oparent.get() != this.configuration()) { - throw new IllegalArgumentException( - "Parent of configuration != configuration of this Layer"); + List<Configuration> 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<Layer> parent() { - return Optional.ofNullable(parent); + public List<Layer> 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<Layer> layers() { + List<Layer> allLayers = this.allLayers; + if (allLayers != null) + return allLayers.stream(); + + allLayers = new ArrayList<>(); + Set<Layer> visited = new HashSet<>(); + Deque<Layer> 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<Layer> 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 <em>tree of layers</em> 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<Module> 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}. * * <p> 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<Layer> ol = parent(); - if (ol.isPresent()) - return ol.get().findLoader(name); - throw new IllegalArgumentException("Module " + name - + " not known to this layer"); + Optional<Module> 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 <em>empty</em> 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<String, Set<ServiceProvider>> map = new HashMap<>(); - for (Module m : nameToModule.values()) { - ModuleDescriptor descriptor = m.getDescriptor(); - for (Provides provides : descriptor.provides().values()) { - String service = provides.service(); - Set<ServiceProvider> 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<ServiceProvider> findServices(String service) { - Set<ServiceProvider> 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<Layer> list = CLV.get(loader); + if (list == null) { + list = new CopyOnWriteArrayList<>(); + List<Layer> 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<Layer> layers(ClassLoader loader) { + List<Layer> 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<List<Layer>> CLV = new ClassLoaderValue<>(); }
--- a/src/java.base/share/classes/java/lang/reflect/Module.java Thu Dec 01 21:01:53 2016 +0000 +++ b/src/java.base/share/classes/java/lang/reflect/Module.java Thu Dec 01 21:39:49 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<String> 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<Module> EVERYONE = Collections.singleton(EVERYONE_MODULE); + // set contains EVERYONE_MODULE, used when a package is opened or + // exported unconditionally + private static final Set<Module> 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<Module> reads; // additional module (2nd key) that some module (1st key) reflectively reads - private static final WeakPairMap<Module, Module, Boolean> transientReads + private static final WeakPairMap<Module, Module, Boolean> 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 <em>Read edges</em> added by this method are <em>weak</em> 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 that are permanently exported - // (will be final when the modules are defined in reverse topology order) - private volatile Map<String, Set<Module>> exports; + // the packages are open to other modules, can be null + // if the value contains EVERYONE_MODULE then the package is open to all + private volatile Map<String, Set<Module>> openPackages; - // additional exports added at run-time - // this module (1st key), other module (2nd key), exported packages (value) + // the packages that are exported, can be null + // if the value contains EVERYONE_MODULE then the package is exported to all + private volatile Map<String, Set<Module>> exportedPackages; + + // additional exports or opens added at run-time + // this module (1st key), other module (2nd key) + // (package name, open?) (value) private static final WeakPairMap<Module, Module, Map<String, Boolean>> - transientExports = new WeakPairMap<>(); + reflectivelyExports = new WeakPairMap<>(); /** * Returns {@code true} if this module exports the given package to at * least the given module. * - * <p> This method always return {@code true} when invoked on an unnamed + * <p> 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. </p> * - * <p> This method does not check if the given module reads this module </p> + * <p> This method does not check if the given module reads this module. </p> * * @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 <em>opened</em> a package to at + * least the given module. + * + * <p> 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. </p> + * + * <p> This method does not check if the given module reads this module. </p> + * + * @param pn + * The package name + * @param other + * The other module + * + * @return {@code true} if this module has <em>opened</em> 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. * - * <p> This method always return {@code true} when invoked on an unnamed - * module. </p> + * <p> 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. </p> * - * <p> This method does not check if the given module reads this module </p> + * <p> This method does not check if the given module reads this module. </p> * * @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 <em>opened</em> a package + * unconditionally. + * + * <p> 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. </p> + * + * <p> This method does not check if the given module reads this module. </p> + * + * @param pn + * The package name + * + * @return {@code true} if this module has <em>opened</em> 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<String, Set<Module>> exports = this.exports; - if (exports != null) { - Set<Module> targets = exports.get(pn); + private boolean isStaticallyExportedOrOpen(String pn, Module other, boolean open) { + // package is open to everyone or <other> + Map<String, Set<Module>> openPackages = this.openPackages; + if (openPackages != null) { + Set<Module> 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 <other> + Map<String, Set<Module>> exportedPackages = this.exportedPackages; + if (exportedPackages != null) { + Set<Module> 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<String, ?> 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<String, Boolean> 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. * - * <p> 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). </p> + * <p> This method has no effect if the package is already exported (or + * <em>open</em>) to the given module. It also has no effect if + * invoked on an {@link ModuleDescriptor#isOpen open} module. </p> * * @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 + * <em>open</em> 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. + * + * <p> This method has no effect if the package is already <em>open</em> + * to the given module. It also has no effect if invoked on an {@link + * ModuleDescriptor#isOpen open} module. </p> + * + * @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 + Map<String, Boolean> map = reflectivelyExports .computeIfAbsent(this, other, - (_this, _other) -> new ConcurrentHashMap<>()) - .putIfAbsent(pn, Boolean.TRUE); + (m1, m2) -> new ConcurrentHashMap<>()); + + if (open) { + map.put(pn, Boolean.TRUE); // may need to promote from FALSE to TRUE + } else { + map.putIfAbsent(pn, Boolean.FALSE); + } } // -- services -- // additional service type (2nd key) that some module (1st key) uses - private static final WeakPairMap<Module, Class<?>, Boolean> transientUses + private static final WeakPairMap<Module, Class<?>, 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. * * <p> This method does not cause {@link * Configuration#resolveRequiresAndUses resolveRequiresAndUses} to be * re-run. </p> *