# HG changeset patch # User chegar # Date 1481204267 0 # Thu Dec 08 13:37:47 2016 +0000 # Branch incubator-branch # Node ID 62027433bad78588c8c1ca506125aa55769eac30 # Parent d5d383fcafd033a8284f5104322d749e43f81e54 8170859: Run time and tool support for ModuleResolution Reviewed-by: alanb diff --git a/src/java.base/share/classes/com/sun/java/util/jar/pack/intrinsic.properties b/src/java.base/share/classes/com/sun/java/util/jar/pack/intrinsic.properties --- a/src/java.base/share/classes/com/sun/java/util/jar/pack/intrinsic.properties +++ b/src/java.base/share/classes/com/sun/java/util/jar/pack/intrinsic.properties @@ -21,6 +21,7 @@ pack.class.attribute.ModuleMainClass = RCH pack.class.attribute.ModuleTarget = RUHRUHRUH pack.class.attribute.ModuleHashes = RUHNH[RUHNH[B]] +pack.class.attribute.ModuleResolution = FH # Note: Zero-length ("marker") attributes do not need to be specified here. diff --git a/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java b/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java --- a/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java +++ b/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java @@ -53,6 +53,7 @@ import jdk.internal.module.Checks; import jdk.internal.module.ModuleHashes; +import jdk.internal.module.WarnIfResolvedReason; /** @@ -992,6 +993,8 @@ private final String osVersion; private final Set packages; private final ModuleHashes hashes; + private final boolean doNotResolveByDefault; + private final WarnIfResolvedReason warnIfResolvedReason; private ModuleDescriptor(String name, @@ -1009,7 +1012,9 @@ String osArch, String osVersion, Set packages, - ModuleHashes hashes) + ModuleHashes hashes, + boolean doNotResolveByDefault, + WarnIfResolvedReason warnIfResolvedReason) { this.name = name; @@ -1032,6 +1037,8 @@ this.osVersion = osVersion; this.hashes = hashes; this.packages = emptyOrUnmodifiableSet(packages); + this.doNotResolveByDefault = doNotResolveByDefault; + this.warnIfResolvedReason = warnIfResolvedReason; } /** @@ -1055,6 +1062,8 @@ this.osArch = md.osArch; this.osVersion = md.osVersion; this.hashes = null; // need to ignore + this.doNotResolveByDefault = md.doNotResolveByDefault; + this.warnIfResolvedReason = md.warnIfResolvedReason; Set packages = new HashSet<>(md.packages); packages.addAll(pkgs); @@ -1082,6 +1091,8 @@ Set packages, ModuleHashes hashes, int hashCode, + boolean doNotResolveByDefault, + WarnIfResolvedReason warnIfResolvedReason, boolean unused) { this.name = name; this.open = open; @@ -1100,6 +1111,8 @@ this.osVersion = osVersion; this.hashes = hashes; this.hash = hashCode; + this.doNotResolveByDefault = doNotResolveByDefault; + this.warnIfResolvedReason = warnIfResolvedReason; } /** @@ -1291,6 +1304,10 @@ return Optional.ofNullable(hashes); } + /* package */ boolean doNotResolveByDefault() { + return doNotResolveByDefault; + } + /** * A builder used for building {@link ModuleDescriptor} objects. @@ -1334,6 +1351,8 @@ String osVersion; String mainClass; ModuleHashes hashes; + boolean doNotResolveByDefault; + WarnIfResolvedReason warnIfResolvedReason = WarnIfResolvedReason.NONE; /** * Initializes a new builder with the given module name. @@ -1705,6 +1724,15 @@ return opens(Collections.emptySet(), pn); } + /* package */ Builder doNotResolveByDefault(boolean value) { + this.doNotResolveByDefault = value; + return this; + } + + /* package */ Builder warnIfResolved(WarnIfResolvedReason reason) { + this.warnIfResolvedReason = reason; + return this; + } // Used by ModuleInfo, after a packageFinder is invoked /* package */ Set exportedAndOpenPackages() { @@ -2013,7 +2041,9 @@ osArch, osVersion, packages, - hashes); + hashes, + doNotResolveByDefault, + warnIfResolvedReason); } } @@ -2089,7 +2119,9 @@ && Objects.equals(osArch, that.osArch) && Objects.equals(osVersion, that.osVersion) && Objects.equals(packages, that.packages) - && Objects.equals(hashes, that.hashes)); + && Objects.equals(hashes, that.hashes) + && doNotResolveByDefault == that.doNotResolveByDefault + && warnIfResolvedReason == that.warnIfResolvedReason); } private transient int hash; // cached hash code @@ -2123,6 +2155,8 @@ hc = hc * 43 + Objects.hashCode(osVersion); hc = hc * 43 + Objects.hashCode(packages); hc = hc * 43 + Objects.hashCode(hashes); + hc = hc * 43 + Objects.hashCode(doNotResolveByDefault); + hc = hc * 43 + Objects.hashCode(warnIfResolvedReason); if (hc == 0) hc = -1; hash = hc; @@ -2448,7 +2482,9 @@ String osVersion, Set packages, ModuleHashes hashes, - int hashCode) { + int hashCode, + boolean doNotResolveByDefault, + WarnIfResolvedReason reason) { return new ModuleDescriptor(name, open, automatic, @@ -2466,6 +2502,8 @@ packages, hashes, hashCode, + doNotResolveByDefault, + reason, false); } @@ -2491,6 +2529,16 @@ } @Override + public boolean doNotResolveByDefault(ModuleDescriptor descriptor) { + return descriptor.doNotResolveByDefault; + } + + @Override + public WarnIfResolvedReason warnIfResolvedReason(ModuleDescriptor descriptor) { + return descriptor.warnIfResolvedReason; + } + + @Override public ModuleFinder newModulePath(Runtime.Version version, boolean isLinkPhase, Path... entries) { diff --git a/src/java.base/share/classes/java/lang/module/ModuleInfo.java b/src/java.base/share/classes/java/lang/module/ModuleInfo.java --- a/src/java.base/share/classes/java/lang/module/ModuleInfo.java +++ b/src/java.base/share/classes/java/lang/module/ModuleInfo.java @@ -47,9 +47,10 @@ import java.util.function.Supplier; import jdk.internal.module.ModuleHashes; +import jdk.internal.module.WarnIfResolvedReason; import static jdk.internal.module.ClassFileConstants.*; - +import static jdk.internal.module.WarnIfResolvedReason.fromClassFileFlags; /** * Read module information from a {@code module-info} class file. @@ -193,6 +194,7 @@ String mainClass = null; String[] osValues = null; ModuleHashes hashes = null; + int moduleResolution = 0; for (int i = 0; i < attributes_count ; i++) { int name_index = in.readUnsignedShort(); @@ -235,6 +237,10 @@ } break; + case MODULE_RESOLUTION : + moduleResolution = readModuleResolution(in, cpool); + break; + default: if (isAttributeDisallowed(attribute_name)) { throw invalidModuleDescriptor(attribute_name @@ -289,6 +295,10 @@ } if (hashes != null) builder.hashes(hashes); + if (moduleResolution != 0) { + builder.doNotResolveByDefault(doNotResolveFromFlags(moduleResolution)); + builder.warnIfResolved(fromClassFileFlags(moduleResolution)); + } return builder.build(); } @@ -529,6 +539,34 @@ return new ModuleHashes(algorithm, map); } + /** + * Reads the ModuleResolution attribute. + */ + private int readModuleResolution(DataInput in, ConstantPool cpool) + throws IOException + { + int flags = in.readUnsignedShort(); + + // just some basic validation + int reason = 0; + if ((flags & WARN_DEPRECATED) != 0) + reason = WARN_DEPRECATED; + if ((flags & WARN_DEPRECATED_FOR_REMOVAL) != 0) { + if (reason != 0) + throw invalidModuleDescriptor("Bad module resolution flags:" + flags); + reason = WARN_DEPRECATED_FOR_REMOVAL; + } + if ((flags & WARN_INCUBATING) != 0) { + if (reason != 0) + throw invalidModuleDescriptor("Bad module resolution flags:" + flags); + } + + return flags; + } + + private boolean doNotResolveFromFlags(int flags) { + return (flags & DO_NOT_RESOLVE_BY_DEFAULT) != 0; + } /** * Returns true if the given attribute can be present at most once @@ -543,7 +581,8 @@ name.equals(MODULE_VERSION) || name.equals(MODULE_MAIN_CLASS) || name.equals(MODULE_TARGET) || - name.equals(MODULE_HASHES)) + name.equals(MODULE_HASHES) || + name.equals(MODULE_RESOLUTION)) return true; return false; diff --git a/src/java.base/share/classes/jdk/internal/misc/JavaLangModuleAccess.java b/src/java.base/share/classes/jdk/internal/misc/JavaLangModuleAccess.java --- a/src/java.base/share/classes/jdk/internal/misc/JavaLangModuleAccess.java +++ b/src/java.base/share/classes/jdk/internal/misc/JavaLangModuleAccess.java @@ -46,6 +46,7 @@ import java.util.function.Supplier; import jdk.internal.module.ModuleHashes; +import jdk.internal.module.WarnIfResolvedReason; /** * Provides access to non-public methods in java.lang.module. @@ -137,7 +138,9 @@ String osVersion, Set packages, ModuleHashes hashes, - int hashCode); + int hashCode, + boolean doNotResolveByDefault, + WarnIfResolvedReason reason); /** * Returns the object with the hashes of other modules @@ -145,6 +148,17 @@ Optional hashes(ModuleDescriptor descriptor); /** + * Returns the value of the DO_NOT_RESOLVE_BY_DEFAULT bit. + */ + boolean doNotResolveByDefault(ModuleDescriptor descriptor); + + /** + * Returns the warn if resolved reason, if any. + */ + WarnIfResolvedReason warnIfResolvedReason(ModuleDescriptor descriptor); + + + /** * Resolves a collection of root modules, with service binding * and the empty configuration as the parent. The post resolution * checks are optionally run. diff --git a/src/java.base/share/classes/jdk/internal/module/Builder.java b/src/java.base/share/classes/jdk/internal/module/Builder.java --- a/src/java.base/share/classes/jdk/internal/module/Builder.java +++ b/src/java.base/share/classes/jdk/internal/module/Builder.java @@ -132,6 +132,8 @@ String osVersion; String algorithm; Map hashes; + boolean doNotResolveByDefault; + WarnIfResolvedReason warnIfResolvedReason; Builder(String name) { this.name = name; @@ -140,6 +142,7 @@ this.opens = Collections.emptySet(); this.provides = Collections.emptySet(); this.uses = Collections.emptySet(); + warnIfResolvedReason = WarnIfResolvedReason.NONE; } Builder open(boolean value) { @@ -294,6 +297,22 @@ } /** + * Sets module DO_NOT_RESOLVE_BY_DEFAULT. + */ + public Builder doNotResolveByDefault(boolean doNotResolveByDefault) { + this.doNotResolveByDefault = doNotResolveByDefault; + return this; + } + + /** + * Sets module WARN_IF_RESOLVED. + */ + public Builder warnIfResolved(WarnIfResolvedReason reason) { + this.warnIfResolvedReason = reason; + return this; + } + + /** * Builds a {@code ModuleDescriptor} from the components. */ public ModuleDescriptor build(int hashCode) { @@ -318,6 +337,8 @@ osVersion, packages, moduleHashes, - hashCode); + hashCode, + doNotResolveByDefault, + warnIfResolvedReason); } } diff --git a/src/java.base/share/classes/jdk/internal/module/ClassFileAttributes.java b/src/java.base/share/classes/jdk/internal/module/ClassFileAttributes.java --- a/src/java.base/share/classes/jdk/internal/module/ClassFileAttributes.java +++ b/src/java.base/share/classes/jdk/internal/module/ClassFileAttributes.java @@ -740,4 +740,82 @@ } } + + /** + * ModuleResolution_attribute { + * u2 attribute_name_index; // "ModuleResolution" + * u4 attribute_length; // 2 + * u2 resolution_flags; + * + * The value of the resolution_flags item is a mask of flags used to denote + * properties of module resolution. The flags are as follows: + * + * // Optional + * 0x0001 (DO_NOT_RESOLVE_BY_DEFAULT) + * + * // At most one of: + * 0x0002 (WARN_DEPRECATED) + * 0x0004 (WARN_DEPRECATED_FOR_REMOVAL) + * 0x0008 (WARN_INCUBATING) + */ + static class ModuleResolution extends Attribute { + private final boolean doNotResolveByDefault; + private final WarnIfResolvedReason warnIfResolvedReason; + + ModuleResolution(boolean doNotResolveByDefault, + WarnIfResolvedReason warnIfResolvedReason) { + super(MODULE_RESOLUTION); + this.doNotResolveByDefault= doNotResolveByDefault; + this.warnIfResolvedReason = warnIfResolvedReason; + } + + ModuleResolution() { + this(false, WarnIfResolvedReason.NONE); + } + + @Override + protected Attribute read(ClassReader cr, + int off, + int len, + char[] buf, + int codeOff, + Label[] labels) + { + int flags = cr.readUnsignedShort(off); + + boolean doNotResolve = false; + if ((flags & DO_NOT_RESOLVE_BY_DEFAULT) != 0) + doNotResolve = true; + + WarnIfResolvedReason warnReason = WarnIfResolvedReason.NONE; + if ((flags & WARN_DEPRECATED) != 0) + warnReason = WarnIfResolvedReason.DEPRECATED; + else if ((flags & WARN_DEPRECATED_FOR_REMOVAL) != 0) + warnReason = WarnIfResolvedReason.DEPRECATED_FOR_REMOVAL; + else if ((flags & WARN_INCUBATING) != 0) + warnReason = WarnIfResolvedReason.INCUBATING; + + return new ModuleResolution(doNotResolve, warnReason); + } + + @Override + protected ByteVector write(ClassWriter cw, + byte[] code, + int len, + int maxStack, + int maxLocals) + { + int flags = 0; + if (doNotResolveByDefault) + flags |= DO_NOT_RESOLVE_BY_DEFAULT; + + int reason = warnIfResolvedReason.classFileConstant(); + if (reason != 0) + flags |= reason; + + ByteVector attr = new ByteVector(); + attr.putShort(flags); + return attr; + } + } } diff --git a/src/java.base/share/classes/jdk/internal/module/ClassFileConstants.java b/src/java.base/share/classes/jdk/internal/module/ClassFileConstants.java --- a/src/java.base/share/classes/jdk/internal/module/ClassFileConstants.java +++ b/src/java.base/share/classes/jdk/internal/module/ClassFileConstants.java @@ -42,6 +42,7 @@ public static final String MODULE_MAIN_CLASS = "ModuleMainClass"; public static final String MODULE_TARGET = "ModuleTarget"; public static final String MODULE_HASHES = "ModuleHashes"; + public static final String MODULE_RESOLUTION = "ModuleResolution"; // access, requires, exports, and opens flags public static final int ACC_MODULE = 0x8000; @@ -51,4 +52,10 @@ public static final int ACC_SYNTHETIC = 0x1000; public static final int ACC_MANDATED = 0x8000; + // ModuleResolution_attribute resolution flags + public static final int DO_NOT_RESOLVE_BY_DEFAULT = 0x0001; + public static final int WARN_DEPRECATED = 0x0002; + public static final int WARN_DEPRECATED_FOR_REMOVAL = 0x0004; + public static final int WARN_INCUBATING = 0x0008; + } diff --git a/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java b/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java --- a/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java +++ b/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java @@ -49,8 +49,10 @@ import jdk.internal.loader.BootLoader; import jdk.internal.loader.BuiltinClassLoader; +import jdk.internal.misc.JavaLangModuleAccess; import jdk.internal.misc.SharedSecrets; import jdk.internal.perf.PerfCounter; +import static jdk.internal.module.WarnIfResolvedReason.*; /** * Initializes/boots the module system. @@ -67,6 +69,9 @@ public final class ModuleBootstrap { private ModuleBootstrap() { } + private static final JavaLangModuleAccess JLMA + = SharedSecrets.getJavaLangModuleAccess(); + private static final String JAVA_BASE = "java.base"; private static final String JAVA_SE = "java.se"; @@ -195,7 +200,9 @@ // module is the unnamed module of the application class loader. This // is implemented by resolving "java.se" and all (non-java.*) modules // that export an API. If "java.se" is not observable then all java.* - // modules are resolved. + // modules are resolved. Modules that have the DO_NOT_RESOLVE_BY_DEFAULT + // bit set in their ModuleResolution attribute flags are excluded from + // the detault set of roots. if (mainModule == null || addAllDefaultModules) { boolean hasJava = false; if (systemModules.find(JAVA_SE).isPresent()) { @@ -208,13 +215,17 @@ } for (ModuleReference mref : systemModules.findAll()) { - String mn = mref.descriptor().name(); + ModuleDescriptor descriptor = mref.descriptor(); + String mn = descriptor.name(); if (hasJava && mn.startsWith("java.")) continue; + if (JLMA.doNotResolveByDefault(descriptor)) { + continue; + } + // add as root if observable and exports at least one package if ((finder == systemModules || finder.find(mn).isPresent())) { - ModuleDescriptor descriptor = mref.descriptor(); for (ModuleDescriptor.Exports e : descriptor.exports()) { if (!e.isQualified()) { roots.add(mn); @@ -232,6 +243,7 @@ systemModules.findAll() .stream() .map(ModuleReference::descriptor) + .filter(md -> !JLMA.doNotResolveByDefault(md)) .map(ModuleDescriptor::name) .filter(mn -> f.find(mn).isPresent()) // observable .forEach(mn -> roots.add(mn)); @@ -277,6 +289,19 @@ // time to create configuration PerfCounters.resolveTime.addElapsedTimeFrom(t3); + // issue a warning for any incubator modules in the configuration + String incubating = null; + for (ResolvedModule rm : cf.modules()) { + ModuleDescriptor descriptor = rm.reference().descriptor(); + if (JLMA.warnIfResolvedReason(descriptor) == INCUBATING) + if (incubating == null) + incubating = descriptor.name(); + else + incubating += ", " + descriptor.name(); + } + + if (incubating != null) + warn("using incubating module(s): " + incubating); // mapping of modules to class loaders Function clf = ModuleLoaderMap.mappingFunction(cf); diff --git a/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java b/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java --- a/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java +++ b/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java @@ -43,6 +43,7 @@ import jdk.internal.org.objectweb.asm.Opcodes; import static jdk.internal.module.ClassFileAttributes.*; +import static jdk.internal.module.WarnIfResolvedReason.NONE; /** * Utility class to extend a module-info.class with additional attributes. @@ -70,6 +71,10 @@ // the hashes for the Hashes attribute private ModuleHashes hashes; + // ModuleResolution flags + private boolean doNotResolveByDefault; + private WarnIfResolvedReason warnIfResolvedReason = NONE; + private ModuleInfoExtender(InputStream in) { this.in = in; } @@ -121,6 +126,16 @@ } /** + * Sets the value for the ModuleResolution attribute. + */ + public ModuleInfoExtender moduleResolution(boolean doNotResolveByDefault, + WarnIfResolvedReason reason) { + this.doNotResolveByDefault = doNotResolveByDefault; + this.warnIfResolvedReason = reason; + return this; + } + + /** * A ClassVisitor that supports adding class file attributes. If an * attribute already exists then the first occurence of the attribute * is replaced. @@ -191,6 +206,10 @@ cv.addAttribute(new ModuleTargetAttribute(osName, osArch, osVersion)); if (hashes != null) cv.addAttribute(new ModuleHashesAttribute(hashes)); + if (doNotResolveByDefault || warnIfResolvedReason != NONE) + cv.addAttribute(new ModuleResolution(doNotResolveByDefault, + warnIfResolvedReason)); + List attrs = new ArrayList<>(); diff --git a/src/java.base/share/classes/jdk/internal/module/WarnIfResolvedReason.java b/src/java.base/share/classes/jdk/internal/module/WarnIfResolvedReason.java new file mode 100644 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/module/WarnIfResolvedReason.java @@ -0,0 +1,77 @@ +/* + * 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 jdk.internal.module; + +/** + * Represents the WARN_IF_RESOLVED reason as per the ModuleResolution attribute. + */ +public enum WarnIfResolvedReason { + /** + * No warn reason. + */ + NONE(0), + + /** + * Deprecated warn reason. + */ + DEPRECATED(ClassFileConstants.WARN_DEPRECATED), + + /** + * Deprecated for removal warn reason. + */ + DEPRECATED_FOR_REMOVAL(ClassFileConstants.WARN_DEPRECATED_FOR_REMOVAL), + + /** + * Incubating warn reason. + */ + INCUBATING(ClassFileConstants.WARN_INCUBATING); + + private final int classFileConstant; + + WarnIfResolvedReason(int classFileConstant) { + this.classFileConstant = classFileConstant; + } + + public int classFileConstant() { + return classFileConstant; + } + + public static WarnIfResolvedReason fromClassFileFlags(int value) { + value = value & 0x000E; // warn reason bits only + switch (value) { + case 0: + return NONE; + case ClassFileConstants.WARN_DEPRECATED: + return DEPRECATED; + case ClassFileConstants.WARN_DEPRECATED_FOR_REMOVAL: + return DEPRECATED_FOR_REMOVAL; + case ClassFileConstants.WARN_INCUBATING: + return INCUBATING; + default : + throw new InternalError("Unexpected value: " + value); + } + } +} diff --git a/src/jdk.jartool/share/classes/sun/tools/jar/GNUStyleOptions.java b/src/jdk.jartool/share/classes/sun/tools/jar/GNUStyleOptions.java --- a/src/jdk.jartool/share/classes/sun/tools/jar/GNUStyleOptions.java +++ b/src/jdk.jartool/share/classes/sun/tools/jar/GNUStyleOptions.java @@ -34,6 +34,7 @@ import java.nio.file.Paths; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import static jdk.internal.module.WarnIfResolvedReason.*; /** * Parser for GNU Style Options. @@ -171,6 +172,25 @@ } boolean isHidden() { return true; } }, + new Option(false, OptionType.CREATE_UPDATE, "--do-not-resolve-by-default") { + void process(Main jartool, String opt, String arg) { + jartool.doNotResolveByDefault = true; + } + boolean isHidden() { return true; } + }, + new Option(true, OptionType.CREATE_UPDATE, "--warn-if-resolved") { + void process(Main jartool, String opt, String arg) throws BadArgs { + if (arg.equals("deprecated")) + jartool.warnIfResolvedReason = DEPRECATED; + else if (arg.equals("deprecated-for-removal")) + jartool.warnIfResolvedReason = DEPRECATED_FOR_REMOVAL; + else if (arg.equals("incubating")) + jartool.warnIfResolvedReason = INCUBATING; + else + throw new BadArgs("err.bad.reason", arg).showUsage(true); + } + boolean isHidden() { return true; } + }, // Other options new Option(true, true, OptionType.OTHER, "--help", "-h") { diff --git a/src/jdk.jartool/share/classes/sun/tools/jar/Main.java b/src/jdk.jartool/share/classes/sun/tools/jar/Main.java --- a/src/jdk.jartool/share/classes/sun/tools/jar/Main.java +++ b/src/jdk.jartool/share/classes/sun/tools/jar/Main.java @@ -62,6 +62,7 @@ import jdk.internal.module.Checks; import jdk.internal.module.ModuleHashes; import jdk.internal.module.ModuleInfoExtender; +import jdk.internal.module.WarnIfResolvedReason; import jdk.internal.util.jar.JarIndex; import static jdk.internal.util.jar.JarIndex.INDEX_NAME; @@ -225,6 +226,8 @@ Version moduleVersion; Pattern modulesToHash; ModuleFinder moduleFinder = ModuleFinder.of(); + boolean doNotResolveByDefault; + WarnIfResolvedReason warnIfResolvedReason = WarnIfResolvedReason.NONE; private static final String MODULE_INFO = "module-info.class"; @@ -2094,7 +2097,12 @@ ModuleDescriptor vd = ModuleDescriptor.read(ByteBuffer.wrap(e.getValue())); if (!(isValidVersionedDescriptor(vd, rd))) return false; - e.setValue(extendedInfoBytes(rd, vd, e.getValue(), packages)); + e.setValue(extendedInfoBytes(rd, + vd, + e.getValue(), + packages, + doNotResolveByDefault, + warnIfResolvedReason)); } return true; } @@ -2179,7 +2187,9 @@ private byte[] extendedInfoBytes(ModuleDescriptor rootDescriptor, ModuleDescriptor md, byte[] miBytes, - Set packages) + Set packages, + boolean doNotResolveByDefault, + WarnIfResolvedReason warnReason) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -2214,6 +2224,9 @@ } } + if (doNotResolveByDefault || warnReason != WarnIfResolvedReason.NONE) + extender.moduleResolution(doNotResolveByDefault, warnReason); + extender.write(baos); return baos.toByteArray(); } diff --git a/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties b/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties --- a/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties +++ b/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties @@ -44,6 +44,8 @@ error.bad.eflag=\ 'e' flag and manifest with the 'Main-Class' attribute cannot be specified \n\ together! +error.bad.reason=\ + bad reason: {0}, must be one of deprecated, deprecated-for-removal, or incubating error.nosuch.fileordir=\ {0} : no such file or directory error.write.file=\ diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java @@ -40,6 +40,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -54,6 +55,7 @@ import jdk.tools.jlink.builder.DefaultImageBuilder; import jdk.tools.jlink.plugin.Plugin; import jdk.internal.misc.SharedSecrets; +import jdk.internal.module.WarnIfResolvedReason; /** * Implementation for the jlink tool. @@ -260,7 +262,8 @@ config.getModules(), config.getByteOrder(), null, - IGNORE_SIGNING_DEFAULT); + IGNORE_SIGNING_DEFAULT, + null); // Then create the Plugin Stack ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(plugins); @@ -328,7 +331,8 @@ roots, options.endian, options.packagedModulesPath, - options.ignoreSigning); + options.ignoreSigning, + log); // Then create the Plugin Stack ImagePluginStack stack = ImagePluginConfiguration. @@ -382,11 +386,16 @@ return Paths.get(uri); } + private static final Predicate hasIncubatorWarning = md -> + SharedSecrets.getJavaLangModuleAccess().warnIfResolvedReason(md) + == WarnIfResolvedReason.INCUBATING; + private static ImageProvider createImageProvider(ModuleFinder finder, Set roots, ByteOrder order, Path retainModulesPath, - boolean ignoreSigning) + boolean ignoreSigning, + PrintWriter log) throws IOException { if (roots.isEmpty()) { @@ -398,6 +407,20 @@ ModuleFinder.of(), roots); + // issue a warning for any incubating modules in the configuration + if (log != null) { + String incubatingModules = cf.modules() + .stream() + .map(ResolvedModule::reference) + .map(ModuleReference::descriptor) + .filter(hasIncubatorWarning) + .map(ModuleDescriptor::name) + .collect(Collectors.joining(", ")); + + if (!"".equals(incubatingModules)) + log.println("WARNING: using incubating module(s): " + incubatingModules); + } + Map mods = cf.modules().stream() .collect(Collectors.toMap(ResolvedModule::name, JlinkTask::toPathLocation)); return new ImageHelper(cf, mods, order, retainModulesPath, ignoreSigning); diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModuleDescriptorPlugin.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModuleDescriptorPlugin.java --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModuleDescriptorPlugin.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModuleDescriptorPlugin.java @@ -46,6 +46,7 @@ import jdk.internal.module.Checks; import jdk.internal.module.ModuleInfoExtender; import jdk.internal.module.SystemModules; +import jdk.internal.module.WarnIfResolvedReason; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.Opcodes; @@ -523,6 +524,14 @@ mh.names().forEach(mn -> moduleHash(mn, mh.hashFor(mn))); }); + // DO_NOT_RESOLVE_BY_DEFAULT + if (JLMA.doNotResolveByDefault(md)) { + setModuleBit("doNotResolveByDefault", true); + } + + // WARN_IF_RESOLVED reason + if (JLMA.warnIfResolvedReason(md) != WarnIfResolvedReason.NONE) + warnIfResolved(JLMA.warnIfResolvedReason(md)); putModuleDescriptor(); } @@ -846,6 +855,30 @@ "moduleHash", STRING_BYTE_ARRAY_SIG, false); mv.visitInsn(POP); } + + static final String WARN_IF_RESOLVED_REASON_CLASSNAME = + "jdk/internal/module/WarnIfResolvedReason"; + static final String WARN_IF_RESOLVED_REASON_TYPE = + "Ljdk/internal/module/WarnIfResolvedReason;"; + static final String WARN_METHOD_SIG = + "(" + WARN_IF_RESOLVED_REASON_TYPE + ")" + BUILDER_TYPE; + + /* + * Invoke Builder.warnIfResolved(WarnIfResolvedReason reason) + */ + void warnIfResolved(WarnIfResolvedReason reason) { + mv.visitVarInsn(ALOAD, BUILDER_VAR); + mv.visitFieldInsn(GETSTATIC, + WARN_IF_RESOLVED_REASON_CLASSNAME, + reason.name(), + WARN_IF_RESOLVED_REASON_TYPE); + mv.visitMethodInsn(INVOKEVIRTUAL, + MODULE_DESCRIPTOR_BUILDER, + "warnIfResolved", + WARN_METHOD_SIG, + false); + mv.visitInsn(POP); + } } /* diff --git a/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java b/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java --- a/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java @@ -104,6 +104,7 @@ import jdk.internal.misc.SharedSecrets; import jdk.internal.module.ModuleHashes; import jdk.internal.module.ModuleInfoExtender; +import jdk.internal.module.WarnIfResolvedReason; import jdk.tools.jlink.internal.Utils; import static java.util.stream.Collectors.joining; @@ -176,6 +177,8 @@ String osArch; String osVersion; Pattern modulesToHash; + boolean doNotResolveByDefault; + WarnIfResolvedReason warnIfResolvedReason = WarnIfResolvedReason.NONE; boolean dryrun; List excludes; } @@ -324,6 +327,13 @@ .append(hashes.algorithm()).append(" ") .append(hashes.hashFor(mod)))); + if (JLMA.doNotResolveByDefault(md)) + sb.append("\n doNotResolveByDefault true"); + + WarnIfResolvedReason reason = JLMA.warnIfResolvedReason(md); + if (!reason.equals(WarnIfResolvedReason.NONE)) + sb.append("\n warnIfResolved ").append(reason); + out.println(sb.toString()); } @@ -367,6 +377,8 @@ final String osVersion = options.osVersion; final List excludes = options.excludes; final Hasher hasher = hasher(); + final boolean doNotResolveByDefault = options.doNotResolveByDefault; + final WarnIfResolvedReason warnReason = options.warnIfResolvedReason; JmodFileWriter() { } @@ -475,6 +487,9 @@ } } + if (doNotResolveByDefault || warnReason != WarnIfResolvedReason.NONE) + extender.moduleResolution(doNotResolveByDefault, warnReason); + // write the (possibly extended or modified) module-info.class out.writeEntry(extender.toByteArray(), Section.CLASSES, MODULE_INFO); } @@ -1099,6 +1114,28 @@ @Override public String valuePattern() { return "regex-pattern"; } } + static class WarnIfResolvedReasonConverter + implements ValueConverter + { + @Override + public WarnIfResolvedReason convert(String value) { + if (value.equals("deprecated")) + return WarnIfResolvedReason.DEPRECATED; + else if (value.equals("deprecated-for-removal")) + return WarnIfResolvedReason.DEPRECATED_FOR_REMOVAL; + else if (value.equals("incubating")) + return WarnIfResolvedReason.INCUBATING; + else + throw new CommandException("err.bad.WarnIfResolvedReason", value); + } + + @Override public Class valueType() { + return WarnIfResolvedReason.class; + } + + @Override public String valuePattern() { return "reason"; } + } + static class PathMatcherConverter implements ValueConverter { @Override public PathMatcher convert(String pattern) { @@ -1129,6 +1166,11 @@ public String format(Map options) { Map all = new HashMap<>(); all.putAll(options); + + // hidden options + all.remove("do-not-resolve-by-default"); + all.remove("warn-if-resolved"); + all.put(CMD_FILENAME, new OptionDescriptor() { @Override public Collection options() { @@ -1270,6 +1312,14 @@ .withRequiredArg() .describedAs(getMessage("main.opt.os-version.arg")); + OptionSpec doNotResolveByDefault + = parser.accepts("do-not-resolve-by-default"); + + OptionSpec warnIfResolved + = parser.accepts("warn-if-resolved") + .withRequiredArg() + .withValuesConvertedBy(new WarnIfResolvedReasonConverter()); + OptionSpec version = parser.accepts("version", getMessage("main.opt.version")); @@ -1334,6 +1384,10 @@ throw new CommandException("err.modulepath.must.be.specified") .showUsage(true); } + if (opts.has(doNotResolveByDefault)) + options.doNotResolveByDefault = true; + if (opts.has(warnIfResolved)) + options.warnIfResolvedReason = opts.valueOf(warnIfResolved); if (options.mode.equals(Mode.HASH)) { if (options.moduleFinder == null || options.modulesToHash == null) diff --git a/src/jdk.jlink/share/classes/jdk/tools/jmod/resources/jmod.properties b/src/jdk.jlink/share/classes/jdk/tools/jmod/resources/jmod.properties --- a/src/jdk.jlink/share/classes/jdk/tools/jmod/resources/jmod.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jmod/resources/jmod.properties @@ -91,6 +91,8 @@ err.file.already.exists=file already exists: {0} err.jmod.not.found=no jmod file found: {0} err.bad.pattern=bad pattern {0} +err.bad.WarnIfResolvedReason=bad reason: {0}, must be one of deprecated,\ +\ deprecated-for-removal, or incubating err.unknown.option=unknown option(s): {0} err.missing.arg=no value given for {0} err.internal.error=internal error: {0} {1} {2} diff --git a/test/tools/modules/incubator/DefaultImage.java b/test/tools/modules/incubator/DefaultImage.java new file mode 100644 --- /dev/null +++ b/test/tools/modules/incubator/DefaultImage.java @@ -0,0 +1,143 @@ +/* + * 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. + * + * 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 + * @bug 8170859 + * @summary Ensure no incubator modules are resolved by default in the image + * @library /lib/testlibrary + * @modules jdk.compiler + * @build CompilerUtils + * @run testng DefaultImage + */ + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static jdk.testlibrary.ProcessTools.executeCommand; +import static org.testng.Assert.*; + +public class DefaultImage { + private static final String JAVA_HOME = System.getProperty("java.home"); + private static final Path TEST_SRC = Paths.get(System.getProperty("test.src")); + private static final Path CP_DIR = Paths.get("cp"); + + @BeforeTest + private void setup() throws Throwable { + Path src = TEST_SRC.resolve("src").resolve("cp").resolve("listmods"); + assertTrue(CompilerUtils.compile(src, CP_DIR)); + } + + public void test() throws Throwable { + java("-cp", CP_DIR.toString(), + "listmods.ListModules") + .assertSuccess() + .resultChecker(r -> r.assertOutputContains("java.base")) + .resultChecker(r -> r.assertOutputDoesNotContain("jdk.incubator")); + } + + @DataProvider(name = "tokens") + public Object[][] singleModuleValues() throws IOException { + return new Object[][]{ { "ALL-DEFAULT" }, { "ALL-SYSTEM"} }; + } + + @Test(dataProvider = "tokens") + public void testAddMods(String addModsToken) throws Throwable { + java("--add-modules", addModsToken, + "-cp", CP_DIR.toString(), + "listmods.ListModules") + .assertSuccess() + .resultChecker(r -> r.assertOutputContains("java.base")) + .resultChecker(r -> r.assertOutputDoesNotContain("jdk.incubator")); + } + + static ToolResult java(String... opts) throws Throwable { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos); + String[] options = Stream.concat(Stream.of(getJava()), Stream.of(opts)) + .toArray(String[]::new); + + ProcessBuilder pb = new ProcessBuilder(options); + int exitValue = executeCommand(pb).outputTo(ps) + .errorTo(ps) + .getExitValue(); + + return new ToolResult(exitValue, new String(baos.toByteArray(), UTF_8)); + } + + static class ToolResult { + final int exitCode; + final String output; + + ToolResult(int exitValue, String output) { + this.exitCode = exitValue; + this.output = output; + } + + ToolResult assertSuccess() { + assertEquals(exitCode, 0, + "Expected exit code 0, got " + exitCode + + ", with output[" + output + "]"); + return this; + } + + ToolResult resultChecker(Consumer r) { + r.accept(this); + return this; + } + + ToolResult assertOutputContains(String subString) { + assertTrue(output.contains(subString), + "Expected to find [" + subString + "], in output [" + + output + "]" + "\n"); + return this; + } + + ToolResult assertOutputDoesNotContain(String subString) { + assertFalse(output.contains(subString), + "Expected to NOT find [" + subString + "], in output [" + + output + "]" + "\n"); + return this; + } + } + + static String getJava() { + Path image = Paths.get(JAVA_HOME); + boolean isWindows = System.getProperty("os.name").startsWith("Windows"); + Path java = image.resolve("bin").resolve(isWindows ? "java.exe" : "java"); + if (Files.notExists(java)) + throw new RuntimeException(java + " not found"); + return java.toAbsolutePath().toString(); + } +} diff --git a/test/tools/modules/incubator/IncubatorModules.java b/test/tools/modules/incubator/IncubatorModules.java new file mode 100644 --- /dev/null +++ b/test/tools/modules/incubator/IncubatorModules.java @@ -0,0 +1,351 @@ +/* + * 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. + * + * 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 + * @bug 8170859 + * @summary Basic test for incubator modules in jmods and images + * @library /lib/testlibrary + * @modules jdk.compiler + * @build CompilerUtils + * @run testng/othervm --add-modules=jdk.jlink IncubatorModules + */ + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.spi.ToolProvider; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import jdk.testlibrary.FileUtils; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static jdk.testlibrary.ProcessTools.executeCommand; +import static org.testng.Assert.*; + +public class IncubatorModules { + private static final String JAVA_HOME = System.getProperty("java.home"); + private static final Path JDK_JMODS = Paths.get(JAVA_HOME, "jmods"); + + private static final Path TEST_SRC = Paths.get(System.getProperty("test.src")); + private static final Path MODS_DIR = Paths.get("mods"); + private static final Path CP_DIR = Paths.get("cp"); + private static final Path JARS_DIR = Paths.get("jars"); + private static final Path JMODS_DIR = Paths.get("jmods"); + private static final Path IMAGE = Paths.get("image"); + + private static final String JAVA_BASE = "java.base"; + private final String[] modules = new String[] { "message.writer", + "message.converter" }; + + @BeforeTest + private void setup() throws Throwable { + Path src = TEST_SRC.resolve("src"); + for (String name : modules) { + assertTrue(CompilerUtils.compile(src.resolve(name), + MODS_DIR, + "--module-source-path", src.toString())); + } + + assertTrue(CompilerUtils.compile(src.resolve("cp"), + CP_DIR, + "--module-path", MODS_DIR.toString(), + "--add-modules", "message.writer")); + } + + @DataProvider(name = "singleModule") + public Object[][] singleModuleValues() throws IOException { + Object[][] values = new Object[][]{ + // { Extra args to the build the message.converter jmod + // Tokens to pass to the run time --add-modules option + // SUCCESS or FAILURE expected + // Messages expected in the run time output + // Messages that must not appear in the run time output }, + { "", + List.of("ALL-DEFAULT", "ALL-SYSTEM"), + ToolResult.ASSERT_SUCCESS, + List.of("hello world", "message.converter", "java.base"), + List.of("WARNING") }, + { "--do-not-resolve-by-default", + List.of("ALL-DEFAULT", "ALL-SYSTEM"), + ToolResult.ASSERT_FAILURE, + List.of("java.base", "java.lang.ClassNotFoundException: converter.MessageConverter"), + List.of("WARNING", "message.converter") }, + { "--warn-if-resolved=incubating", + List.of("ALL-DEFAULT", "ALL-SYSTEM"), + ToolResult.ASSERT_SUCCESS, + List.of("hello world", "message.converter", "java.base", + "WARNING: using incubating module(s): message.converter"), + List.of() }, + { "--do-not-resolve-by-default --warn-if-resolved=incubating", + List.of("ALL-DEFAULT", "ALL-SYSTEM"), + ToolResult.ASSERT_FAILURE, + List.of("java.base", "java.lang.ClassNotFoundException: converter.MessageConverter"), + List.of("WARNING", "message.converter") }, + { "--do-not-resolve-by-default --warn-if-resolved=incubating", + List.of("message.converter"), + ToolResult.ASSERT_SUCCESS, + List.of("hello world", "message.converter", "java.base", "WARNING"), + List.of() } + }; + return values; + } + + @Test(dataProvider = "singleModule") + public void singleModule(String extraJmodArg, + List addModsTokens, + Consumer assertExitCode, + List expectedOutput, + List unexpectedOutput) + throws Throwable + { + if (Files.notExists(JDK_JMODS)) { + System.out.println("JDK jmods not found test cannot run."); + return; + } + + FileUtils.deleteFileTreeUnchecked(JMODS_DIR); + FileUtils.deleteFileTreeUnchecked(IMAGE); + Files.createDirectories(JMODS_DIR); + Path converterJmod = JMODS_DIR.resolve("converter.jmod"); + + jmod("create", + "--class-path", MODS_DIR.resolve("message.converter").toString(), + extraJmodArg, + converterJmod.toString()) + .assertSuccess(); + + String mpath = JDK_JMODS.toString() + File.pathSeparator + JMODS_DIR.toString(); + jlink("--module-path", mpath, + "--add-modules", JAVA_BASE + ",message.converter", + "--output", IMAGE.toString()) + .assertSuccess(); + + for (String addModsToken : addModsTokens) { + java(IMAGE, + "--add-modules", addModsToken, + "-cp", CP_DIR.toString(), + "test.ConvertToLowerCase", "HEllo WoRlD") + .resultChecker(assertExitCode) + .resultChecker(r -> { + expectedOutput.forEach(e -> r.assertContains(e)); + unexpectedOutput.forEach(e -> r.assertDoesNotContains(e)); + }); + } + } + + @DataProvider(name = "twoModules") + public Object[][] twoModulesValues() throws IOException { + Object[][] values = new Object[][]{ + // { Extra args to the build the message.writer jmod + // Extra args to the build the message.converter jmod + // Tokens to pass to the run time --add-modules option + // SUCCESS or FAILURE expected + // Messages expected in the run time output + // Messages that must not appear in the run time output }, + { "", + "", + List.of("ALL-DEFAULT", "ALL-SYSTEM"), + ToolResult.ASSERT_SUCCESS, + List.of("HELLO CHEGAR !!!", "message.writer", "message.converter", "java.base"), + List.of() }, + { "", + "--do-not-resolve-by-default", + List.of("ALL-DEFAULT", "ALL-SYSTEM"), + ToolResult.ASSERT_SUCCESS, + List.of("HELLO CHEGAR !!!", "message.writer", "message.converter", "java.base"), + List.of() }, + { "--do-not-resolve-by-default", + "", + List.of("ALL-DEFAULT", "ALL-SYSTEM"), + ToolResult.ASSERT_FAILURE, + List.of("java.lang.ClassNotFoundException: writer.MessageWriter", "java.base"), + List.of("message.writer") }, + { "--do-not-resolve-by-default", + "--do-not-resolve-by-default", + List.of("ALL-DEFAULT", "ALL-SYSTEM"), + ToolResult.ASSERT_FAILURE, + List.of("java.lang.ClassNotFoundException: writer.MessageWriter", "java.base"), + List.of("message.converter", "message.writer") }, + // now add in warnings + { "--do-not-resolve-by-default --warn-if-resolved=incubating", + "", + List.of("message.writer"), + ToolResult.ASSERT_SUCCESS, + List.of("HELLO CHEGAR !!!", "message.writer", "message.converter", "java.base", + "WARNING: using incubating module(s): message.writer"), + List.of() }, + { "", + "--do-not-resolve-by-default --warn-if-resolved=incubating", + List.of("message.writer"), + ToolResult.ASSERT_SUCCESS, + List.of("HELLO CHEGAR !!!", "message.writer", "message.converter", "java.base", + "WARNING: using incubating module(s): message.converter"), + List.of() } }; + return values; + } + + @Test(dataProvider = "twoModules") + public void doNotResolveByDefaultTwoModules(String extraFirstJmodArg, + String extraSecondJmodArg, + List addModsTokens, + Consumer assertExitCode, + List expectedOutput, + List unexpectedOutput) + throws Throwable + { + if (Files.notExists(JDK_JMODS)) { + System.out.println("JDK jmods not found test cannot run."); + return; + } + + FileUtils.deleteFileTreeUnchecked(JMODS_DIR); + FileUtils.deleteFileTreeUnchecked(IMAGE); + Files.createDirectories(JMODS_DIR); + Path writerJmod = JMODS_DIR.resolve("writer.jmod"); + Path converterJmod = JMODS_DIR.resolve("converter.jmod"); + + jmod("create", + extraFirstJmodArg, + "--class-path", MODS_DIR.resolve("message.writer").toString(), + writerJmod.toString()); + + jmod("create", + "--class-path", MODS_DIR.resolve("message.converter").toString(), + extraSecondJmodArg, + converterJmod.toString()) + .assertSuccess(); + + String mpath = JDK_JMODS.toString() + File.pathSeparator + JMODS_DIR.toString(); + jlink("--module-path", mpath, + "--add-modules", JAVA_BASE + ",message.writer,message.converter", + "--output", IMAGE.toString()) + .assertSuccess(); + + for (String addModsToken : addModsTokens) { + java(IMAGE, + "--add-modules", addModsToken, + "-cp", CP_DIR.toString(), + "test.WriteUpperCase", "hello chegar !!!") + .resultChecker(assertExitCode) + .resultChecker(r -> { + expectedOutput.forEach(e -> r.assertContains(e)); + unexpectedOutput.forEach(e -> r.assertDoesNotContains(e)); + }); + } + } + + static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod") + .orElseThrow(() -> new RuntimeException("jmod tool not found")); + static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar") + .orElseThrow(() -> new RuntimeException("jar tool not found")); + static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink") + .orElseThrow(() -> new RuntimeException("jlink tool not found")); + + static ToolResult jmod(String... args) { return execTool(JMOD_TOOL, args); } + + static ToolResult jar(String... args) { return execTool(JAR_TOOL, args); } + + static ToolResult jlink(String... args) { return execTool(JLINK_TOOL, args); } + + static ToolResult java(Path image, String... opts) throws Throwable { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos); + String[] options = Stream.concat(Stream.of(getJava(image)), Stream.of(opts)) + .toArray(String[]::new); + + ProcessBuilder pb = new ProcessBuilder(options); + int exitValue = executeCommand(pb).outputTo(ps) + .errorTo(ps) + .getExitValue(); + + return new ToolResult(exitValue, new String(baos.toByteArray(), UTF_8)); + } + + static ToolResult execTool(ToolProvider tool, String... args) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos); + List filteredArgs = Stream.of(args) + .map(s -> s.split(" ")).flatMap(Stream::of) + .filter(s -> !s.equals("")) + .collect(Collectors.toList()); + System.out.println(tool + " " + filteredArgs); + int ec = tool.run(ps, ps, filteredArgs.toArray(new String[] {})); + return new ToolResult(ec, new String(baos.toByteArray(), UTF_8)); + } + + static class ToolResult { + final int exitCode; + final String output; + + ToolResult(int exitValue, String output) { + this.exitCode = exitValue; + this.output = output; + } + + static Consumer ASSERT_SUCCESS = r -> + assertEquals(r.exitCode, 0, + "Expected exit code 0, got " + r.exitCode + + ", with output[" + r.output + "]"); + static Consumer ASSERT_FAILURE = r -> + assertNotEquals(r.exitCode, 0, + "Expected exit code != 0, got " + r.exitCode + + ", with output[" + r.output + "]"); + + ToolResult assertSuccess() { ASSERT_SUCCESS.accept(this); return this; } + ToolResult assertFailure() { ASSERT_FAILURE.accept(this); return this; } + ToolResult resultChecker(Consumer r) { r.accept(this); return this; } + + ToolResult assertContains(String subString) { + assertTrue(output.contains(subString), + "Expected to find [" + subString + "], in output [" + + output + "]" + "\n"); + return this; + } + ToolResult assertDoesNotContains(String subString) { + assertFalse(output.contains(subString), + "Expected to NOT find [" + subString + "], in output [" + + output + "]" + "\n"); + return this; + } + } + + static String getJava(Path image) { + boolean isWindows = System.getProperty("os.name").startsWith("Windows"); + Path java = image.resolve("bin").resolve(isWindows ? "java.exe" : "java"); + if (Files.notExists(java)) + throw new RuntimeException(java + " not found"); + return java.toAbsolutePath().toString(); + } +} diff --git a/test/tools/modules/incubator/src/cp/listmods/ListModules.java b/test/tools/modules/incubator/src/cp/listmods/ListModules.java new file mode 100644 --- /dev/null +++ b/test/tools/modules/incubator/src/cp/listmods/ListModules.java @@ -0,0 +1,38 @@ +/* + * 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. + * + * 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 listmods; + +import java.lang.reflect.Layer; +import java.lang.reflect.Module; +import java.util.stream.Collectors; + +public class ListModules { + public static void main(String[] args) throws Exception { + System.out.println(Layer.boot() + .modules().stream() + .map(Module::getName) + .sorted() + .collect(Collectors.joining("\n"))); + } +} diff --git a/test/tools/modules/incubator/src/cp/test/ConvertToLowerCase.java b/test/tools/modules/incubator/src/cp/test/ConvertToLowerCase.java new file mode 100644 --- /dev/null +++ b/test/tools/modules/incubator/src/cp/test/ConvertToLowerCase.java @@ -0,0 +1,40 @@ +/* + * 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. + * + * 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.reflect.Layer; +import java.lang.reflect.Module; +import java.util.stream.Collectors; + +public class ConvertToLowerCase { + public static void main(String[] args) { + System.out.println(Layer.boot() + .modules() + .stream() + .map(Module::getName) + .sorted() + .collect(Collectors.joining("\n"))); + System.out.println(converter.MessageConverter.toLowerCase(args[0])); + } +} diff --git a/test/tools/modules/incubator/src/cp/test/WriteUpperCase.java b/test/tools/modules/incubator/src/cp/test/WriteUpperCase.java new file mode 100644 --- /dev/null +++ b/test/tools/modules/incubator/src/cp/test/WriteUpperCase.java @@ -0,0 +1,39 @@ +/* + * 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. + * + * 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.reflect.Layer; +import java.lang.reflect.Module; +import java.util.stream.Collectors; + +public class WriteUpperCase { + public static void main(String[] args) { + System.out.println(Layer.boot() + .modules().stream() + .map(Module::getName) + .sorted() + .collect(Collectors.joining("\n"))); + writer.MessageWriter.writeOn(args[0], System.out); + } +} diff --git a/test/tools/modules/incubator/src/message.converter/converter/MessageConverter.java b/test/tools/modules/incubator/src/message.converter/converter/MessageConverter.java new file mode 100644 --- /dev/null +++ b/test/tools/modules/incubator/src/message.converter/converter/MessageConverter.java @@ -0,0 +1,34 @@ +/* + * 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. + * + * 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 converter; + +public class MessageConverter { + public static String toUpperCase(String message) { + return message.toUpperCase(java.util.Locale.ROOT); + } + + public static String toLowerCase(String message) { + return message.toLowerCase(java.util.Locale.ROOT); + } +} diff --git a/test/tools/modules/incubator/src/message.converter/module-info.java b/test/tools/modules/incubator/src/message.converter/module-info.java new file mode 100644 --- /dev/null +++ b/test/tools/modules/incubator/src/message.converter/module-info.java @@ -0,0 +1,26 @@ +/* + * 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. + * + * 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 message.converter { + exports converter; +} diff --git a/test/tools/modules/incubator/src/message.writer/module-info.java b/test/tools/modules/incubator/src/message.writer/module-info.java new file mode 100644 --- /dev/null +++ b/test/tools/modules/incubator/src/message.writer/module-info.java @@ -0,0 +1,27 @@ +/* + * 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. + * + * 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 message.writer { + requires message.converter; + exports writer; +} diff --git a/test/tools/modules/incubator/src/message.writer/writer/MessageWriter.java b/test/tools/modules/incubator/src/message.writer/writer/MessageWriter.java new file mode 100644 --- /dev/null +++ b/test/tools/modules/incubator/src/message.writer/writer/MessageWriter.java @@ -0,0 +1,38 @@ +/* + * 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. + * + * 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 writer; + +import java.io.PrintStream; +import java.util.Locale; + +public class MessageWriter { + public static void writeOn(String message, PrintStream out) { + String newMesssage = converter.MessageConverter.toUpperCase(message); + if (!newMesssage.equals(message.toUpperCase(Locale.ROOT))) + throw new RuntimeException("Expected " + message.toUpperCase(Locale.ROOT) + + ", got " + newMesssage ); + + out.println(newMesssage); + } +}