OpenJDK / jigsaw / jake / jdk
changeset 12720:d82cd22736fd
Initial support for automatic modules
line wrap: on
line diff
--- a/src/java.base/share/classes/java/lang/module/Configuration.java Thu May 14 12:16:00 2015 -0700 +++ b/src/java.base/share/classes/java/lang/module/Configuration.java Fri May 15 13:02:59 2015 +0100 @@ -45,7 +45,7 @@ * ModuleFinder.of(dir1, dir2, dir3); * * Configuration cf = - * Configuration.resolve(ModuleFinder.nullFinder(), + * Configuration.resolve(ModuleFinder.empty(), * Layer.bootLayer(), * finder, * "myapp")
--- a/src/java.base/share/classes/java/lang/module/Layer.java Thu May 14 12:16:00 2015 -0700 +++ b/src/java.base/share/classes/java/lang/module/Layer.java Fri May 15 13:02:59 2015 +0100 @@ -26,6 +26,7 @@ package java.lang.module; import java.lang.reflect.Module; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Map; @@ -34,6 +35,7 @@ import sun.misc.JavaLangModuleAccess; import sun.misc.JavaLangReflectAccess; +import sun.misc.Modules; import sun.misc.SharedSecrets; /** @@ -48,7 +50,7 @@ * ModuleFinder.of(dir1, dir2, dir3); * * Configuration cf = - * Configuration.resolve(ModuleFinder.nullFinder(), + * Configuration.resolve(ModuleFinder.empty(), * Layer.bootLayer(), * finder, * "myapp"); @@ -114,7 +116,38 @@ public static Layer create(Configuration cf, ClassLoaderFinder clf) { Objects.requireNonNull(cf); Objects.requireNonNull(clf); - return new Layer(cf, reflectAccess.defineModules(cf, clf)); + Layer layer = new Layer(cf, reflectAccess.defineModules(cf, clf)); + + // Update the readability graph so that every automatic module in the + // newly created Layer reads every other module. + // We do this here for now but it may move. + cf.descriptors().stream() + .filter(ModuleDescriptor::isAutomatic) + .map(ModuleDescriptor::name) + .map(layer::findModule) + .forEach(m -> layer.fixupAutomaticModule(m)); + + return layer; + } + + /** + * Changes an automatic module to be a "loose" module, and adds a + * read edge to every module in the Layer (and all parent Layers). + */ + private void fixupAutomaticModule(Module autoModule) { + assert autoModule.getDescriptor().isAutomatic(); + + // automatic modules read all unnamed modules + // (use Modules.addReads to skip permission check) + Modules.addReads(autoModule, null); + + // automatic modules read all modules in this, and parent, layers + Layer l = this; + do { + Collection<Module> modules = l.nameToModule.values(); + modules.forEach(m -> Modules.addReads(autoModule, m)); + l = l.parent(); + } while (l != Layer.emptyLayer()); } /**
--- a/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java Thu May 14 12:16:00 2015 -0700 +++ b/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java Fri May 15 13:02:59 2015 +0100 @@ -264,6 +264,9 @@ private final Set<String> uses; private final Map<String, Provides> provides; + // Indicates if synthesised for a JAR file found on the module path + private final boolean automatic; + // "Extended" information, added post-compilation private final Optional<Version> version; private final Optional<String> mainClass; @@ -272,6 +275,7 @@ private final Optional<DependencyHashes> hashes; private ModuleDescriptor(String name, + boolean automatic, Map<String, Requires> requires, Set<String> uses, Map<String, Exports> exports, @@ -283,6 +287,7 @@ { this.name = requireModuleName(name); + this.automatic = automatic; Set<Requires> rqs = new HashSet<>(requires.values()); assert (rqs.stream().map(Requires::name).sorted().distinct().count() @@ -324,6 +329,13 @@ } /** + * <p> Indicates if this is an automatic module. </p> + */ + /* package */ boolean isAutomatic() { + return automatic; + } + + /** * <p> The dependences of this module </p> * * @return A possibly-empty unmodifiable set of {@link Requires} objects @@ -404,10 +416,13 @@ /** * A builder used for building {@link ModuleDescriptor} objects. + * + * @apiNote Should Builder be final? */ public static class Builder { - String name; + final String name; + final boolean automatic; final Map<String, Requires> requires = new HashMap<>(); final Set<String> uses = new HashSet<>(); final Map<String, Exports> exports = new HashMap<>(); @@ -421,7 +436,12 @@ * Initializes a new builder. */ public Builder(String name) { + this(name, false); + } + + /* package */ Builder(String name, boolean automatic) { this.name = name; + this.automatic = automatic; } /** @@ -539,6 +559,7 @@ public ModuleDescriptor build() { assert name != null; return new ModuleDescriptor(name, + automatic, requires, uses, exports, @@ -571,6 +592,7 @@ return false; ModuleDescriptor that = (ModuleDescriptor)ob; return (name.equals(that.name) + && automatic == that.automatic && requires.equals(that.requires) && uses.equals(that.uses) && exports.equals(that.exports) @@ -588,6 +610,7 @@ int hc = hash; if (hc == 0) { hc = name.hashCode(); + hc = hc * 43 + Boolean.hashCode(automatic); hc = hc * 43 + requires.hashCode(); hc = hc * 43 + uses.hashCode(); hc = hc * 43 + exports.hashCode();
--- a/src/java.base/share/classes/java/lang/module/ModuleFinder.java Thu May 14 12:16:00 2015 -0700 +++ b/src/java.base/share/classes/java/lang/module/ModuleFinder.java Fri May 15 13:02:59 2015 +0100 @@ -39,7 +39,7 @@ * * <p> An important property is that a {@code ModuleFinder} admits to * at most one module with a given name. A {@code ModuleFinder} that - * finds modules in sequence of directories for example, will locate the first + * finds modules in a sequence of directories for example, will locate the first * occurrence of a module and ignores other modules of that name that appear in * directories later in the sequence. </p> *
--- a/src/java.base/share/classes/java/lang/module/ModulePath.java Thu May 14 12:16:00 2015 -0700 +++ b/src/java.base/share/classes/java/lang/module/ModulePath.java Fri May 15 13:02:59 2015 +0100 @@ -195,12 +195,12 @@ if (ze == null) { throw new IOException(MODULE_INFO + " is missing: " + file); } - // jmod URI - syntax not defined yet - URI location = URI.create("jmod:" + file.toUri() + "!/"); ModuleDescriptor md; try (InputStream in = zf.getInputStream(ze)) { md = ModuleDescriptor.read(in, () -> jmodPackages(zf)); } + // jmod URI - syntax not defined yet + URI location = URI.create("jmod:" + file.toUri() + "!/"); HashSupplier hasher = (algorithm) -> Hasher.generate(file, algorithm); return ModuleReferences.newModuleReference(md, location, hasher); } @@ -216,21 +216,61 @@ } /** + * Derive a module name from the given JAR file name. + * + * @apiNote This needs to move to somewhere where it can be used by tools, + * maybe even a standard API if automatic modules are a Java SE feature. + */ + private String deriveModuleNameFromJarName(String name) { + // drop .jar + name = name.substring(0, name.length()-4); + + // drop -${VERSION} + int index = name.lastIndexOf('-'); + if (index > 0) { + String tail = name.substring(index+1); + if (tail.matches("^(\\d+\\.)?(\\d+\\.)?(\\*|\\d+)$")) { + name = name.substring(0, index); + } + } + + // drop all non-alphanumeric chars + return name.replaceAll("[^A-Za-z0-9]", ""); + } + + /** * Returns a {@code ModuleReference} to represent a module jar on the * file system. */ private ModuleReference readJar(Path file) throws IOException { try (JarFile jf = new JarFile(file.toString())) { + + ModuleDescriptor md; JarEntry entry = jf.getJarEntry(MODULE_INFO); if (entry == null) { - // not a modular jar - return null; + + // no module-info.class so treat as automatic module + // Builder throws IAE if module name is empty + String fn = file.getFileName().toString(); + String mn = deriveModuleNameFromJarName(fn); + ModuleDescriptor.Builder builder + = new ModuleDescriptor.Builder(mn, true).requires("java.base"); + + // all packages are exported + jarPackages(jf).stream().forEach(p -> builder.exports(p)); + + // TBD: Need to scan META-INF/services for any configuration files + + md = builder.build(); + + } else { + md = ModuleDescriptor.read(jf.getInputStream(entry), + () -> jarPackages(jf)); } - // jar URI + URI location = URI.create("jar:" + file.toUri() + "!/"); - ModuleDescriptor md = ModuleDescriptor.read(jf.getInputStream(entry), - () -> jarPackages(jf)); HashSupplier hasher = (algorithm) -> Hasher.generate(file, algorithm); + return ModuleReferences.newModuleReference(md, location, hasher); } } @@ -254,14 +294,14 @@ * on the file system. */ private ModuleReference readExploded(Path dir) throws IOException { - Path file = dir.resolve(MODULE_INFO); - if (Files.notExists((file))) { + Path mi = dir.resolve(MODULE_INFO); + if (Files.notExists(mi)) { // no module-info in directory return null; } URI location = dir.toUri(); ModuleDescriptor md; - try (InputStream in = Files.newInputStream(file)) { + try (InputStream in = Files.newInputStream(mi)) { md = ModuleDescriptor.read(new BufferedInputStream(in), () -> explodedPackages(dir)); }
--- a/src/java.base/share/classes/java/lang/module/Resolver.java Thu May 14 12:16:00 2015 -0700 +++ b/src/java.base/share/classes/java/lang/module/Resolver.java Fri May 15 13:02:59 2015 +0100 @@ -40,6 +40,7 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import jdk.internal.module.Hasher.DependencyHashes; @@ -204,13 +205,16 @@ private Set<ModuleDescriptor> resolve(Deque<ModuleDescriptor> q) { Set<ModuleDescriptor> newlySelected = new HashSet<>(); + // true if all modules are selected + boolean allModulesSelected = false; + while (!q.isEmpty()) { ModuleDescriptor descriptor = q.poll(); assert nameToReference.containsKey(descriptor.name()); selected.add(descriptor); // process dependences - for (ModuleDescriptor.Requires requires: descriptor.requires()) { + for (ModuleDescriptor.Requires requires : descriptor.requires()) { String dn = requires.name(); // before finder @@ -243,6 +247,28 @@ q.offer(other); } } + + // If an automatic module is encountered then its dependences are + // not known so this requires resolving all modules. We do this at + // most once. + if (descriptor.isAutomatic() && !allModulesSelected) { + + findAll().forEach(mref -> { + ModuleDescriptor other = mref.descriptor(); + if (!selected.contains(other) && !newlySelected.contains(other)) { + + trace("Module %s located (%s), implicitly required by %s", + other.name(), mref.location(), descriptor.name()); + + newlySelected.add(other); + nameToReference.put(other.name(), mref); + q.offer(other); + } + }); + + allModulesSelected = true; + } + } return newlySelected; @@ -257,22 +283,13 @@ // Scan the finders for all available service provider modules. As java.base // uses services then all finders will need to be scanned anyway. Map<String, Set<ModuleReference>> availableProviders = new HashMap<>(); - for (ModuleReference mref : findAll(beforeFinder)) { + findAll().forEach(mref -> { ModuleDescriptor descriptor = mref.descriptor(); if (!descriptor.provides().isEmpty()) { descriptor.provides().keySet().forEach(s -> availableProviders.computeIfAbsent(s, k -> new HashSet<>()).add(mref)); } - } - for (ModuleReference mref : findAll(afterFinder)) { - ModuleDescriptor descriptor = mref.descriptor(); - // the parent layer may hide service providers from afterFinder - if (!descriptor.provides().isEmpty() && layer.findModule(descriptor.name()) == null) { - descriptor.provides().keySet().forEach(s -> - availableProviders.computeIfAbsent(s, k -> new HashSet<>()).add(mref)); - } - } - + }); // create the visit stack Deque<ModuleDescriptor> q = new ArrayDeque<>(); @@ -531,6 +548,13 @@ /** + * Returns true if a module of the given module's name is in a parent Layer + */ + private boolean inParentLayer(ModuleReference mref) { + return layer.findModule(mref.descriptor().name()) != null; + } + + /** * Invokes the finder's find method to find the given module. */ private static ModuleReference find(ModuleFinder finder, String mn) { @@ -546,9 +570,13 @@ /** * Invokes the finder's allModules to find all modules. */ - private static Set<ModuleReference> findAll(ModuleFinder finder) { + private Stream<ModuleReference> findAll() { try { - return finder.findAll(); + Stream<ModuleReference> s1 + = beforeFinder.findAll().stream(); + Stream<ModuleReference> s2 + = afterFinder.findAll().stream().filter(m -> !inParentLayer(m)); + return Stream.concat(s1, s2); } catch (UncheckedIOException e) { throw new ResolutionException(e.getCause()); } catch (RuntimeException | Error e) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jigsaw/etc/automaticmodules/basic.sh Fri May 15 13:02:59 2015 +0100 @@ -0,0 +1,70 @@ +# +# Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# 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. +# + +# @test +# @summary Basic test for automatic modules + + +set -e + +if [ -z "$TESTJAVA" ]; then + if [ $# -lt 1 ]; then exit 1; fi + TESTJAVA="$1"; shift + COMPILEJAVA="${TESTJAVA}" + TESTSRC="`pwd`" + TESTCLASSES="`pwd`" +fi + +JAVAC="$COMPILEJAVA/bin/javac" +JAVA="$TESTJAVA/bin/java ${TESTVMOPTS}" +JAR="$TESTJAVA/bin/jar ${TESTTOOLVMOPTS}" + +rm -rf mods + +# Compile logging and http server libraries as modules as javac doesn't +# support automatic modules yet + +mkdir -p mods/logging +$JAVAC -d mods/logging `find $TESTSRC/src/logging -name "*.java"` + +mkdir -p mods/httpserver +$JAVAC -d mods/httpserver -mp mods `find $TESTSRC/src/httpserver -name "*.java"` + +mkdir -p mods/test +$JAVAC -d mods/test -mp mods `find $TESTSRC/src/test -name "*.java"` + + +# Create regular (non-modular JAR files) + +$JAR cf mods/logging-1.0.jar -C mods/logging logging +rm -rf mods/logging + +$JAR cf mods/http-server-9.0.0.jar -C mods/httpserver http +rm -rf mods/httpserver + + +# Run the test +$JAVA -mp mods -m test/test.Main + + +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jigsaw/etc/automaticmodules/src/httpserver/http/HttpServer.java Fri May 15 13:02:59 2015 +0100 @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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 http; + +import logging.Logger; + +/** + * A do-nothing HTTP server + */ + +public class HttpServer { + private final int port; + private final Logger logger; + + public HttpServer(int port) { + this.port = port; + this.logger = new Logger(); + } + + public void start() { + logger.log("Start HTTP server on port " + port); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jigsaw/etc/automaticmodules/src/httpserver/module-info.java Fri May 15 13:02:59 2015 +0100 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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. + */ + +module httpserver { + requires logging; + exports http; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jigsaw/etc/automaticmodules/src/logging/logging/Logger.java Fri May 15 13:02:59 2015 +0100 @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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 logging; + +/** + * A simple logger. + */ + +public class Logger { + public Logger() { } + + public void log(String msg) { + System.out.println(msg); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jigsaw/etc/automaticmodules/src/logging/module-info.java Fri May 15 13:02:59 2015 +0100 @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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. + */ + +module logging { + exports logging; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jigsaw/etc/automaticmodules/src/test/module-info.java Fri May 15 13:02:59 2015 +0100 @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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. + */ + +module test { + requires httpserver; // automatic module +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jigsaw/etc/automaticmodules/src/test/test/Main.java Fri May 15 13:02:59 2015 +0100 @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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 test; + +import java.lang.module.*; +import java.lang.reflect.Module; + +import http.HttpServer; + +/** + * Basic test using automatic modules. + */ + +public class Main { + public static void main(String[] args) throws Exception { + + Module httpModule = HttpServer.class.getModule(); + + // automatic modules are named + assertTrue(httpModule.isNamed()); + + // and loose + assertTrue(httpModule.canRead(null)); + + // and read all modules in the boot Layer + Layer layer = Layer.bootLayer(); + layer.configuration().descriptors().stream() + .map(ModuleDescriptor::name) + .map(layer::findModule) + .forEach(m -> assertTrue(httpModule.canRead(m))); + + // run code in the automatic modue, ensures access is allowed + HttpServer http = new HttpServer(80); + http.start(); + } + + static void assertTrue(boolean e) { + if (!e) + throw new RuntimeException(); + } +}