undefect. CWE-407 — 92 sites, 42 ecosystems

B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections.
Squash of 94 local commits onto remote master.
This commit is contained in:
russell@unturf.com 2026-03-26 19:48:18 -04:00
parent 0a580b313d
commit db29a08762
1311 changed files with 371202 additions and 1188 deletions

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2025, 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;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_PARAMETER;
/**
* Indicates that the annotated parameter or type parameter is not expected to be a
* Value Based class.
* Using a parameter or type parameter of a <a href="../lang/doc-files/ValueBased.html">value-based classes</a>
* should produce warnings about behavior that is inconsistent with identity based semantics.
*
* Note this internal annotation is handled specially by the javac compiler.
* To work properly with {@code --release older-release}, it requires special
* handling in {@code make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java}
* and {@code src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java}.
*
* @since 25
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value={PARAMETER, TYPE_PARAMETER})
public @interface RequiresIdentity {
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2020, 2025, 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;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
/**
* Indicates the API declaration in question is associated with a Value Based class.
* References to <a href="../lang/doc-files/ValueBased.html">value-based classes</a>
* should produce warnings about behavior that is inconsistent with value based semantics.
*
* Note this internal annotation is handled specially by the javac compiler.
* To work properly with {@code --release older-release}, it requires special
* handling in {@code make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java}
* and {@code src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java}.
*
* @since 16
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value={TYPE})
public @interface ValueBased {
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2020, 2025, 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;
import jdk.internal.misc.CDS;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
/**
* Used by ModuleBootstrap for archiving the boot layer.
*/
@AOTSafeClassInitializer
class ArchivedBootLayer {
private static ArchivedBootLayer archivedBootLayer;
private final ModuleLayer bootLayer;
private ArchivedBootLayer(ModuleLayer bootLayer) {
this.bootLayer = bootLayer;
}
ModuleLayer bootLayer() {
return bootLayer;
}
static ArchivedBootLayer get() {
return archivedBootLayer;
}
static void archive(ModuleLayer layer) {
archivedBootLayer = new ArchivedBootLayer(layer);
}
static {
CDS.initializeFromArchive(ArchivedBootLayer.class);
}
}

View file

@ -0,0 +1,134 @@
/*
* Copyright (c) 2018, 2025, 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;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import jdk.internal.misc.CDS;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
/**
* Used by ModuleBootstrap for archiving the configuration for the boot layer,
* and the system module finder.
*/
@AOTSafeClassInitializer
class ArchivedModuleGraph {
private static ArchivedModuleGraph archivedModuleGraph;
private final boolean hasSplitPackages;
private final boolean hasIncubatorModules;
private final ModuleFinder finder;
private final Configuration configuration;
private final Function<String, ClassLoader> classLoaderFunction;
private final String mainModule;
private final Set<String> addModules;
private ArchivedModuleGraph(boolean hasSplitPackages,
boolean hasIncubatorModules,
ModuleFinder finder,
Configuration configuration,
Function<String, ClassLoader> classLoaderFunction,
String mainModule,
Set<String> addModules) {
this.hasSplitPackages = hasSplitPackages;
this.hasIncubatorModules = hasIncubatorModules;
this.finder = finder;
this.configuration = configuration;
this.classLoaderFunction = classLoaderFunction;
this.mainModule = mainModule;
this.addModules = addModules;
}
ModuleFinder finder() {
return finder;
}
Configuration configuration() {
return configuration;
}
Function<String, ClassLoader> classLoaderFunction() {
return classLoaderFunction;
}
boolean hasSplitPackages() {
return hasSplitPackages;
}
boolean hasIncubatorModules() {
return hasIncubatorModules;
}
static boolean sameAddModules(Set<String> addModules) {
if (archivedModuleGraph.addModules == null || addModules == null) {
return false;
}
if (archivedModuleGraph.addModules.size() != addModules.size()) {
return false;
}
return archivedModuleGraph.addModules.containsAll(addModules);
}
/**
* Returns the ArchivedModuleGraph for the given initial module.
*/
static ArchivedModuleGraph get(String mainModule, Set<String> addModules) {
ArchivedModuleGraph graph = archivedModuleGraph;
if ((graph != null) && Objects.equals(graph.mainModule, mainModule) && sameAddModules(addModules)) {
return graph;
} else {
return null;
}
}
/**
* Archive the module graph for the given initial module.
*/
static void archive(boolean hasSplitPackages,
boolean hasIncubatorModules,
ModuleFinder finder,
Configuration configuration,
Function<String, ClassLoader> classLoaderFunction,
String mainModule,
Set<String> addModules) {
archivedModuleGraph = new ArchivedModuleGraph(hasSplitPackages,
hasIncubatorModules,
finder,
configuration,
classLoaderFunction,
mainModule,
addModules);
}
static {
// Legacy CDS archive support (to be deprecated)
CDS.initializeFromArchive(ArchivedModuleGraph.class);
}
}

View file

@ -0,0 +1,282 @@
/*
* Copyright (c) 2015, 2018, 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;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Exports;
import java.lang.module.ModuleDescriptor.Opens;
import java.lang.module.ModuleDescriptor.Provides;
import java.lang.module.ModuleDescriptor.Requires;
import java.lang.module.ModuleDescriptor.Version;
import java.util.List;
import java.util.Set;
import jdk.internal.access.JavaLangModuleAccess;
import jdk.internal.access.SharedSecrets;
/**
* This builder is optimized for reconstituting the {@code ModuleDescriptor}s
* for system modules. The validation should be done at jlink time.
*
* 1. skip name validation
* 2. ignores dependency hashes.
* 3. ModuleDescriptor skips the defensive copy and directly uses the
* sets/maps created in this Builder.
*
* SystemModules should contain modules for the boot layer.
*/
final class Builder {
private static final JavaLangModuleAccess JLMA =
SharedSecrets.getJavaLangModuleAccess();
// Static cache of the most recently seen Version to cheaply deduplicate
// most Version objects. JDK modules have the same version.
static Version cachedVersion;
/**
* Returns a {@link Requires} for a dependence on a module with the given
* (and possibly empty) set of modifiers, and optionally the version
* recorded at compile time.
*/
public static Requires newRequires(Set<Requires.Modifier> mods,
String mn,
String compiledVersion)
{
Version version = null;
if (compiledVersion != null) {
// use the cached version if the same version string
Version ver = cachedVersion;
if (ver != null && compiledVersion.equals(ver.toString())) {
version = ver;
} else {
version = Version.parse(compiledVersion);
}
}
return JLMA.newRequires(mods, mn, version);
}
/**
* Returns a {@link Requires} for a dependence on a module with the given
* (and possibly empty) set of modifiers, and optionally the version
* recorded at compile time.
*/
public static Requires newRequires(Set<Requires.Modifier> mods,
String mn)
{
return newRequires(mods, mn, null);
}
/**
* Returns a {@link Exports} for a qualified export, with
* the given (and possibly empty) set of modifiers,
* to a set of target modules.
*/
public static Exports newExports(Set<Exports.Modifier> ms,
String pn,
Set<String> targets) {
return JLMA.newExports(ms, pn, targets);
}
/**
* Returns an {@link Opens} for an unqualified open with a given set of
* modifiers.
*/
public static Opens newOpens(Set<Opens.Modifier> ms, String pn) {
return JLMA.newOpens(ms, pn);
}
/**
* Returns an {@link Opens} for a qualified opens, with
* the given (and possibly empty) set of modifiers,
* to a set of target modules.
*/
public static Opens newOpens(Set<Opens.Modifier> ms,
String pn,
Set<String> targets) {
return JLMA.newOpens(ms, pn, targets);
}
/**
* Returns a {@link Exports} for an unqualified export with a given set
* of modifiers.
*/
public static Exports newExports(Set<Exports.Modifier> ms, String pn) {
return JLMA.newExports(ms, pn);
}
/**
* Returns a {@link Provides} for a service with a given list of
* implementation classes.
*/
public static Provides newProvides(String st, List<String> pcs) {
return JLMA.newProvides(st, pcs);
}
final String name;
boolean open, synthetic, mandated;
Set<Requires> requires;
Set<Exports> exports;
Set<Opens> opens;
Set<String> packages;
Set<String> uses;
Set<Provides> provides;
Version version;
String mainClass;
Builder(String name) {
this.name = name;
this.requires = Set.of();
this.exports = Set.of();
this.opens = Set.of();
this.provides = Set.of();
this.uses = Set.of();
}
Builder open(boolean value) {
this.open = value;
return this;
}
Builder synthetic(boolean value) {
this.synthetic = value;
return this;
}
Builder mandated(boolean value) {
this.mandated = value;
return this;
}
/**
* Sets module exports.
*/
public Builder exports(Exports[] exports) {
this.exports = Set.of(exports);
return this;
}
/**
* Sets module opens.
*/
public Builder opens(Opens[] opens) {
this.opens = Set.of(opens);
return this;
}
/**
* Sets module requires.
*/
public Builder requires(Requires[] requires) {
this.requires = Set.of(requires);
return this;
}
/**
* Adds a set of (possible empty) packages.
*/
public Builder packages(Set<String> packages) {
this.packages = packages;
return this;
}
/**
* Sets the set of service dependences.
*/
public Builder uses(Set<String> uses) {
this.uses = uses;
return this;
}
/**
* Sets module provides.
*/
public Builder provides(Provides[] provides) {
this.provides = Set.of(provides);
return this;
}
/**
* Sets the module version.
*
* @throws IllegalArgumentException if {@code v} is null or cannot be
* parsed as a version string
*
* @see Version#parse(String)
*/
public Builder version(String v) {
Version ver = cachedVersion;
if (ver != null && v.equals(ver.toString())) {
version = ver;
} else {
cachedVersion = version = Version.parse(v);
}
return this;
}
/**
* Sets the module main class.
*/
public Builder mainClass(String mc) {
mainClass = mc;
return this;
}
/**
* Returns an immutable set of the module modifiers derived from the flags.
*/
private Set<ModuleDescriptor.Modifier> modifiers() {
int n = 0;
if (open) n++;
if (synthetic) n++;
if (mandated) n++;
if (n == 0) {
return Set.of();
} else {
ModuleDescriptor.Modifier[] mods = new ModuleDescriptor.Modifier[n];
if (open) mods[--n] = ModuleDescriptor.Modifier.OPEN;
if (synthetic) mods[--n] = ModuleDescriptor.Modifier.SYNTHETIC;
if (mandated) mods[--n] = ModuleDescriptor.Modifier.MANDATED;
return Set.of(mods);
}
}
/**
* Builds a {@code ModuleDescriptor} from the components.
*/
public ModuleDescriptor build(int hashCode) {
assert name != null;
return JLMA.newModuleDescriptor(name,
version,
modifiers(),
requires,
exports,
opens,
uses,
provides,
packages,
mainClass,
hashCode);
}
}

View file

@ -0,0 +1,247 @@
/*
* Copyright (c) 2009, 2021, 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;
import java.util.Set;
/**
* Utility class for checking module, package, and class names.
*/
public final class Checks {
private Checks() { }
/**
* Checks a name to ensure that it's a legal module name.
*
* @throws IllegalArgumentException if name is null or not a legal
* module name
*/
public static String requireModuleName(String name) {
if (name == null)
throw new IllegalArgumentException("Null module name");
int next;
int off = 0;
while ((next = name.indexOf('.', off)) != -1) {
String id = name.substring(off, next);
if (!isJavaIdentifier(id)) {
throw new IllegalArgumentException(name + ": Invalid module name"
+ ": '" + id + "' is not a Java identifier");
}
off = next+1;
}
String last = name.substring(off);
if (!isJavaIdentifier(last)) {
throw new IllegalArgumentException(name + ": Invalid module name"
+ ": '" + last + "' is not a Java identifier");
}
return name;
}
/**
* Checks a name to ensure that it's a legal package name.
*
* @throws IllegalArgumentException if name is null or not a legal
* package name
*/
public static String requirePackageName(String name) {
return requireTypeName("package name", name);
}
/**
* Returns {@code true} if the given name is a legal package name.
*/
public static boolean isPackageName(String name) {
return isTypeName(name);
}
/**
* Checks a name to ensure that it's a legal qualified class name
*
* @throws IllegalArgumentException if name is null or not a legal
* qualified class name
*/
public static String requireServiceTypeName(String name) {
return requireQualifiedClassName("service type name", name);
}
/**
* Checks a name to ensure that it's a legal qualified class name.
*
* @throws IllegalArgumentException if name is null or not a legal
* qualified class name
*/
public static String requireServiceProviderName(String name) {
return requireQualifiedClassName("service provider name", name);
}
/**
* Checks a name to ensure that it's a legal qualified class name in
* a named package.
*
* @throws IllegalArgumentException if name is null or not a legal
* qualified class name in a named package
*/
public static String requireQualifiedClassName(String what, String name) {
requireTypeName(what, name);
if (name.indexOf('.') == -1)
throw new IllegalArgumentException(name + ": is not a qualified name of"
+ " a Java class in a named package");
return name;
}
/**
* Returns {@code true} if the given name is a legal class name.
*/
public static boolean isClassName(String name) {
return isTypeName(name);
}
/**
* Returns {@code true} if the given name is a legal type name.
*/
private static boolean isTypeName(String name) {
int next;
int off = 0;
while ((next = name.indexOf('.', off)) != -1) {
String id = name.substring(off, next);
if (!isJavaIdentifier(id))
return false;
off = next+1;
}
String last = name.substring(off);
return isJavaIdentifier(last);
}
/**
* Checks if the given name is a legal type name.
*
* @throws IllegalArgumentException if name is null or not a legal
* type name
*/
private static String requireTypeName(String what, String name) {
if (name == null)
throw new IllegalArgumentException("Null " + what);
int next;
int off = 0;
while ((next = name.indexOf('.', off)) != -1) {
String id = name.substring(off, next);
if (!isJavaIdentifier(id)) {
throw new IllegalArgumentException(name + ": Invalid " + what
+ ": '" + id + "' is not a Java identifier");
}
off = next + 1;
}
String last = name.substring(off);
if (!isJavaIdentifier(last)) {
throw new IllegalArgumentException(name + ": Invalid " + what
+ ": '" + last + "' is not a Java identifier");
}
return name;
}
/**
* Returns true if the given string is a legal Java identifier,
* otherwise false.
*/
public static boolean isJavaIdentifier(String str) {
if (str.isEmpty() || RESERVED.contains(str))
return false;
int first = Character.codePointAt(str, 0);
if (!Character.isJavaIdentifierStart(first))
return false;
int i = Character.charCount(first);
while (i < str.length()) {
int cp = Character.codePointAt(str, i);
if (!Character.isJavaIdentifierPart(cp))
return false;
i += Character.charCount(cp);
}
return true;
}
// keywords, boolean and null literals, not allowed in identifiers
private static final Set<String> RESERVED = Set.of(
"abstract",
"assert",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum",
"extends",
"final",
"finally",
"float",
"for",
"goto",
"if",
"implements",
"import",
"instanceof",
"int",
"interface",
"long",
"native",
"new",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"strictfp",
"super",
"switch",
"synchronized",
"this",
"throw",
"throws",
"transient",
"try",
"void",
"volatile",
"while",
"true",
"false",
"null",
"_"
);
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2015, 2017, 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;
// Constants in module-info.class files
public class ClassFileConstants {
private ClassFileConstants() { }
// Attribute names
public static final String MODULE = "Module";
public static final String SOURCE_FILE = "SourceFile";
public static final String SDE = "SourceDebugExtension";
public static final String MODULE_PACKAGES = "ModulePackages";
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;
public static final int ACC_OPEN = 0x0020;
public static final int ACC_TRANSITIVE = 0x0020;
public static final int ACC_STATIC_PHASE = 0x0040;
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;
}

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2017, 2018, 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;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Defines methods to compute the default set of root modules for the unnamed
* module.
*/
public final class DefaultRoots {
private DefaultRoots() { }
/**
* Returns the default set of root modules for the unnamed module from the
* modules observable with the intersection of two module finders.
*
* The first module finder should be the module finder that finds modules on
* the upgrade module path or among the system modules. The second module
* finder should be the module finder that finds all modules on the module
* path, or a subset of when using --limit-modules.
*/
static Set<String> compute(ModuleFinder finder1, ModuleFinder finder2) {
return finder1.findAll().stream()
.filter(mref -> !ModuleResolution.doNotResolveByDefault(mref))
.map(ModuleReference::descriptor)
.filter(descriptor -> finder2.find(descriptor.name()).isPresent()
&& exportsAPI(descriptor))
.map(ModuleDescriptor::name)
.collect(Collectors.toSet());
}
/**
* Returns the default set of root modules for the unnamed module from the
* modules observable with the given module finder.
*
* This method is used by the jlink system modules plugin.
*/
public static Set<String> compute(ModuleFinder finder) {
return compute(finder, finder);
}
/**
* Returns true if the given module exports a package to all modules
*/
private static boolean exportsAPI(ModuleDescriptor descriptor) {
return descriptor.exports()
.stream()
.filter(e -> !e.isQualified())
.findAny()
.isPresent();
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2017, 2021, 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;
import java.lang.module.ModuleDescriptor;
import java.util.Map;
import java.util.Set;
/**
* A dummy SystemModules for use with exploded builds or testing.
*/
class ExplodedSystemModules implements SystemModules {
@Override
public boolean hasSplitPackages() {
return true; // not known
}
@Override
public boolean hasIncubatorModules() {
return true; // not known
}
@Override
public ModuleDescriptor[] moduleDescriptors() {
throw new InternalError();
}
@Override
public ModuleTarget[] moduleTargets() {
throw new InternalError();
}
@Override
public ModuleHashes[] moduleHashes() {
throw new InternalError();
}
@Override
public ModuleResolution[] moduleResolutions() {
throw new InternalError();
}
@Override
public Map<String, Set<String>> moduleReads() {
throw new InternalError();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,254 @@
/*
* Copyright (c) 2015, 2020, 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;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Supplier;
import java.util.stream.Stream;
/**
* The result of hashing the contents of a number of module artifacts.
*/
public final class ModuleHashes {
/**
* A supplier of a message digest.
*/
public static interface HashSupplier {
byte[] generate(String algorithm);
}
private final String algorithm;
private final Map<String, byte[]> nameToHash;
/**
* Creates a {@code ModuleHashes}.
*
* @param algorithm the algorithm used to create the hashes
* @param nameToHash the map of module name to hash value
*/
ModuleHashes(String algorithm, Map<String, byte[]> nameToHash) {
this.algorithm = Objects.requireNonNull(algorithm);
this.nameToHash = Collections.unmodifiableMap(nameToHash);
}
/**
* Returns the algorithm used to hash the modules ("SHA-256" for example).
*/
public String algorithm() {
return algorithm;
}
/**
* Returns the set of module names for which hashes are recorded.
*/
public Set<String> names() {
return nameToHash.keySet();
}
/**
* Returns the hash for the given module name, {@code null}
* if there is no hash recorded for the module.
*/
public byte[] hashFor(String mn) {
return nameToHash.get(mn);
}
/**
* Returns unmodifiable map of module name to hash
*/
public Map<String, byte[]> hashes() {
return nameToHash;
}
/**
* Computes a hash from the names and content of a module.
*
* @param reader the module reader to access the module content
* @param algorithm the name of the message digest algorithm to use
* @return the hash
* @throws IllegalArgumentException if digest algorithm is not supported
* @throws UncheckedIOException if an I/O error occurs
*/
private static byte[] computeHash(ModuleReader reader, String algorithm) {
MessageDigest md;
try {
md = MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
byte[] buf = new byte[32*1024];
try (Stream<String> stream = reader.list()) {
stream.sorted().forEach(rn -> {
md.update(rn.getBytes(StandardCharsets.UTF_8));
try (InputStream in = reader.open(rn).orElseThrow()) {
int n;
while ((n = in.read(buf)) > 0) {
md.update(buf, 0, n);
}
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
});
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
return md.digest();
}
/**
* Computes a hash from the names and content of a module.
*
* @param supplier supplies the module reader to access the module content
* @param algorithm the name of the message digest algorithm to use
* @return the hash
* @throws IllegalArgumentException if digest algorithm is not supported
* @throws UncheckedIOException if an I/O error occurs
*/
static byte[] computeHash(Supplier<ModuleReader> supplier, String algorithm) {
try (ModuleReader reader = supplier.get()) {
return computeHash(reader, algorithm);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
/**
* Computes the hash from the names and content of a set of modules. Returns
* a {@code ModuleHashes} to encapsulate the result.
*
* @param mrefs the set of modules
* @param algorithm the name of the message digest algorithm to use
* @return ModuleHashes that encapsulates the hashes
* @throws IllegalArgumentException if digest algorithm is not supported
* @throws UncheckedIOException if an I/O error occurs
*/
static ModuleHashes generate(Set<ModuleReference> mrefs, String algorithm) {
Map<String, byte[]> nameToHash = new TreeMap<>();
for (ModuleReference mref : mrefs) {
try (ModuleReader reader = mref.open()) {
byte[] hash = computeHash(reader, algorithm);
nameToHash.put(mref.descriptor().name(), hash);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
return new ModuleHashes(algorithm, nameToHash);
}
@Override
public int hashCode() {
int h = algorithm.hashCode();
for (Map.Entry<String, byte[]> e : nameToHash.entrySet()) {
h = h * 31 + e.getKey().hashCode();
h = h * 31 + Arrays.hashCode(e.getValue());
}
return h;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ModuleHashes))
return false;
ModuleHashes other = (ModuleHashes) obj;
if (!algorithm.equals(other.algorithm)
|| nameToHash.size() != other.nameToHash.size())
return false;
for (Map.Entry<String, byte[]> e : nameToHash.entrySet()) {
String name = e.getKey();
byte[] hash = e.getValue();
if (!Arrays.equals(hash, other.nameToHash.get(name)))
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(algorithm);
sb.append(" ");
nameToHash.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.forEach(e -> {
sb.append(e.getKey());
sb.append("=");
byte[] ba = e.getValue();
for (byte b : ba) {
sb.append(String.format("%02x", b & 0xff));
}
});
return sb.toString();
}
/**
* This is used by jdk.internal.module.SystemModules class
* generated at link time.
*/
public static class Builder {
final String algorithm;
final Map<String, byte[]> nameToHash;
Builder(String algorithm, int initialCapacity) {
this.nameToHash = new HashMap<>(initialCapacity);
this.algorithm = Objects.requireNonNull(algorithm);
}
/**
* Sets the module hash for the given module name
*/
public Builder hashForModule(String mn, byte[] hash) {
nameToHash.put(mn, hash);
return this;
}
/**
* Builds a {@code ModuleHashes}.
*/
public ModuleHashes build() {
if (!nameToHash.isEmpty()) {
return new ModuleHashes(algorithm, nameToHash);
} else {
return null;
}
}
}
}

View file

@ -0,0 +1,292 @@
/*
* Copyright (c) 2017, 2020, 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;
import java.io.PrintStream;
import java.lang.module.Configuration;
import java.lang.module.ModuleReference;
import java.lang.module.ResolvedModule;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.TreeMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Stream;
import static java.util.stream.Collectors.*;
/**
* A Builder to compute ModuleHashes from a given configuration
*/
public class ModuleHashesBuilder {
private final Configuration configuration;
private final Set<String> hashModuleCandidates;
/**
* Constructs a ModuleHashesBuilder that finds the packaged modules
* from the location of ModuleReference found from the given Configuration.
*
* @param config Configuration for building module hashes
* @param modules the candidate modules to be hashed
*/
public ModuleHashesBuilder(Configuration config, Set<String> modules) {
this.configuration = config;
this.hashModuleCandidates = modules;
}
/**
* Returns a map of a module M to ModuleHashes for the modules
* that depend upon M directly or indirectly.
*
* The key for each entry in the returned map is a module M that has
* no outgoing edges to any of the candidate modules to be hashed
* i.e. M is a leaf node in a connected subgraph containing M and
* other candidate modules from the module graph filtering
* the outgoing edges from M to non-candidate modules.
*/
public Map<String, ModuleHashes> computeHashes(Set<String> roots) {
// build a graph containing the packaged modules and
// its transitive dependences matching --hash-modules
Graph.Builder<String> builder = new Graph.Builder<>();
Deque<ResolvedModule> todo = new ArrayDeque<>(configuration.modules());
Set<ResolvedModule> visited = new HashSet<>();
ResolvedModule rm;
while ((rm = todo.poll()) != null) {
if (visited.add(rm)) {
builder.addNode(rm.name());
for (ResolvedModule dm : rm.reads()) {
if (!visited.contains(dm)) {
todo.push(dm);
}
builder.addEdge(rm.name(), dm.name());
}
}
}
// each node in a transposed graph is a matching packaged module
// in which the hash of the modules that depend upon it is recorded
Graph<String> transposedGraph = builder.build().transpose();
// traverse the modules in topological order that will identify
// the modules to record the hashes - it is the first matching
// module and has not been hashed during the traversal.
Set<String> mods = new HashSet<>();
Map<String, ModuleHashes> hashes = new TreeMap<>();
builder.build()
.orderedNodes()
.filter(mn -> roots.contains(mn) && !mods.contains(mn))
.forEach(mn -> {
// Compute hashes of the modules that depend on mn directly and
// indirectly excluding itself.
Set<String> ns = transposedGraph.dfs(mn)
.stream()
.filter(n -> !n.equals(mn) && hashModuleCandidates.contains(n))
.collect(toSet());
mods.add(mn);
mods.addAll(ns);
if (!ns.isEmpty()) {
Set<ModuleReference> mrefs = ns.stream()
.map(name -> configuration.findModule(name)
.orElseThrow(InternalError::new))
.map(ResolvedModule::reference)
.collect(toSet());
hashes.put(mn, ModuleHashes.generate(mrefs, "SHA-256"));
}
});
return hashes;
}
/*
* Utility class
*/
static class Graph<T> {
private final Set<T> nodes;
private final Map<T, Set<T>> edges;
public Graph(Set<T> nodes, Map<T, Set<T>> edges) {
this.nodes = Collections.unmodifiableSet(nodes);
this.edges = Collections.unmodifiableMap(edges);
}
public Set<T> nodes() {
return nodes;
}
public Map<T, Set<T>> edges() {
return edges;
}
public Set<T> adjacentNodes(T u) {
return edges.get(u);
}
public boolean contains(T u) {
return nodes.contains(u);
}
/**
* Returns nodes sorted in topological order.
*/
public Stream<T> orderedNodes() {
TopoSorter<T> sorter = new TopoSorter<>(this);
return sorter.result.stream();
}
/**
* Traverses this graph and performs the given action in topological order.
*/
public void ordered(Consumer<T> action) {
TopoSorter<T> sorter = new TopoSorter<>(this);
sorter.ordered(action);
}
/**
* Traverses this graph and performs the given action in reverse topological order.
*/
public void reverse(Consumer<T> action) {
TopoSorter<T> sorter = new TopoSorter<>(this);
sorter.reverse(action);
}
/**
* Returns a transposed graph from this graph.
*/
public Graph<T> transpose() {
Builder<T> builder = new Builder<>();
nodes.forEach(builder::addNode);
// reverse edges
edges.keySet().forEach(u -> {
edges.get(u).forEach(v -> builder.addEdge(v, u));
});
return builder.build();
}
/**
* Returns all nodes reachable from the given root.
*/
public Set<T> dfs(T root) {
return dfs(Set.of(root));
}
/**
* Returns all nodes reachable from the given set of roots.
*/
public Set<T> dfs(Set<T> roots) {
ArrayDeque<T> todo = new ArrayDeque<>(roots);
Set<T> visited = new HashSet<>();
T u;
while ((u = todo.poll()) != null) {
if (visited.add(u) && contains(u)) {
adjacentNodes(u).stream()
.filter(v -> !visited.contains(v))
.forEach(todo::push);
}
}
return visited;
}
public void printGraph(PrintStream out) {
out.println("graph for " + nodes);
nodes
.forEach(u -> adjacentNodes(u)
.forEach(v -> out.format(" %s -> %s%n", u, v)));
}
static class Builder<T> {
final Set<T> nodes = new HashSet<>();
final Map<T, Set<T>> edges = new HashMap<>();
public void addNode(T node) {
if (nodes.add(node)) {
edges.computeIfAbsent(node, _e -> new HashSet<>());
}
}
public void addEdge(T u, T v) {
addNode(u);
addNode(v);
edges.get(u).add(v);
}
public Graph<T> build() {
return new Graph<T>(nodes, edges);
}
}
}
/**
* Topological sort
*/
private static class TopoSorter<T> {
final Deque<T> result = new ArrayDeque<>();
final Graph<T> graph;
TopoSorter(Graph<T> graph) {
this.graph = graph;
sort();
}
public void ordered(Consumer<T> action) {
result.forEach(action);
}
public void reverse(Consumer<T> action) {
result.descendingIterator().forEachRemaining(action);
}
private void sort() {
Set<T> visited = new HashSet<>();
Deque<T> stack = new ArrayDeque<>();
// CWE-407 fix: parallel Set for O(1) stack membership test.
// Deque.contains() is O(n); stackSet.contains() is O(1).
Set<T> stackSet = new HashSet<>();
graph.nodes.forEach(node -> visit(node, visited, stack, stackSet));
}
private Set<T> children(T node) {
return graph.edges().get(node);
}
private void visit(T node, Set<T> visited, Deque<T> stack, Set<T> stackSet) {
if (visited.add(node)) {
stack.push(node);
stackSet.add(node);
children(node).forEach(child -> visit(child, visited, stack, stackSet));
stack.pop();
stackSet.remove(node);
result.addLast(node);
}
else if (stackSet.contains(node)) {
throw new IllegalArgumentException(
"Cycle detected: " + node + " -> " + children(node));
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,212 @@
/*
* Copyright (c) 2014, 2024, 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;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.constant.ClassDesc;
import java.lang.module.ModuleDescriptor.Version;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.lang.classfile.ClassFile;
import java.lang.classfile.ClassTransform;
import java.lang.classfile.attribute.ModuleAttribute;
import java.lang.classfile.attribute.ModuleHashInfo;
import java.lang.classfile.attribute.ModuleHashesAttribute;
import java.lang.classfile.attribute.ModuleMainClassAttribute;
import java.lang.classfile.attribute.ModulePackagesAttribute;
import java.lang.classfile.attribute.ModuleResolutionAttribute;
import java.lang.classfile.attribute.ModuleTargetAttribute;
import java.lang.constant.ModuleDesc;
import java.lang.constant.PackageDesc;
/**
* Utility class to extend a module-info.class with additional attributes.
*/
public final class ModuleInfoExtender {
// the input stream to read the original module-info.class
private final InputStream in;
// the packages in the ModulePackages attribute
private Set<String> packages;
// the value for the module version in the Module attribute
private Version version;
// the value of the ModuleMainClass attribute
private String mainClass;
// the value for the ModuleTarget attribute
private String targetPlatform;
// the hashes for the ModuleHashes attribute
private ModuleHashes hashes;
// the value of the ModuleResolution attribute
private ModuleResolution moduleResolution;
private ModuleInfoExtender(InputStream in) {
this.in = in;
}
/**
* Sets the packages for the ModulePackages attribute
*
* @apiNote This method does not check that the package names are legal
* package names or that the set of packages is a super set of the
* packages in the module.
*/
public ModuleInfoExtender packages(Set<String> packages) {
this.packages = Collections.unmodifiableSet(packages);
return this;
}
/**
* Sets the value for the module version in the Module attribute
*/
public ModuleInfoExtender version(Version version) {
this.version = version;
return this;
}
/**
* Sets the value of the ModuleMainClass attribute.
*
* @apiNote This method does not check that the main class is a legal
* class name in a named package.
*/
public ModuleInfoExtender mainClass(String mainClass) {
this.mainClass = mainClass;
return this;
}
/**
* Sets the value for the ModuleTarget attribute.
*/
public ModuleInfoExtender targetPlatform(String targetPlatform) {
this.targetPlatform = targetPlatform;
return this;
}
/**
* The ModuleHashes attribute will be emitted to the module-info with
* the hashes encapsulated in the given {@code ModuleHashes}
* object.
*/
public ModuleInfoExtender hashes(ModuleHashes hashes) {
this.hashes = hashes;
return this;
}
/**
* Sets the value for the ModuleResolution attribute.
*/
public ModuleInfoExtender moduleResolution(ModuleResolution mres) {
this.moduleResolution = mres;
return this;
}
/**
* Outputs the modified module-info.class to the given output stream.
* Once this method has been called then the Extender object should
* be discarded.
*/
public void write(OutputStream out) throws IOException {
// emit to the output stream
out.write(toByteArray());
}
/**
* Returns the bytes of the modified module-info.class.
* Once this method has been called then the Extender object should
* be discarded.
*/
public byte[] toByteArray() throws IOException {
var cc = ClassFile.of();
var cm = cc.parse(in.readAllBytes());
Version v = ModuleInfoExtender.this.version;
return cc.transformClass(cm, ClassTransform.endHandler(clb -> {
// ModuleMainClass attribute
if (mainClass != null) {
clb.with(ModuleMainClassAttribute.of(ClassDesc.of(mainClass)));
}
// ModulePackages attribute
if (packages != null) {
List<PackageDesc> packageNames = packages.stream()
.sorted()
.map(PackageDesc::of)
.toList();
clb.with(ModulePackagesAttribute.ofNames(packageNames));
}
// ModuleTarget, ModuleResolution and ModuleHashes attributes
if (targetPlatform != null) {
clb.with(ModuleTargetAttribute.of(targetPlatform));
}
if (moduleResolution != null) {
clb.with(ModuleResolutionAttribute.of(moduleResolution.value()));
}
if (hashes != null) {
clb.with(ModuleHashesAttribute.of(
hashes.algorithm(),
hashes.hashes().entrySet().stream().map(he ->
ModuleHashInfo.of(ModuleDesc.of(
he.getKey()),
he.getValue())).toList()));
}
}).andThen((clb, cle) -> {
if (v != null && cle instanceof ModuleAttribute ma) {
clb.with(ModuleAttribute.of(
ma.moduleName(),
ma.moduleFlagsMask(),
clb.constantPool().utf8Entry(v.toString()),
ma.requires(),
ma.exports(),
ma.opens(),
ma.uses(),
ma.provides()));
} else {
clb.accept(cle);
}
}));
}
/**
* Returns an {@code Extender} that may be used to add additional
* attributes to the module-info.class read from the given input
* stream.
*/
public static ModuleInfoExtender newExtender(InputStream in) {
return new ModuleInfoExtender(in);
}
}

View file

@ -0,0 +1,149 @@
/*
* Copyright (c) 2015, 2024, 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;
import java.lang.module.Configuration;
import java.lang.module.ResolvedModule;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import jdk.internal.loader.ClassLoaders;
/**
* Supports the mapping of modules to class loaders. The set of modules mapped
* to the boot and platform class loaders is generated at build time from
* this source file.
*/
public final class ModuleLoaderMap {
/**
* Maps the system modules to the built-in class loaders.
*/
private static final class Mapper implements Function<String, ClassLoader> {
private static final ClassLoader PLATFORM_CLASSLOADER =
ClassLoaders.platformClassLoader();
private static final ClassLoader APP_CLASSLOADER =
ClassLoaders.appClassLoader();
private static final String PLATFORM_LOADER_NAME = "PLATFORM";
private static final String APP_LOADER_NAME = "APP";
/**
* Map from module name to class loader name. The name is resolved to the
* actual class loader in {@code apply}.
*/
private final Map<String, String> map;
/**
* Creates a Mapper to map module names in the given Configuration to
* built-in classloaders.
*
* As a proxy for the actual classloader, we store an easily archiveable
* loader name in the internal map.
*/
Mapper(Configuration cf) {
var map = new HashMap<String, String>();
for (ResolvedModule resolvedModule : cf.modules()) {
String mn = resolvedModule.name();
if (!Modules.bootModules.contains(mn)) {
if (Modules.platformModules.contains(mn)) {
map.put(mn, PLATFORM_LOADER_NAME);
} else {
map.put(mn, APP_LOADER_NAME);
}
}
}
this.map = map;
}
@Override
public ClassLoader apply(String name) {
String loader = map.get(name);
if (APP_LOADER_NAME.equals(loader)) {
return APP_CLASSLOADER;
} else if (PLATFORM_LOADER_NAME.equals(loader)) {
return PLATFORM_CLASSLOADER;
} else {
return null;
}
}
}
/**
* Returns the names of the modules defined to the boot loader.
*/
public static Set<String> bootModules() {
return Modules.bootModules;
}
/**
* Returns the names of the modules defined to the platform loader.
*/
public static Set<String> platformModules() {
return Modules.platformModules;
}
/**
* Returns the names of the modules defined to the application loader which perform native access.
*/
public static Set<String> nativeAccessModules() {
return Modules.nativeAccessModules;
}
private static class Modules {
// list of boot modules is generated at build time.
private static final Set<String> bootModules =
Set.of(new String[] { "@@BOOT_MODULE_NAMES@@" });
// list of platform modules is generated at build time.
private static final Set<String> platformModules =
Set.of(new String[] { "@@PLATFORM_MODULE_NAMES@@" });
// list of jdk modules is generated at build time.
private static final Set<String> nativeAccessModules =
Set.of(new String[] { "@@NATIVE_ACCESS_MODULE_NAMES@@" });
}
/**
* Returns a function to map modules in the given configuration to the
* built-in class loaders.
*/
static Function<String, ClassLoader> mappingFunction(Configuration cf) {
return new Mapper(cf);
}
/**
* When defining modules for a configuration, we only allow defining modules
* to the boot or platform classloader if the ClassLoader mapping function
* originate from here.
*/
public static boolean isBuiltinMapper(Function<String, ClassLoader> clf) {
return clf instanceof Mapper;
}
}

View file

@ -0,0 +1,624 @@
/*
* Copyright (c) 2015, 2025, 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;
import java.io.Closeable;
import java.io.File;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Builder;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;
import jdk.internal.loader.Resource;
import jdk.internal.access.JavaLangModuleAccess;
import jdk.internal.access.SharedSecrets;
import sun.net.www.ParseUtil;
/**
* Provides support for patching modules, mostly the boot layer.
*/
public final class ModulePatcher {
private static final JavaLangModuleAccess JLMA
= SharedSecrets.getJavaLangModuleAccess();
// module name -> sequence of patches (directories or JAR files)
private final Map<String, List<Path>> map;
/**
* Initialize the module patcher with the given map. The map key is
* the module name, the value is a list of path strings.
*/
public ModulePatcher(Map<String, List<String>> input) {
if (input.isEmpty()) {
this.map = Map.of();
} else {
Map<String, List<Path>> map = new HashMap<>();
for (Map.Entry<String, List<String>> e : input.entrySet()) {
String mn = e.getKey();
List<Path> paths = e.getValue().stream()
.map(Paths::get)
.toList();
map.put(mn, paths);
}
this.map = map;
}
}
/**
* Returns a module reference that interposes on the given module if
* needed. If there are no patches for the given module then the module
* reference is simply returned. Otherwise the patches for the module
* are scanned (to find any new packages) and a new module reference is
* returned.
*
* @throws UncheckedIOException if an I/O error is detected
*/
public ModuleReference patchIfNeeded(ModuleReference mref) {
// if there are no patches for the module then nothing to do
ModuleDescriptor descriptor = mref.descriptor();
String mn = descriptor.name();
List<Path> paths = map.get(mn);
if (paths == null)
return mref;
// Scan the JAR file or directory tree to get the set of packages.
// For automatic modules then packages that do not contain class files
// must be ignored.
Set<String> packages = new HashSet<>();
boolean isAutomatic = descriptor.isAutomatic();
try {
for (Path file : paths) {
if (Files.isRegularFile(file)) {
// JAR file - do not open as a multi-release JAR as this
// is not supported by the boot class loader
try (JarFile jf = new JarFile(file.toString())) {
jf.stream()
.filter(e -> !e.isDirectory()
&& (!isAutomatic || e.getName().endsWith(".class")))
.map(e -> toPackageName(file, e))
.filter(Checks::isPackageName)
.forEach(packages::add);
}
} else if (Files.isDirectory(file)) {
// exploded directory without following sym links
Path top = file;
try (Stream<Path> stream = Files.find(top, Integer.MAX_VALUE,
((path, attrs) -> attrs.isRegularFile()))) {
stream.filter(path -> (!isAutomatic
|| path.toString().endsWith(".class"))
&& !isHidden(path))
.map(path -> toPackageName(top, path))
.filter(Checks::isPackageName)
.forEach(packages::add);
}
}
}
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
// if there are new packages then we need a new ModuleDescriptor
packages.removeAll(descriptor.packages());
if (!packages.isEmpty()) {
Builder builder = JLMA.newModuleBuilder(descriptor.name(),
/*strict*/ descriptor.isAutomatic(),
descriptor.modifiers());
if (!descriptor.isAutomatic()) {
descriptor.requires().forEach(builder::requires);
descriptor.exports().forEach(builder::exports);
descriptor.opens().forEach(builder::opens);
descriptor.uses().forEach(builder::uses);
}
descriptor.provides().forEach(builder::provides);
descriptor.version().ifPresent(builder::version);
descriptor.mainClass().ifPresent(builder::mainClass);
// original + new packages
builder.packages(descriptor.packages());
builder.packages(packages);
descriptor = builder.build();
}
// return a module reference to the patched module
URI location = mref.location().orElse(null);
ModuleTarget target = null;
ModuleHashes recordedHashes = null;
ModuleHashes.HashSupplier hasher = null;
ModuleResolution mres = null;
if (mref instanceof ModuleReferenceImpl) {
ModuleReferenceImpl impl = (ModuleReferenceImpl)mref;
target = impl.moduleTarget();
recordedHashes = impl.recordedHashes();
hasher = impl.hasher();
mres = impl.moduleResolution();
}
return new ModuleReferenceImpl(descriptor,
location,
() -> new PatchedModuleReader(paths, mref),
this,
target,
recordedHashes,
hasher,
mres);
}
/**
* Returns true is this module patcher has patches.
*/
public boolean hasPatches() {
return !map.isEmpty();
}
/*
* Returns the names of the patched modules.
*/
Set<String> patchedModules() {
return map.keySet();
}
/**
* A ModuleReader that reads resources from a patched module.
*
* This class is public so as to expose the findResource method to the
* built-in class loaders and avoid locating the resource twice during
* class loading (once to locate the resource, the second to gets the
* URL for the CodeSource).
*/
public static class PatchedModuleReader implements ModuleReader {
private final List<ResourceFinder> finders;
private final ModuleReference mref;
private final URL delegateCodeSourceURL;
private volatile ModuleReader delegate;
private volatile boolean closed;
/**
* Creates the ModuleReader to reads resources in a patched module.
*/
PatchedModuleReader(List<Path> patches, ModuleReference mref) {
List<ResourceFinder> finders = new ArrayList<>();
boolean initialized = false;
try {
for (Path file : patches) {
if (Files.isRegularFile(file)) {
finders.add(new JarResourceFinder(file));
} else {
finders.add(new ExplodedResourceFinder(file));
}
}
initialized = true;
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
} finally {
// close all ResourceFinder in the event of an error
if (!initialized) closeAll(finders);
}
this.finders = finders;
this.mref = mref;
this.delegateCodeSourceURL = codeSourceURL(mref);
}
/**
* Closes all resource finders.
*/
private static void closeAll(List<ResourceFinder> finders) {
for (ResourceFinder finder : finders) {
try { finder.close(); } catch (IOException ioe) { }
}
}
/**
* Returns the code source URL for the given module.
*/
private static URL codeSourceURL(ModuleReference mref) {
try {
Optional<URI> ouri = mref.location();
if (ouri.isPresent())
return ouri.get().toURL();
} catch (MalformedURLException e) { }
return null;
}
/**
* Returns the ModuleReader to delegate to when the resource is not
* found in a patch location.
*/
private ModuleReader delegate() throws IOException {
ModuleReader r = delegate;
if (r == null) {
synchronized (this) {
r = delegate;
if (r == null) {
delegate = r = mref.open();
}
}
}
return r;
}
/**
* Throws an IOException if the ModuleReader is closed.
*/
private void ensureOpen() throws IOException {
if (closed) {
throw new IOException("ModuleReader is closed");
}
}
/**
* Finds a resources in the patch locations. Returns null if not found
* or the name is "module-info.class" as that cannot be overridden.
*/
private Resource findResourceInPatch(String name) throws IOException {
if (!name.equals("module-info.class")) {
for (ResourceFinder finder : finders) {
Resource r = finder.find(name);
if (r != null)
return r;
}
}
return null;
}
/**
* Finds a resource of the given name in the patched module.
*/
public Resource findResource(String name) throws IOException {
assert !closed : "module reader is closed";
// patch locations
Resource r = findResourceInPatch(name);
if (r != null)
return r;
// original module
ByteBuffer bb = delegate().read(name).orElse(null);
if (bb == null)
return null;
return new Resource() {
private <T> T shouldNotGetHere(Class<T> type) {
throw new InternalError("should not get here");
}
@Override
public String getName() {
return shouldNotGetHere(String.class);
}
@Override
public URL getURL() {
return shouldNotGetHere(URL.class);
}
@Override
public URL getCodeSourceURL() {
return delegateCodeSourceURL;
}
@Override
public ByteBuffer getByteBuffer() throws IOException {
return bb;
}
@Override
public InputStream getInputStream() throws IOException {
return shouldNotGetHere(InputStream.class);
}
@Override
public int getContentLength() throws IOException {
return shouldNotGetHere(int.class);
}
};
}
@Override
public Optional<URI> find(String name) throws IOException {
ensureOpen();
Resource r = findResourceInPatch(name);
if (r != null) {
URI uri = URI.create(r.getURL().toString());
return Optional.of(uri);
} else {
return delegate().find(name);
}
}
@Override
public Optional<InputStream> open(String name) throws IOException {
ensureOpen();
Resource r = findResourceInPatch(name);
if (r != null) {
return Optional.of(r.getInputStream());
} else {
return delegate().open(name);
}
}
@Override
public Optional<ByteBuffer> read(String name) throws IOException {
ensureOpen();
Resource r = findResourceInPatch(name);
if (r != null) {
ByteBuffer bb = r.getByteBuffer();
assert !bb.isDirect();
return Optional.of(bb);
} else {
return delegate().read(name);
}
}
@Override
public void release(ByteBuffer bb) {
if (bb.isDirect()) {
try {
delegate().release(bb);
} catch (IOException ioe) {
throw new InternalError(ioe);
}
}
}
@Override
public Stream<String> list() throws IOException {
ensureOpen();
Stream<String> s = delegate().list();
for (ResourceFinder finder : finders) {
s = Stream.concat(s, finder.list());
}
return s.distinct();
}
@Override
public void close() throws IOException {
if (closed) {
return;
}
closed = true;
closeAll(finders);
delegate().close();
}
}
/**
* A resource finder that find resources in a patch location.
*/
private static interface ResourceFinder extends Closeable {
Resource find(String name) throws IOException;
Stream<String> list() throws IOException;
}
/**
* A ResourceFinder that finds resources in a JAR file.
*/
private static class JarResourceFinder implements ResourceFinder {
private final JarFile jf;
private final URL csURL;
JarResourceFinder(Path path) throws IOException {
this.jf = new JarFile(path.toString());
this.csURL = path.toUri().toURL();
}
@Override
public void close() throws IOException {
jf.close();
}
@Override
public Resource find(String name) throws IOException {
JarEntry entry = jf.getJarEntry(name);
if (entry == null)
return null;
return new Resource() {
@Override
public String getName() {
return name;
}
@Override
public URL getURL() {
String encodedPath = ParseUtil.encodePath(name, false);
try {
@SuppressWarnings("deprecation")
var result = new URL("jar:" + csURL + "!/" + encodedPath);
return result;
} catch (MalformedURLException e) {
return null;
}
}
@Override
public URL getCodeSourceURL() {
return csURL;
}
@Override
public ByteBuffer getByteBuffer() throws IOException {
try (InputStream in = getInputStream()) {
byte[] bytes = in.readAllBytes();
return ByteBuffer.wrap(bytes);
}
}
@Override
public InputStream getInputStream() throws IOException {
return jf.getInputStream(entry);
}
@Override
public int getContentLength() throws IOException {
long size = entry.getSize();
return (size > Integer.MAX_VALUE) ? -1 : (int) size;
}
};
}
@Override
public Stream<String> list() throws IOException {
return jf.stream().map(JarEntry::getName);
}
}
/**
* A ResourceFinder that finds resources on the file system.
*/
private static class ExplodedResourceFinder implements ResourceFinder {
private final Path dir;
ExplodedResourceFinder(Path dir) {
this.dir = dir;
}
@Override
public void close() { }
@Override
public Resource find(String name) throws IOException {
Path file = Resources.toFilePath(dir, name);
if (file != null) {
return newResource(name, dir, file);
} else {
return null;
}
}
private Resource newResource(String name, Path top, Path file) {
return new Resource() {
@Override
public String getName() {
return name;
}
@Override
public URL getURL() {
try {
return file.toUri().toURL();
} catch (IOException | IOError e) {
return null;
}
}
@Override
public URL getCodeSourceURL() {
try {
return top.toUri().toURL();
} catch (IOException | IOError e) {
return null;
}
}
@Override
public ByteBuffer getByteBuffer() throws IOException {
return ByteBuffer.wrap(Files.readAllBytes(file));
}
@Override
public InputStream getInputStream() throws IOException {
return Files.newInputStream(file);
}
@Override
public int getContentLength() throws IOException {
long size = Files.size(file);
return (size > Integer.MAX_VALUE) ? -1 : (int)size;
}
};
}
@Override
public Stream<String> list() throws IOException {
return Files.walk(dir, Integer.MAX_VALUE)
.map(f -> Resources.toResourceName(dir, f))
.filter(s -> !s.isEmpty());
}
}
/**
* Derives a package name from the file path of an entry in an exploded patch
*/
private static String toPackageName(Path top, Path file) {
Path entry = top.relativize(file);
Path parent = entry.getParent();
if (parent == null) {
return warnIfModuleInfo(top, entry.toString());
} else {
return parent.toString().replace(File.separatorChar, '.');
}
}
/**
* Returns true if the given file exists and is a hidden file
*/
private boolean isHidden(Path file) {
try {
return Files.isHidden(file);
} catch (IOException ioe) {
return false;
}
}
/**
* Derives a package name from the name of an entry in a JAR file.
*/
private static String toPackageName(Path file, JarEntry entry) {
String name = entry.getName();
int index = name.lastIndexOf("/");
if (index == -1) {
return warnIfModuleInfo(file, name);
} else {
return name.substring(0, index).replace('/', '.');
}
}
private static String warnIfModuleInfo(Path file, String e) {
if (e.equals("module-info.class"))
System.err.println("WARNING: " + e + " ignored in patch: " + file);
return "";
}
}

View file

@ -0,0 +1,789 @@
/*
* Copyright (c) 2014, 2022, 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;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.lang.module.FindException;
import java.lang.module.InvalidModuleDescriptorException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Builder;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.net.URI;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import sun.nio.cs.UTF_8;
import jdk.internal.jmod.JmodFile;
import jdk.internal.jmod.JmodFile.Section;
import jdk.internal.perf.PerfCounter;
/**
* A {@code ModuleFinder} that locates modules on the file system by searching
* a sequence of directories or packaged modules. The ModuleFinder can be
* created to work in either the run-time or link-time phases. In both cases it
* locates modular JAR and exploded modules. When created for link-time then it
* additionally locates modules in JMOD files. The ModuleFinder can also
* optionally patch any modules that it locates with a ModulePatcher.
*/
public class ModulePath implements ModuleFinder {
private static final String MODULE_INFO = "module-info.class";
// the version to use for multi-release modular JARs
private final Runtime.Version releaseVersion;
// true for the link phase (supports modules packaged in JMOD format)
private final boolean isLinkPhase;
// for patching modules, can be null
private final ModulePatcher patcher;
// the entries on this module path
private final Path[] entries;
private int next;
// map of module name to module reference map for modules already located
private final Map<String, ModuleReference> cachedModules = new HashMap<>();
private ModulePath(Runtime.Version version,
boolean isLinkPhase,
ModulePatcher patcher,
Path... entries) {
this.releaseVersion = version;
this.isLinkPhase = isLinkPhase;
this.patcher = patcher;
this.entries = entries.clone();
for (Path entry : this.entries) {
Objects.requireNonNull(entry);
}
}
/**
* Returns a ModuleFinder that locates modules on the file system by
* searching a sequence of directories and/or packaged modules. The modules
* may be patched by the given ModulePatcher.
*/
public static ModuleFinder of(ModulePatcher patcher, Path... entries) {
return new ModulePath(JarFile.runtimeVersion(), false, patcher, entries);
}
/**
* Returns a ModuleFinder that locates modules on the file system by
* searching a sequence of directories and/or packaged modules.
*/
public static ModuleFinder of(Path... entries) {
return of((ModulePatcher)null, entries);
}
/**
* Returns a ModuleFinder that locates modules on the file system by
* searching a sequence of directories and/or packaged modules.
*
* @param version The release version to use for multi-release JAR files
* @param isLinkPhase {@code true} if the link phase to locate JMOD files
*/
public static ModuleFinder of(Runtime.Version version,
boolean isLinkPhase,
Path... entries) {
return new ModulePath(version, isLinkPhase, null, entries);
}
@Override
public Optional<ModuleReference> find(String name) {
Objects.requireNonNull(name);
// try cached modules
ModuleReference m = cachedModules.get(name);
if (m != null)
return Optional.of(m);
// the module may not have been encountered yet
while (hasNextEntry()) {
scanNextEntry();
m = cachedModules.get(name);
if (m != null)
return Optional.of(m);
}
return Optional.empty();
}
@Override
public Set<ModuleReference> findAll() {
// need to ensure that all entries have been scanned
while (hasNextEntry()) {
scanNextEntry();
}
return cachedModules.values().stream().collect(Collectors.toSet());
}
/**
* Returns {@code true} if there are additional entries to scan
*/
private boolean hasNextEntry() {
return next < entries.length;
}
/**
* Scans the next entry on the module path. A no-op if all entries have
* already been scanned.
*
* @throws FindException if an error occurs scanning the next entry
*/
private void scanNextEntry() {
if (hasNextEntry()) {
long t0 = System.nanoTime();
Path entry = entries[next];
Map<String, ModuleReference> modules = scan(entry);
next++;
// update cache, ignoring duplicates
int initialSize = cachedModules.size();
for (Map.Entry<String, ModuleReference> e : modules.entrySet()) {
cachedModules.putIfAbsent(e.getKey(), e.getValue());
}
// update counters
int added = cachedModules.size() - initialSize;
moduleCount.add(added);
scanTime.addElapsedTimeFrom(t0);
}
}
/**
* Scan the given module path entry. If the entry is a directory then it is
* a directory of modules or an exploded module. If the entry is a regular
* file then it is assumed to be a packaged module.
*
* @throws FindException if an error occurs scanning the entry
*/
private Map<String, ModuleReference> scan(Path entry) {
BasicFileAttributes attrs;
try {
attrs = Files.readAttributes(entry, BasicFileAttributes.class);
} catch (NoSuchFileException e) {
return Map.of();
} catch (IOException ioe) {
throw new FindException(ioe);
}
try {
if (attrs.isDirectory()) {
Path mi = entry.resolve(MODULE_INFO);
if (!Files.exists(mi)) {
// assume a directory of modules
return scanDirectory(entry);
}
}
// packaged or exploded module
ModuleReference mref = readModule(entry, attrs);
if (mref != null) {
String name = mref.descriptor().name();
return Map.of(name, mref);
}
// not recognized
String msg;
if (!isLinkPhase && entry.toString().endsWith(".jmod")) {
msg = "JMOD format not supported at execution time";
} else {
msg = "Module format not recognized";
}
throw new FindException(msg + ": " + entry);
} catch (IOException ioe) {
throw new FindException(ioe);
}
}
/**
* Scans the given directory for packaged or exploded modules.
*
* @return a map of module name to ModuleReference for the modules found
* in the directory
*
* @throws IOException if an I/O error occurs
* @throws FindException if an error occurs scanning the entry or the
* directory contains two or more modules with the same name
*/
private Map<String, ModuleReference> scanDirectory(Path dir)
throws IOException
{
// The map of name -> mref of modules found in this directory.
Map<String, ModuleReference> nameToReference = new HashMap<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path entry : stream) {
BasicFileAttributes attrs;
try {
attrs = Files.readAttributes(entry, BasicFileAttributes.class);
} catch (NoSuchFileException ignore) {
// file has been removed or moved, ignore for now
continue;
}
ModuleReference mref = readModule(entry, attrs);
// module found
if (mref != null) {
// can have at most one version of a module in the directory
String name = mref.descriptor().name();
ModuleReference previous = nameToReference.put(name, mref);
if (previous != null) {
String fn1 = fileName(mref);
String fn2 = fileName(previous);
throw new FindException("Two versions of module "
+ name + " found in " + dir
+ " (" + fn1 + " and " + fn2 + ")");
}
}
}
}
return nameToReference;
}
/**
* Reads a packaged or exploded module, returning a {@code ModuleReference}
* to the module. Returns {@code null} if the entry is not recognized.
*
* @throws IOException if an I/O error occurs
* @throws FindException if an error occurs parsing its module descriptor
*/
private ModuleReference readModule(Path entry, BasicFileAttributes attrs)
throws IOException
{
try {
// exploded module
if (attrs.isDirectory()) {
return readExplodedModule(entry); // may return null
}
// JAR or JMOD file
if (attrs.isRegularFile()) {
String fn = entry.getFileName().toString();
boolean isDefaultFileSystem = isDefaultFileSystem(entry);
// JAR file
if (fn.endsWith(".jar")) {
if (isDefaultFileSystem) {
return readJar(entry);
} else {
// the JAR file is in a custom file system so
// need to copy it to the local file system
Path tmpdir = Files.createTempDirectory("mlib");
Path target = Files.copy(entry, tmpdir.resolve(fn));
return readJar(target);
}
}
// JMOD file
if (isDefaultFileSystem && isLinkPhase && fn.endsWith(".jmod")) {
return readJMod(entry);
}
}
return null;
} catch (InvalidModuleDescriptorException e) {
throw new FindException("Error reading module: " + entry, e);
}
}
/**
* Returns a string with the file name of the module if possible.
* If the module location is not a file URI then return the URI
* as a string.
*/
private String fileName(ModuleReference mref) {
URI uri = mref.location().orElse(null);
if (uri != null) {
if (uri.getScheme().equalsIgnoreCase("file")) {
Path file = Path.of(uri);
return file.getFileName().toString();
} else {
return uri.toString();
}
} else {
return "<unknown>";
}
}
// -- JMOD files --
private Set<String> jmodPackages(JmodFile jf) {
return jf.stream()
.filter(e -> e.section() == Section.CLASSES)
.map(JmodFile.Entry::name)
.map(this::toPackageName)
.flatMap(Optional::stream)
.collect(Collectors.toSet());
}
/**
* Returns a {@code ModuleReference} to a module in JMOD file on the
* file system.
*
* @throws IOException
* @throws InvalidModuleDescriptorException
*/
private ModuleReference readJMod(Path file) throws IOException {
try (JmodFile jf = new JmodFile(file)) {
ModuleInfo.Attributes attrs;
try (InputStream in = jf.getInputStream(Section.CLASSES, MODULE_INFO)) {
attrs = ModuleInfo.read(in, () -> jmodPackages(jf));
}
return ModuleReferences.newJModModule(attrs, file);
}
}
// -- JAR files --
private static final String SERVICES_PREFIX = "META-INF/services/";
private static final Attributes.Name AUTOMATIC_MODULE_NAME
= new Attributes.Name("Automatic-Module-Name");
/**
* Returns the service type corresponding to the name of a services
* configuration file if it is a legal type name.
*
* For example, if called with "META-INF/services/p.S" then this method
* returns a container with the value "p.S".
*/
private Optional<String> toServiceName(String cf) {
assert cf.startsWith(SERVICES_PREFIX);
int index = cf.lastIndexOf("/") + 1;
if (index < cf.length()) {
String prefix = cf.substring(0, index);
if (prefix.equals(SERVICES_PREFIX)) {
String sn = cf.substring(index);
if (Checks.isClassName(sn))
return Optional.of(sn);
}
}
return Optional.empty();
}
/**
* Reads the next line from the given reader and trims it of comments and
* leading/trailing white space.
*
* Returns null if the reader is at EOF.
*/
private String nextLine(BufferedReader reader) throws IOException {
String ln = reader.readLine();
if (ln != null) {
int ci = ln.indexOf('#');
if (ci >= 0)
ln = ln.substring(0, ci);
ln = ln.trim();
}
return ln;
}
/**
* Treat the given JAR file as a module as follows:
*
* 1. The value of the Automatic-Module-Name attribute is the module name
* 2. The version, and the module name when the Automatic-Module-Name
* attribute is not present, is derived from the file ame of the JAR file
* 3. All packages are derived from the .class files in the JAR file
* 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
* is mapped to the module descriptor mainClass if possible
*/
private ModuleDescriptor deriveModuleDescriptor(JarFile jf)
throws IOException
{
// Read Automatic-Module-Name attribute if present
Manifest man = jf.getManifest();
Attributes attrs = null;
String moduleName = null;
if (man != null) {
attrs = man.getMainAttributes();
if (attrs != null) {
moduleName = attrs.getValue(AUTOMATIC_MODULE_NAME);
}
}
// Derive the version, and the module name if needed, from JAR file name
String fn = jf.getName();
int i = fn.lastIndexOf(File.separator);
if (i != -1)
fn = fn.substring(i + 1);
// drop ".jar"
String name = fn.substring(0, fn.length() - 4);
String vs = null;
// find first occurrence of -${NUMBER}. or -${NUMBER}$
Matcher matcher = Patterns.DASH_VERSION.matcher(name);
if (matcher.find()) {
int start = matcher.start();
// attempt to parse the tail as a version string
try {
String tail = name.substring(start + 1);
ModuleDescriptor.Version.parse(tail);
vs = tail;
} catch (IllegalArgumentException ignore) { }
name = name.substring(0, start);
}
// Create builder, using the name derived from file name when
// Automatic-Module-Name not present
Builder builder;
if (moduleName != null) {
try {
builder = ModuleDescriptor.newAutomaticModule(moduleName);
} catch (IllegalArgumentException e) {
throw new FindException(AUTOMATIC_MODULE_NAME + ": " + e.getMessage());
}
} else {
builder = ModuleDescriptor.newAutomaticModule(cleanModuleName(name));
}
// module version if present
if (vs != null)
builder.version(vs);
// scan the names of the entries in the JAR file
Map<Boolean, Set<String>> map = jf.versionedStream()
.filter(e -> !e.isDirectory())
.map(JarEntry::getName)
.filter(e -> (e.endsWith(".class") ^ e.startsWith(SERVICES_PREFIX)))
.collect(Collectors.partitioningBy(e -> e.startsWith(SERVICES_PREFIX),
Collectors.toSet()));
Set<String> classFiles = map.get(Boolean.FALSE);
Set<String> configFiles = map.get(Boolean.TRUE);
// the packages containing class files
Set<String> packages = classFiles.stream()
.map(this::toPackageName)
.flatMap(Optional::stream)
.collect(Collectors.toSet());
// all packages are exported and open
builder.packages(packages);
// map names of service configuration files to service names
Set<String> serviceNames = configFiles.stream()
.map(this::toServiceName)
.flatMap(Optional::stream)
.collect(Collectors.toSet());
// parse each service configuration file
for (String sn : serviceNames) {
JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn);
List<String> providerClasses = new ArrayList<>();
try (InputStream in = jf.getInputStream(entry)) {
BufferedReader reader
= new BufferedReader(new InputStreamReader(in, UTF_8.INSTANCE));
String cn;
while ((cn = nextLine(reader)) != null) {
if (!cn.isEmpty()) {
String pn = packageName(cn);
if (!packages.contains(pn)) {
String msg = "Provider class " + cn + " not in JAR file " + fn;
throw new InvalidModuleDescriptorException(msg);
}
providerClasses.add(cn);
}
}
}
if (!providerClasses.isEmpty())
builder.provides(sn, providerClasses);
}
// Main-Class attribute if it exists
if (attrs != null) {
String mainClass = attrs.getValue(Attributes.Name.MAIN_CLASS);
if (mainClass != null) {
mainClass = mainClass.replace('/', '.');
if (Checks.isClassName(mainClass)) {
String pn = packageName(mainClass);
if (packages.contains(pn)) {
builder.mainClass(mainClass);
}
}
}
}
return builder.build();
}
/**
* Patterns used to derive the module name from a JAR file name.
*/
private static class Patterns {
static final Pattern DASH_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("^\\.");
static final Pattern TRAILING_DOTS = Pattern.compile("\\.$");
}
/**
* Clean up candidate module name derived from a JAR file name.
*/
private static String cleanModuleName(String mn) {
// replace non-alphanumeric
mn = Patterns.NON_ALPHANUM.matcher(mn).replaceAll(".");
// collapse repeating dots
mn = Patterns.REPEATING_DOTS.matcher(mn).replaceAll(".");
// drop leading dots
if (!mn.isEmpty() && mn.charAt(0) == '.')
mn = Patterns.LEADING_DOTS.matcher(mn).replaceAll("");
// drop trailing dots
int len = mn.length();
if (len > 0 && mn.charAt(len-1) == '.')
mn = Patterns.TRAILING_DOTS.matcher(mn).replaceAll("");
return mn;
}
private Set<String> jarPackages(JarFile jf) {
return jf.versionedStream()
.filter(e -> !e.isDirectory())
.map(JarEntry::getName)
.map(this::toPackageName)
.flatMap(Optional::stream)
.collect(Collectors.toSet());
}
/**
* Returns a {@code ModuleReference} to a module in modular JAR file on
* the file system.
*
* @throws IOException
* @throws FindException
* @throws InvalidModuleDescriptorException
*/
private ModuleReference readJar(Path file) throws IOException {
try (JarFile jf = new JarFile(file.toFile(),
true, // verify
ZipFile.OPEN_READ,
releaseVersion))
{
ModuleInfo.Attributes attrs;
JarEntry entry = jf.getJarEntry(MODULE_INFO);
if (entry == null) {
// no module-info.class so treat it as automatic module
try {
ModuleDescriptor md = deriveModuleDescriptor(jf);
attrs = new ModuleInfo.Attributes(md, null, null, null);
} catch (RuntimeException e) {
throw new FindException("Unable to derive module descriptor for "
+ jf.getName(), e);
}
} else {
attrs = ModuleInfo.read(jf.getInputStream(entry),
() -> jarPackages(jf));
}
return ModuleReferences.newJarModule(attrs, patcher, file);
} catch (ZipException e) {
throw new FindException("Error reading " + file, e);
}
}
// -- exploded directories --
private Set<String> explodedPackages(Path dir) {
String separator = dir.getFileSystem().getSeparator();
try (Stream<Path> stream = Files.find(dir, Integer.MAX_VALUE,
(path, attrs) -> attrs.isRegularFile() && !isHidden(path))) {
return stream.map(dir::relativize)
.map(path -> toPackageName(path, separator))
.flatMap(Optional::stream)
.collect(Collectors.toSet());
} catch (IOException x) {
throw new UncheckedIOException(x);
}
}
/**
* Returns a {@code ModuleReference} to an exploded module on the file
* system or {@code null} if {@code module-info.class} not found.
*
* @throws IOException
* @throws InvalidModuleDescriptorException
*/
private ModuleReference readExplodedModule(Path dir) throws IOException {
Path mi = dir.resolve(MODULE_INFO);
ModuleInfo.Attributes attrs;
try (InputStream in = Files.newInputStream(mi)) {
attrs = ModuleInfo.read(new BufferedInputStream(in),
() -> explodedPackages(dir));
} catch (NoSuchFileException e) {
// for now
return null;
}
return ModuleReferences.newExplodedModule(attrs, patcher, dir);
}
/**
* Maps a type name to its package name.
*/
private static String packageName(String cn) {
int index = cn.lastIndexOf('.');
return (index == -1) ? "" : cn.substring(0, index);
}
/**
* Maps the name of an entry in a JAR or ZIP file to a package name.
*
* @throws InvalidModuleDescriptorException if the name is a class file in
* the top-level directory of the JAR/ZIP file (and it's not
* module-info.class)
*/
private Optional<String> toPackageName(String name) {
assert !name.endsWith("/");
int index = name.lastIndexOf("/");
if (index == -1) {
if (name.endsWith(".class") && !name.equals(MODULE_INFO)) {
String msg = name + " found in top-level directory"
+ " (unnamed package not allowed in module)";
throw new InvalidModuleDescriptorException(msg);
}
return Optional.empty();
}
String pn = name.substring(0, index).replace('/', '.');
if (Checks.isPackageName(pn)) {
return Optional.of(pn);
} else {
// not a valid package name
return Optional.empty();
}
}
/**
* Maps the relative path of an entry in an exploded module to a package
* name.
*
* @throws InvalidModuleDescriptorException if the name is a class file in
* the top-level directory (and it's not module-info.class)
*/
private Optional<String> toPackageName(Path file, String separator) {
assert file.getRoot() == null;
Path parent = file.getParent();
if (parent == null) {
String name = file.toString();
if (name.endsWith(".class") && !name.equals(MODULE_INFO)) {
String msg = name + " found in top-level directory"
+ " (unnamed package not allowed in module)";
throw new InvalidModuleDescriptorException(msg);
}
return Optional.empty();
}
String pn = parent.toString().replace(separator, ".");
if (Checks.isPackageName(pn)) {
return Optional.of(pn);
} else {
// not a valid package name
return Optional.empty();
}
}
/**
* Returns true if the given file exists and is a hidden file
*/
private boolean isHidden(Path file) {
try {
return Files.isHidden(file);
} catch (IOException ioe) {
return false;
}
}
/**
* Return true if a path locates a path in the default file system
*/
private boolean isDefaultFileSystem(Path path) {
return path.getFileSystem().provider()
.getScheme().equalsIgnoreCase("file");
}
private static final PerfCounter scanTime
= PerfCounter.newPerfCounter("jdk.module.finder.modulepath.scanTime");
private static final PerfCounter moduleCount
= PerfCounter.newPerfCounter("jdk.module.finder.modulepath.modules");
}

View file

@ -0,0 +1,254 @@
/*
* Copyright (c) 2018, 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;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.module.FindException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.net.URI;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
/**
* A validator to check for errors and conflicts between modules.
*/
class ModulePathValidator {
private static final String MODULE_INFO = "module-info.class";
private static final String INDENT = " ";
private final Map<String, ModuleReference> nameToModule;
private final Map<String, ModuleReference> packageToModule;
private final PrintStream out;
private int errorCount;
private ModulePathValidator(PrintStream out) {
this.nameToModule = new HashMap<>();
this.packageToModule = new HashMap<>();
this.out = out;
}
/**
* Scans and the validates all modules on the module path. The module path
* comprises the upgrade module path, system modules, and the application
* module path.
*
* @param out the print stream for output messages
* @return the number of errors found
*/
static int scanAllModules(PrintStream out) {
ModulePathValidator validator = new ModulePathValidator(out);
// upgrade module path
String value = System.getProperty("jdk.module.upgrade.path");
if (value != null) {
Stream.of(value.split(File.pathSeparator))
.map(Path::of)
.forEach(validator::scan);
}
// system modules
ModuleFinder.ofSystem().findAll().stream()
.sorted(Comparator.comparing(ModuleReference::descriptor))
.forEach(validator::process);
// application module path
value = System.getProperty("jdk.module.path");
if (value != null) {
Stream.of(value.split(File.pathSeparator))
.map(Path::of)
.forEach(validator::scan);
}
return validator.errorCount;
}
/**
* Prints the module location and name.
*/
private void printModule(ModuleReference mref) {
mref.location()
.filter(uri -> !isJrt(uri))
.ifPresent(uri -> out.print(uri + " "));
ModuleDescriptor descriptor = mref.descriptor();
out.print(descriptor.name());
if (descriptor.isAutomatic())
out.print(" automatic");
out.println();
}
/**
* Prints the module location and name, checks if the module is
* shadowed by a previously seen module, and finally checks for
* package conflicts with previously seen modules.
*/
private void process(ModuleReference mref) {
String name = mref.descriptor().name();
ModuleReference previous = nameToModule.putIfAbsent(name, mref);
if (previous != null) {
printModule(mref);
out.print(INDENT + "shadowed by ");
printModule(previous);
} else {
boolean first = true;
// check for package conflicts when not shadowed
for (String pkg : mref.descriptor().packages()) {
previous = packageToModule.putIfAbsent(pkg, mref);
if (previous != null) {
if (first) {
printModule(mref);
first = false;
errorCount++;
}
String mn = previous.descriptor().name();
out.println(INDENT + "contains " + pkg
+ " conflicts with module " + mn);
}
}
}
}
/**
* Scan an element on a module path. The element is a directory
* of modules, an exploded module, or a JAR file.
*/
private void scan(Path entry) {
BasicFileAttributes attrs;
try {
attrs = Files.readAttributes(entry, BasicFileAttributes.class);
} catch (NoSuchFileException ignore) {
return;
} catch (IOException ioe) {
out.println(entry + " " + ioe);
errorCount++;
return;
}
String fn = entry.getFileName().toString();
if (attrs.isRegularFile() && fn.endsWith(".jar")) {
// JAR file, explicit or automatic module
scanModule(entry).ifPresent(this::process);
} else if (attrs.isDirectory()) {
Path mi = entry.resolve(MODULE_INFO);
if (Files.exists(mi)) {
// exploded module
scanModule(entry).ifPresent(this::process);
} else {
// directory of modules
scanDirectory(entry);
}
}
}
/**
* Scan the JAR files and exploded modules in a directory.
*/
private void scanDirectory(Path dir) {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
Map<String, Path> moduleToEntry = new HashMap<>();
for (Path entry : stream) {
BasicFileAttributes attrs;
try {
attrs = Files.readAttributes(entry, BasicFileAttributes.class);
} catch (IOException ioe) {
out.println(entry + " " + ioe);
errorCount++;
continue;
}
ModuleReference mref = null;
String fn = entry.getFileName().toString();
if (attrs.isRegularFile() && fn.endsWith(".jar")) {
mref = scanModule(entry).orElse(null);
} else if (attrs.isDirectory()) {
Path mi = entry.resolve(MODULE_INFO);
if (Files.exists(mi)) {
mref = scanModule(entry).orElse(null);
}
}
if (mref != null) {
String name = mref.descriptor().name();
Path previous = moduleToEntry.putIfAbsent(name, entry);
if (previous != null) {
// same name as other module in the directory
printModule(mref);
out.println(INDENT + "contains same module as "
+ previous.getFileName());
errorCount++;
} else {
process(mref);
}
}
}
} catch (IOException ioe) {
out.println(dir + " " + ioe);
errorCount++;
}
}
/**
* Scan a JAR file or exploded module.
*/
private Optional<ModuleReference> scanModule(Path entry) {
ModuleFinder finder = ModuleFinder.of(entry);
try {
return finder.findAll().stream().findFirst();
} catch (FindException e) {
out.println(entry);
out.println(INDENT + e.getMessage());
Throwable cause = e.getCause();
if (cause != null) {
out.println(INDENT + cause);
}
errorCount++;
return Optional.empty();
}
}
/**
* Returns true if the given URI is a jrt URI
*/
private static boolean isJrt(URI uri) {
return (uri != null && uri.getScheme().equalsIgnoreCase("jrt"));
}
}

View file

@ -0,0 +1,200 @@
/*
* Copyright (c) 2016, 2022, 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;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.net.URI;
import java.util.Objects;
import java.util.function.Supplier;
/**
* A ModuleReference implementation that supports referencing a module that
* is patched and/or can be tied to other modules by means of hashes.
*/
public class ModuleReferenceImpl extends ModuleReference {
// location of module
private final URI location;
// the module reader
private final Supplier<ModuleReader> readerSupplier;
// non-null if the module is patched
private final ModulePatcher patcher;
// ModuleTarget if the module is OS/architecture specific
private final ModuleTarget target;
// the hashes of other modules recorded in this module
private final ModuleHashes recordedHashes;
// the function that computes the hash of this module
private final ModuleHashes.HashSupplier hasher;
// ModuleResolution flags
private final ModuleResolution moduleResolution;
// Single-slot cache of this module's hash to avoid needing to compute
// it many times. For correctness under concurrent updates, we need to
// wrap the fields updated at the same time with a record.
private record CachedHash(byte[] hash, String algorithm) {}
private CachedHash cachedHash;
/**
* Constructs a new instance of this class.
*/
public ModuleReferenceImpl(ModuleDescriptor descriptor,
URI location,
Supplier<ModuleReader> readerSupplier,
ModulePatcher patcher,
ModuleTarget target,
ModuleHashes recordedHashes,
ModuleHashes.HashSupplier hasher,
ModuleResolution moduleResolution)
{
super(descriptor, Objects.requireNonNull(location));
this.location = location;
this.readerSupplier = readerSupplier;
this.patcher = patcher;
this.target = target;
this.recordedHashes = recordedHashes;
this.hasher = hasher;
this.moduleResolution = moduleResolution;
}
@Override
public ModuleReader open() throws IOException {
try {
return readerSupplier.get();
} catch (UncheckedIOException e) {
throw e.getCause();
}
}
/**
* Returns {@code true} if this module has been patched via --patch-module.
*/
public boolean isPatched() {
return (patcher != null);
}
/**
* Returns the ModuleTarget or {@code null} if the no target platform.
*/
public ModuleTarget moduleTarget() {
return target;
}
/**
* Returns the hashes recorded in this module or {@code null} if there
* are no hashes recorded.
*/
public ModuleHashes recordedHashes() {
return recordedHashes;
}
/**
* Returns the supplier that computes the hash of this module.
*/
ModuleHashes.HashSupplier hasher() {
return hasher;
}
/**
* Returns the ModuleResolution flags.
*/
public ModuleResolution moduleResolution() {
return moduleResolution;
}
/**
* 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
*/
public byte[] computeHash(String algorithm) {
CachedHash ch = cachedHash;
if (ch != null && ch.algorithm().equals(algorithm)) {
return ch.hash();
}
if (hasher == null) {
return null;
}
byte[] hash = hasher.generate(algorithm);
cachedHash = new CachedHash(hash, algorithm);
return hash;
}
@Override
public int hashCode() {
int hc = hash;
if (hc == 0) {
hc = descriptor().hashCode();
hc = 43 * hc + Objects.hashCode(location);
hc = 43 * hc + Objects.hashCode(patcher);
if (hc == 0)
hc = -1;
hash = hc;
}
return hc;
}
private int hash;
@Override
public boolean equals(Object ob) {
if (!(ob instanceof ModuleReferenceImpl))
return false;
ModuleReferenceImpl that = (ModuleReferenceImpl)ob;
// assume module content, recorded hashes, etc. are the same
// when the modules have equal module descriptors, are at the
// same location, and are patched by the same patcher.
return Objects.equals(this.descriptor(), that.descriptor())
&& Objects.equals(this.location, that.location)
&& Objects.equals(this.patcher, that.patcher);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[module ");
sb.append(descriptor().name());
sb.append(", location=");
sb.append(location);
if (isPatched()) sb.append(" (patched)");
sb.append("]");
return sb.toString();
}
}

View file

@ -0,0 +1,433 @@
/*
* Copyright (c) 2015, 2024, 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;
import java.io.File;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;
import java.util.zip.ZipFile;
import jdk.internal.jmod.JmodFile;
import jdk.internal.module.ModuleHashes.HashSupplier;
import sun.net.www.ParseUtil;
/**
* A factory for creating ModuleReference implementations where the modules are
* packaged as modular JAR file, JMOD files or where the modules are exploded
* on the file system.
*/
class ModuleReferences {
private ModuleReferences() { }
/**
* Creates a ModuleReference to a possibly-patched module
*/
private static ModuleReference newModule(ModuleInfo.Attributes attrs,
URI uri,
Supplier<ModuleReader> supplier,
ModulePatcher patcher,
HashSupplier hasher) {
ModuleReference mref = new ModuleReferenceImpl(attrs.descriptor(),
uri,
supplier,
null,
attrs.target(),
attrs.recordedHashes(),
hasher,
attrs.moduleResolution());
if (patcher != null)
mref = patcher.patchIfNeeded(mref);
return mref;
}
/**
* Creates a ModuleReference to a possibly-patched module in a modular JAR.
*/
static ModuleReference newJarModule(ModuleInfo.Attributes attrs,
ModulePatcher patcher,
Path file) {
URI uri = file.toUri();
String fileString = file.toString();
Supplier<ModuleReader> supplier = new Supplier<>() {
@Override
public ModuleReader get() {
return new JarModuleReader(fileString, uri);
}
};
HashSupplier hasher = new HashSupplier() {
@Override
public byte[] generate(String algorithm) {
return ModuleHashes.computeHash(supplier, algorithm);
}
};
return newModule(attrs, uri, supplier, patcher, hasher);
}
/**
* Creates a ModuleReference to a module in a JMOD file.
*/
static ModuleReference newJModModule(ModuleInfo.Attributes attrs, Path file) {
URI uri = file.toUri();
Supplier<ModuleReader> supplier = () -> new JModModuleReader(file, uri);
HashSupplier hasher = (a) -> ModuleHashes.computeHash(supplier, a);
return newModule(attrs, uri, supplier, null, hasher);
}
/**
* Creates a ModuleReference to a possibly-patched exploded module.
*/
static ModuleReference newExplodedModule(ModuleInfo.Attributes attrs,
ModulePatcher patcher,
Path dir) {
Supplier<ModuleReader> supplier = () -> new ExplodedModuleReader(dir);
return newModule(attrs, dir.toUri(), supplier, patcher, null);
}
/**
* A base module reader that encapsulates machinery required to close the
* module reader safely.
*/
abstract static class SafeCloseModuleReader implements ModuleReader {
// RW lock to support safe close
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private boolean closed;
SafeCloseModuleReader() { }
/**
* Returns a URL to resource. This method is invoked by the find
* method to do the actual work of finding the resource.
*/
abstract Optional<URI> implFind(String name) throws IOException;
/**
* Returns an input stream for reading a resource. This method is
* invoked by the open method to do the actual work of opening
* an input stream to the resource.
*/
abstract Optional<InputStream> implOpen(String name) throws IOException;
/**
* Returns a stream of the names of resources in the module. This
* method is invoked by the list method to do the actual work of
* creating the stream.
*/
abstract Stream<String> implList() throws IOException;
/**
* Closes the module reader. This method is invoked by close to do the
* actual work of closing the module reader.
*/
abstract void implClose() throws IOException;
@Override
public final Optional<URI> find(String name) throws IOException {
readLock.lock();
try {
if (!closed) {
return implFind(name);
} else {
throw new IOException("ModuleReader is closed");
}
} finally {
readLock.unlock();
}
}
@Override
public final Optional<InputStream> open(String name) throws IOException {
readLock.lock();
try {
if (!closed) {
return implOpen(name);
} else {
throw new IOException("ModuleReader is closed");
}
} finally {
readLock.unlock();
}
}
@Override
public final Stream<String> list() throws IOException {
readLock.lock();
try {
if (!closed) {
return implList();
} else {
throw new IOException("ModuleReader is closed");
}
} finally {
readLock.unlock();
}
}
@Override
public final void close() throws IOException {
writeLock.lock();
try {
if (!closed) {
closed = true;
implClose();
}
} finally {
writeLock.unlock();
}
}
}
/**
* A ModuleReader for a modular JAR file.
*/
static class JarModuleReader extends SafeCloseModuleReader {
private final JarFile jf;
private final URI uri;
static JarFile newJarFile(String path) {
try {
return new JarFile(new File(path),
true, // verify
ZipFile.OPEN_READ,
JarFile.runtimeVersion());
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
JarModuleReader(String path, URI uri) {
this.jf = newJarFile(path);
this.uri = uri;
}
private JarEntry getEntry(String name) {
return jf.getJarEntry(Objects.requireNonNull(name));
}
@Override
Optional<URI> implFind(String name) throws IOException {
JarEntry je = getEntry(name);
if (je != null) {
if (jf.isMultiRelease())
name = je.getRealName();
if (je.isDirectory() && !name.endsWith("/"))
name += "/";
String encodedPath = ParseUtil.encodePath(name, false);
String uris = "jar:" + uri + "!/" + encodedPath;
return Optional.of(URI.create(uris));
} else {
return Optional.empty();
}
}
@Override
Optional<InputStream> implOpen(String name) throws IOException {
JarEntry je = getEntry(name);
if (je != null) {
return Optional.of(jf.getInputStream(je));
} else {
return Optional.empty();
}
}
@Override
Stream<String> implList() throws IOException {
// take snapshot to avoid async close
List<String> names = jf.versionedStream()
.map(JarEntry::getName)
.toList();
return names.stream();
}
@Override
void implClose() throws IOException {
jf.close();
}
}
/**
* A ModuleReader for a JMOD file.
*/
static class JModModuleReader extends SafeCloseModuleReader {
private final JmodFile jf;
private final URI uri;
static JmodFile newJmodFile(Path path) {
try {
return new JmodFile(path);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
JModModuleReader(Path path, URI uri) {
this.jf = newJmodFile(path);
this.uri = uri;
}
private JmodFile.Entry getEntry(String name) {
Objects.requireNonNull(name);
return jf.getEntry(JmodFile.Section.CLASSES, name);
}
@Override
Optional<URI> implFind(String name) {
JmodFile.Entry je = getEntry(name);
if (je != null) {
if (je.isDirectory() && !name.endsWith("/"))
name += "/";
String encodedPath = ParseUtil.encodePath(name, false);
String uris = "jmod:" + uri + "!/" + encodedPath;
return Optional.of(URI.create(uris));
} else {
return Optional.empty();
}
}
@Override
Optional<InputStream> implOpen(String name) throws IOException {
JmodFile.Entry je = getEntry(name);
if (je != null) {
return Optional.of(jf.getInputStream(je));
} else {
return Optional.empty();
}
}
@Override
Stream<String> implList() throws IOException {
// take snapshot to avoid async close
List<String> names = jf.stream()
.filter(e -> e.section() == JmodFile.Section.CLASSES)
.map(JmodFile.Entry::name)
.toList();
return names.stream();
}
@Override
void implClose() throws IOException {
jf.close();
}
}
/**
* A ModuleReader for an exploded module.
*/
static class ExplodedModuleReader implements ModuleReader {
private final Path dir;
private volatile boolean closed;
ExplodedModuleReader(Path dir) {
this.dir = dir;
}
/**
* Throws IOException if the module reader is closed;
*/
private void ensureOpen() throws IOException {
if (closed) throw new IOException("ModuleReader is closed");
}
@Override
public Optional<URI> find(String name) throws IOException {
ensureOpen();
Path path = Resources.toFilePath(dir, name);
if (path != null) {
try {
return Optional.of(path.toUri());
} catch (IOError e) {
throw (IOException) e.getCause();
}
} else {
return Optional.empty();
}
}
@Override
public Optional<InputStream> open(String name) throws IOException {
ensureOpen();
Path path = Resources.toFilePath(dir, name);
if (path != null) {
return Optional.of(Files.newInputStream(path));
} else {
return Optional.empty();
}
}
@Override
public Optional<ByteBuffer> read(String name) throws IOException {
ensureOpen();
Path path = Resources.toFilePath(dir, name);
if (path != null) {
return Optional.of(ByteBuffer.wrap(Files.readAllBytes(path)));
} else {
return Optional.empty();
}
}
@Override
public Stream<String> list() throws IOException {
ensureOpen();
return Files.walk(dir, Integer.MAX_VALUE)
.map(f -> Resources.toResourceName(dir, f))
.filter(s -> s.length() > 0);
}
@Override
public void close() {
closed = true;
}
}
}

View file

@ -0,0 +1,113 @@
/*
* 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;
import java.lang.module.ModuleReference;
import static jdk.internal.module.ClassFileConstants.*;
/**
* Represents the Module Resolution flags.
*/
public final class ModuleResolution {
final int value;
ModuleResolution(int value) {
this.value = value;
}
public int value() {
return value;
}
public static ModuleResolution empty() {
return new ModuleResolution(0);
}
public boolean doNotResolveByDefault() {
return (value & DO_NOT_RESOLVE_BY_DEFAULT) != 0;
}
public boolean hasDeprecatedWarning() {
return (value & WARN_DEPRECATED) != 0;
}
public boolean hasDeprecatedForRemovalWarning() {
return (value & WARN_DEPRECATED_FOR_REMOVAL) != 0;
}
public boolean hasIncubatingWarning() {
return (value & WARN_INCUBATING) != 0;
}
public ModuleResolution withDoNotResolveByDefault() {
return new ModuleResolution(value | DO_NOT_RESOLVE_BY_DEFAULT);
}
public ModuleResolution withDeprecated() {
if ((value & (WARN_DEPRECATED_FOR_REMOVAL | WARN_INCUBATING)) != 0)
throw new InternalError("cannot add deprecated to " + value);
return new ModuleResolution(value | WARN_DEPRECATED);
}
public ModuleResolution withDeprecatedForRemoval() {
if ((value & (WARN_DEPRECATED | WARN_INCUBATING)) != 0)
throw new InternalError("cannot add deprecated for removal to " + value);
return new ModuleResolution(value | WARN_DEPRECATED_FOR_REMOVAL);
}
public ModuleResolution withIncubating() {
if ((value & (WARN_DEPRECATED | WARN_DEPRECATED_FOR_REMOVAL)) != 0)
throw new InternalError("cannot add incubating to " + value);
return new ModuleResolution(value | WARN_INCUBATING);
}
public static boolean doNotResolveByDefault(ModuleReference mref) {
// get the DO_NOT_RESOLVE_BY_DEFAULT flag, if any
if (mref instanceof ModuleReferenceImpl) {
ModuleResolution mres = ((ModuleReferenceImpl) mref).moduleResolution();
if (mres != null)
return mres.doNotResolveByDefault();
}
return false;
}
public static boolean hasIncubatingWarning(ModuleReference mref) {
if (mref instanceof ModuleReferenceImpl) {
ModuleResolution mres = ((ModuleReferenceImpl) mref).moduleResolution();
if (mres != null)
return mres.hasIncubatingWarning();
}
return false;
}
@Override
public String toString() {
return super.toString() + "[value=" + value + "]";
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2017, 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 module target.
*
* For now, this is a single value for the target platform, e.g. "linux-x64".
*/
public final class ModuleTarget {
private final String targetPlatform;
public ModuleTarget(String targetPlatform) {
this.targetPlatform = targetPlatform;
}
public String targetPlatform() {
return targetPlatform;
}
}

View file

@ -0,0 +1,332 @@
/*
* Copyright (c) 2015, 2025, 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;
import java.io.PrintStream;
import java.lang.module.Configuration;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.lang.module.ResolvedModule;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import jdk.internal.access.JavaLangModuleAccess;
import jdk.internal.loader.BootLoader;
import jdk.internal.loader.BuiltinClassLoader;
import jdk.internal.loader.ClassLoaders;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
/**
* A helper class for creating and updating modules. This class is intended to
* support command-line options, tests, and the instrumentation API. It is also
* used by the VM to load modules or add read edges when agents are instrumenting
* code that need to link to supporting classes.
*
* The parameters that are package names in this API are the fully-qualified
* names of the packages as defined in section 6.5.3 of <cite>The Java
* Language Specification </cite>, for example, {@code "java.lang"}.
*/
public class Modules {
private Modules() { }
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
private static final JavaLangModuleAccess JLMA = SharedSecrets.getJavaLangModuleAccess();
/**
* Creates a new Module. The module has the given ModuleDescriptor and
* is defined to the given class loader.
*
* The resulting Module is in a larval state in that it does not read
* any other module and does not have any exports.
*
* The URI is for information purposes only.
*/
public static Module defineModule(ClassLoader loader,
ModuleDescriptor descriptor,
URI uri)
{
return JLA.defineModule(loader, descriptor, uri);
}
/**
* Updates m1 to read m2.
* Same as m1.addReads(m2) but without a caller check.
*/
public static void addReads(Module m1, Module m2) {
JLA.addReads(m1, m2);
}
/**
* Update module m to read all unnamed modules.
*/
public static void addReadsAllUnnamed(Module m) {
JLA.addReadsAllUnnamed(m);
}
/**
* Updates module m1 to export a package to module m2.
* Same as m1.addExports(pn, m2) but without a caller check
*/
public static void addExports(Module m1, String pn, Module m2) {
JLA.addExports(m1, pn, m2);
}
/**
* Updates module m to export a package unconditionally.
*/
public static void addExports(Module m, String pn) {
JLA.addExports(m, pn);
}
/**
* Updates module m to export a package to all unnamed modules.
*/
public static void addExportsToAllUnnamed(Module m, String pn) {
JLA.addExportsToAllUnnamed(m, pn);
}
/**
* Updates module m1 to open a package to module m2.
* Same as m1.addOpens(pn, m2) but without a caller check.
*/
public static void addOpens(Module m1, String pn, Module m2) {
JLA.addOpens(m1, pn, m2);
}
/**
* Updates module m to open a package to all unnamed modules.
*/
public static void addOpensToAllUnnamed(Module m, String pn) {
JLA.addOpensToAllUnnamed(m, pn);
}
/**
* Adds native access to all unnamed modules.
*/
public static void addEnableNativeAccessToAllUnnamed() {
JLA.addEnableNativeAccessToAllUnnamed();
}
/**
* Enable code in all unnamed modules to mutate final instance fields.
*/
public static void addEnableFinalMutationToAllUnnamed() {
JLA.addEnableFinalMutationToAllUnnamed();
}
/**
* Enable code in a given module to mutate final instance fields.
*/
public static boolean tryEnableFinalMutation(Module m) {
return JLA.tryEnableFinalMutation(m);
}
/**
* Return true if code in a given module is allowed to mutate final instance fields.
*/
public static boolean isFinalMutationEnabled(Module m) {
return JLA.isFinalMutationEnabled(m);
}
/**
* Return true if a given module has statically exported the given package to a given
* other module. "statically exported" means the module declaration, --add-exports on
* the command line, or Add-Exports in the main manifest of an executable JAR.
*/
public static boolean isStaticallyExported(Module m, String pn, Module other) {
return JLA.isStaticallyExported(m, pn, other);
}
/**
* Return true if a given module has statically opened the given package to a given
* other module. "statically open" means the module declaration, --add-opens on the
* command line, or Add-Opens in the main manifest of an executable JAR.
*/
public static boolean isStaticallyOpened(Module m, String pn, Module other) {
return JLA.isStaticallyOpened(m, pn, other);
}
/**
* Updates module m to use a service.
* Same as m2.addUses(service) but without a caller check.
*/
public static void addUses(Module m, Class<?> service) {
JLA.addUses(m, service);
}
/**
* Updates module m to provide a service
*/
public static void addProvides(Module m, Class<?> service, Class<?> impl) {
ModuleLayer layer = m.getLayer();
ClassLoader loader = m.getClassLoader();
ClassLoader platformClassLoader = ClassLoaders.platformClassLoader();
if (layer == null || loader == null || loader == platformClassLoader) {
// update ClassLoader catalog
ServicesCatalog catalog;
if (loader == null) {
catalog = BootLoader.getServicesCatalog();
} else {
catalog = ServicesCatalog.getServicesCatalog(loader);
}
catalog.addProvider(m, service, impl);
}
if (layer != null) {
// update Layer catalog
JLA.getServicesCatalog(layer).addProvider(m, service, impl);
}
}
/**
* Resolves a collection of root modules, with service binding and the empty
* Configuration as the parent to create a Configuration for the boot layer.
*
* This method is intended to be used to create the Configuration for the
* boot layer during startup or at a link-time.
*/
public static Configuration newBootLayerConfiguration(ModuleFinder finder,
Collection<String> roots,
PrintStream traceOutput)
{
return JLMA.resolveAndBind(finder, roots, traceOutput);
}
/**
* Called by the VM when code in the given Module has been transformed by
* an agent and so may have been instrumented to call into supporting
* classes on the boot class path or application class path.
*/
public static void transformedByAgent(Module m) {
addReads(m, BootLoader.getUnnamedModule());
addReads(m, ClassLoaders.appClassLoader().getUnnamedModule());
}
/**
* Called by the VM to load a system module, typically "java.instrument" or
* "jdk.management.agent". If the module is not loaded then it is resolved
* and loaded (along with any dependences that weren't previously loaded)
* into a child layer.
*/
public static synchronized Module loadModule(String name) {
ModuleLayer top = topLayer;
if (top == null)
top = ModuleLayer.boot();
Module module = top.findModule(name).orElse(null);
if (module != null) {
// module already loaded
return module;
}
// resolve the module with the top-most layer as the parent
ModuleFinder empty = ModuleFinder.of();
ModuleFinder finder = ModuleBootstrap.unlimitedFinder();
Set<String> roots = Set.of(name);
Configuration cf = top.configuration().resolveAndBind(empty, finder, roots);
// create the child layer
Function<String, ClassLoader> clf = ModuleLoaderMap.mappingFunction(cf);
ModuleLayer newLayer = top.defineModules(cf, clf);
// add qualified exports/opens to give access to modules in child layer
Map<String, Module> map = newLayer.modules().stream()
.collect(Collectors.toMap(Module::getName,
Function.identity()));
ModuleLayer layer = top;
while (layer != null) {
for (Module m : layer.modules()) {
// qualified exports
m.getDescriptor().exports().stream()
.filter(ModuleDescriptor.Exports::isQualified)
.forEach(e -> e.targets().forEach(target -> {
Module other = map.get(target);
if (other != null) {
addExports(m, e.source(), other);
}}));
// qualified opens
m.getDescriptor().opens().stream()
.filter(ModuleDescriptor.Opens::isQualified)
.forEach(o -> o.targets().forEach(target -> {
Module other = map.get(target);
if (other != null) {
addOpens(m, o.source(), other);
}}));
}
List<ModuleLayer> parents = layer.parents();
assert parents.size() <= 1;
layer = parents.isEmpty() ? null : parents.get(0);
}
// update the built-in class loaders to make the types visible
for (ResolvedModule resolvedModule : cf.modules()) {
ModuleReference mref = resolvedModule.reference();
String mn = mref.descriptor().name();
ClassLoader cl = clf.apply(mn);
if (cl == null) {
BootLoader.loadModule(mref);
} else {
((BuiltinClassLoader) cl).loadModule(mref);
}
}
// new top layer
topLayer = newLayer;
// return module
return newLayer.findModule(name)
.orElseThrow(() -> new InternalError("module not loaded"));
}
/**
* Finds the module with the given name in the boot layer or any child
* layers created to load the "java.instrument" or "jdk.management.agent"
* modules into a running VM.
*/
public static Optional<Module> findLoadedModule(String name) {
ModuleLayer top = topLayer;
if (top == null)
top = ModuleLayer.boot();
return top.findModule(name);
}
// the top-most layer
private static volatile ModuleLayer topLayer;
}

View file

@ -0,0 +1,177 @@
/*
* Copyright (c) 2016, 2023, 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;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
/**
* A helper class to support working with resources in modules. Also provides
* support for translating resource names to file paths.
*/
public final class Resources {
private Resources() { }
/**
* Return true if a resource can be encapsulated. Resource with names
* ending in ".class" or "/" cannot be encapsulated. Resource names
* that map to a legal package name can be encapsulated.
*/
public static boolean canEncapsulate(String name) {
int len = name.length();
if (len > 6 && name.endsWith(".class")) {
return false;
} else {
return Checks.isPackageName(toPackageName(name));
}
}
/**
* Derive a <em>package name</em> for a resource. The package name
* returned by this method may not be a legal package name. This method
* returns null if the resource name ends with a "/" (a directory)
* or the resource name does not contain a "/".
*/
public static String toPackageName(String name) {
int index = name.lastIndexOf('/');
if (index == -1 || index == name.length()-1) {
return "";
} else {
return name.substring(0, index).replace('/', '.');
}
}
/**
* Returns a resource name corresponding to the relative file path
* between {@code dir} and {@code file}. If the file is a directory
* then the name will end with a "/", except the top-level directory
* where the empty string is returned.
*/
public static String toResourceName(Path dir, Path file) {
String s = dir.relativize(file)
.toString()
.replace(File.separatorChar, '/');
if (!s.isEmpty() && Files.isDirectory(file))
s += "/";
return s;
}
/**
* Returns a file path to a resource in a file tree. If the resource
* name has a trailing "/" then the file path will locate a directory.
* Returns {@code null} if the resource does not map to a file in the
* tree file.
*/
public static Path toFilePath(Path dir, String name) throws IOException {
boolean expectDirectory = name.endsWith("/");
if (expectDirectory) {
name = name.substring(0, name.length() - 1); // drop trailing "/"
}
Path path = toSafeFilePath(dir.getFileSystem(), name);
if (path != null) {
Path file = dir.resolve(path);
try {
BasicFileAttributes attrs;
attrs = Files.readAttributes(file, BasicFileAttributes.class);
if (attrs.isDirectory()
|| (!attrs.isDirectory() && !expectDirectory))
return file;
} catch (NoSuchFileException ignore) { }
}
return null;
}
/**
* Map a resource name to a "safe" file path. Returns {@code null} if
* the resource name cannot be converted into a "safe" file path.
*
* Resource names with empty elements, or elements that are "." or ".."
* are rejected, as are resource names that translates to a file path
* with a root component.
*/
private static Path toSafeFilePath(FileSystem fs, String name) {
// scan elements of resource name
int next;
int off = 0;
while ((next = name.indexOf('/', off)) != -1) {
int len = next - off;
if (!mayTranslate(name, off, len)) {
return null;
}
off = next + 1;
}
int rem = name.length() - off;
if (!mayTranslate(name, off, rem)) {
return null;
}
// map resource name to a file path string
String pathString;
if (File.separatorChar == '/') {
pathString = name;
} else {
// not allowed to embed file separators
if (name.contains(File.separator))
return null;
pathString = name.replace('/', File.separatorChar);
}
// try to convert to a Path
Path path;
try {
path = fs.getPath(pathString);
} catch (InvalidPathException e) {
// not a valid file path
return null;
}
// file path not allowed to have root component
return (path.getRoot() == null) ? path : null;
}
/**
* Returns {@code true} if the element in a resource name is a candidate
* to translate to the element of a file path.
*/
private static boolean mayTranslate(String name, int off, int len) {
if (len <= 2) {
if (len == 0)
return false;
boolean starsWithDot = (name.charAt(off) == '.');
if (len == 1 && starsWithDot)
return false;
if (len == 2 && starsWithDot && (name.charAt(off+1) == '.'))
return false;
}
return true;
}
}

View file

@ -0,0 +1,186 @@
/*
* Copyright (c) 2014, 2020, 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;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Provides;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import jdk.internal.loader.ClassLoaderValue;
/**
* A <em>services catalog</em>. Each {@code ClassLoader} and {@code Layer} has
* an optional {@code ServicesCatalog} for modules that provide services.
*
* @apiNote This class will be replaced once the ServiceLoader is further
* specified
*/
public final class ServicesCatalog {
/**
* Represents a service provider in the services catalog.
*/
public static final class ServiceProvider {
private final Module module;
private final String providerName;
public ServiceProvider(Module module, String providerName) {
this.module = module;
this.providerName = providerName;
}
public Module module() {
return module;
}
public String providerName() {
return providerName;
}
@Override
public int hashCode() {
return Objects.hash(module, providerName);
}
@Override
public boolean equals(Object ob) {
if (!(ob instanceof ServiceProvider))
return false;
ServiceProvider that = (ServiceProvider)ob;
return Objects.equals(this.module, that.module)
&& Objects.equals(this.providerName, that.providerName);
}
}
// service name -> list of providers
private final Map<String, List<ServiceProvider>> map = new ConcurrentHashMap<>(32);
private ServicesCatalog() { }
/**
* Creates a ServicesCatalog that supports concurrent registration
* and lookup
*/
public static ServicesCatalog create() {
return new ServicesCatalog();
}
/**
* Adds service providers for the given service type.
*/
private void addProviders(String service, ServiceProvider ... providers) {
List<ServiceProvider> list = map.get(service);
if (list == null) {
list = new CopyOnWriteArrayList<>(providers);
List<ServiceProvider> prev = map.putIfAbsent(service, list);
if (prev != null) {
// someone else got there
prev.addAll(list);
}
} else {
if (providers.length == 1) {
list.add(providers[0]);
} else {
list.addAll(Arrays.asList(providers));
}
}
}
/**
* Registers the providers in the given module in this services catalog.
*/
public void register(Module module) {
ModuleDescriptor descriptor = module.getDescriptor();
for (Provides provides : descriptor.provides()) {
String service = provides.service();
List<String> providerNames = provides.providers();
int count = providerNames.size();
ServiceProvider[] providers = new ServiceProvider[count];
for (int i = 0; i < count; i++) {
providers[i] = new ServiceProvider(module, providerNames.get(i));
}
addProviders(service, providers);
}
}
/**
* Adds a provider in the given module to this services catalog.
*
* @apiNote This method is for use by java.lang.instrument
*/
public void addProvider(Module module, Class<?> service, Class<?> impl) {
addProviders(service.getName(), new ServiceProvider(module, impl.getName()));
}
/**
* Returns the (possibly empty) list of service providers that implement
* the given service type.
*/
public List<ServiceProvider> findServices(String service) {
return map.getOrDefault(service, List.of());
}
/**
* Returns the ServicesCatalog for the given class loader or {@code null}
* if there is none.
*/
public static ServicesCatalog getServicesCatalogOrNull(ClassLoader loader) {
return CLV.get(loader);
}
/**
* Returns the ServicesCatalog for the given class loader, creating it if
* needed.
*/
public static ServicesCatalog getServicesCatalog(ClassLoader loader) {
// CLV.computeIfAbsent(loader, (cl, clv) -> create());
ServicesCatalog catalog = CLV.get(loader);
if (catalog == null) {
catalog = create();
ServicesCatalog previous = CLV.putIfAbsent(loader, catalog);
if (previous != null) catalog = previous;
}
return catalog;
}
/**
* Associates the given ServicesCatalog with the given class loader.
*/
public static void putServicesCatalog(ClassLoader loader, ServicesCatalog catalog) {
ServicesCatalog previous = CLV.putIfAbsent(loader, catalog);
if (previous != null) {
throw new InternalError();
}
}
// the ServicesCatalog registered to a class loader
private static final ClassLoaderValue<ServicesCatalog> CLV = new ClassLoaderValue<>();
}

View file

@ -0,0 +1,565 @@
/*
* Copyright (c) 2015, 2026, 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;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import jdk.internal.jimage.ImageReader;
import jdk.internal.jimage.ImageReaderFactory;
import jdk.internal.access.JavaNetUriAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.util.StaticProperty;
import jdk.internal.module.ModuleHashes.HashSupplier;
/**
* The factory for SystemModules objects and for creating ModuleFinder objects
* that find modules in the runtime image.
*
* This class supports initializing the module system when the runtime is an
* images build, an exploded build, or an images build with java.base patched
* by an exploded java.base. It also supports a testing mode that re-parses
* the module-info.class resources in the run-time image.
*/
public final class SystemModuleFinders {
private static final JavaNetUriAccess JNUA = SharedSecrets.getJavaNetUriAccess();
private static final boolean USE_FAST_PATH;
static {
String value = System.getProperty("jdk.system.module.finder.disableFastPath");
if (value == null) {
USE_FAST_PATH = true;
} else {
USE_FAST_PATH = !value.isEmpty() && !Boolean.parseBoolean(value);
}
}
// cached ModuleFinder returned from ofSystem
private static volatile ModuleFinder cachedSystemModuleFinder;
private SystemModuleFinders() { }
/**
* Returns the SystemModules object to reconstitute all modules. Returns
* null if this is an exploded build or java.base is patched by an exploded
* build.
*/
static SystemModules allSystemModules() {
if (USE_FAST_PATH) {
return SystemModulesMap.allSystemModules();
} else {
return null;
}
}
/**
* Returns a SystemModules object to reconstitute the modules for the
* given initial module. If the initial module is null then return the
* SystemModules object to reconstitute the default modules.
*
* Return null if there is no SystemModules class for the initial module,
* this is an exploded build, or java.base is patched by an exploded build.
*/
static SystemModules systemModules(String initialModule) {
if (USE_FAST_PATH) {
if (initialModule == null) {
return SystemModulesMap.defaultSystemModules();
}
String[] initialModules = SystemModulesMap.moduleNames();
for (int i = 0; i < initialModules.length; i++) {
String moduleName = initialModules[i];
if (initialModule.equals(moduleName)) {
String cn = SystemModulesMap.classNames()[i];
try {
// one-arg Class.forName as java.base may not be defined
Constructor<?> ctor = Class.forName(cn).getConstructor();
return (SystemModules) ctor.newInstance();
} catch (Exception e) {
throw new InternalError(e);
}
}
}
}
return null;
}
/**
* Returns a ModuleFinder that is backed by the given SystemModules object.
*
* @apiNote The returned ModuleFinder is thread safe.
*/
static ModuleFinder of(SystemModules systemModules) {
ModuleDescriptor[] descriptors = systemModules.moduleDescriptors();
ModuleTarget[] targets = systemModules.moduleTargets();
ModuleHashes[] recordedHashes = systemModules.moduleHashes();
ModuleResolution[] moduleResolutions = systemModules.moduleResolutions();
int moduleCount = descriptors.length;
ModuleReference[] mrefs = new ModuleReference[moduleCount];
@SuppressWarnings(value = {"rawtypes", "unchecked"})
Map.Entry<String, ModuleReference>[] map
= (Map.Entry<String, ModuleReference>[])new Map.Entry[moduleCount];
Map<String, byte[]> nameToHash = generateNameToHash(recordedHashes);
for (int i = 0; i < moduleCount; i++) {
String name = descriptors[i].name();
HashSupplier hashSupplier = hashSupplier(nameToHash, name);
ModuleReference mref = toModuleReference(descriptors[i],
targets[i],
recordedHashes[i],
hashSupplier,
moduleResolutions[i]);
mrefs[i] = mref;
map[i] = Map.entry(name, mref);
}
return new SystemModuleFinder(mrefs, map);
}
/**
* Returns the ModuleFinder to find all system modules. Supports both
* images and exploded builds.
*
* @apiNote Used by ModuleFinder.ofSystem()
*/
public static ModuleFinder ofSystem() {
ModuleFinder finder = cachedSystemModuleFinder;
if (finder != null) {
return finder;
}
// probe to see if this is an images build
String home = StaticProperty.javaHome();
Path modules = Path.of(home, "lib", "modules");
if (Files.isRegularFile(modules)) {
if (USE_FAST_PATH) {
SystemModules systemModules = allSystemModules();
if (systemModules != null) {
finder = of(systemModules);
}
}
// fall back to parsing the module-info.class files in image
if (finder == null) {
finder = ofModuleInfos();
}
cachedSystemModuleFinder = finder;
return finder;
}
// exploded build (do not cache module finder)
Path dir = Path.of(home, "modules");
if (!Files.isDirectory(dir))
throw new InternalError("Unable to detect the run-time image");
return ModulePath.of(ModuleBootstrap.patcher(), dir);
}
/**
* Parses the {@code module-info.class} of all modules in the runtime image and
* returns a ModuleFinder to find the modules.
*
* @apiNote The returned ModuleFinder is thread safe.
*/
private static ModuleFinder ofModuleInfos() {
// parse the module-info.class in every module
Map<String, ModuleInfo.Attributes> nameToAttributes = new HashMap<>();
Map<String, byte[]> nameToHash = new HashMap<>();
allModuleAttributes().forEach(attrs -> {
nameToAttributes.put(attrs.descriptor().name(), attrs);
ModuleHashes hashes = attrs.recordedHashes();
if (hashes != null) {
for (String name : hashes.names()) {
nameToHash.computeIfAbsent(name, k -> hashes.hashFor(name));
}
}
});
// create a ModuleReference for each module
Set<ModuleReference> mrefs = new HashSet<>();
Map<String, ModuleReference> nameToModule = new HashMap<>();
for (Map.Entry<String, ModuleInfo.Attributes> e : nameToAttributes.entrySet()) {
String mn = e.getKey();
ModuleInfo.Attributes attrs = e.getValue();
HashSupplier hashSupplier = hashSupplier(nameToHash, mn);
ModuleReference mref = toModuleReference(attrs.descriptor(),
attrs.target(),
attrs.recordedHashes(),
hashSupplier,
attrs.moduleResolution());
mrefs.add(mref);
nameToModule.put(mn, mref);
}
return new SystemModuleFinder(mrefs, nameToModule);
}
/**
* Parses the {@code module-info.class} of all modules in the runtime image and
* returns a stream of {@link ModuleInfo.Attributes Attributes} for them. The
* returned attributes are in no specific order.
*/
private static Stream<ModuleInfo.Attributes> allModuleAttributes() {
// System-wide image reader.
ImageReader reader = SystemImage.reader();
try {
return reader.findNode("/modules")
.getChildNames()
.map(mn -> readModuleAttributes(reader, mn));
} catch (IOException e) {
throw new Error("Error reading root /modules entry", e);
}
}
/**
* Returns the module's "module-info", returning a holder for its class file
* attributes. Every module is required to have a valid {@code module-info.class}.
*/
private static ModuleInfo.Attributes readModuleAttributes(ImageReader reader, String moduleName) {
Exception err = null;
try {
ImageReader.Node node = reader.findNode(moduleName + "/module-info.class");
if (node != null && node.isResource()) {
return ModuleInfo.read(reader.getResourceBuffer(node), null);
}
} catch (IOException | UncheckedIOException e) {
err = e;
}
throw new Error("Missing or invalid module-info.class for module: " + moduleName, err);
}
/**
* A ModuleFinder that finds module in an array or set of modules.
*/
private static class SystemModuleFinder implements ModuleFinder {
final Set<ModuleReference> mrefs;
final Map<String, ModuleReference> nameToModule;
SystemModuleFinder(ModuleReference[] array,
Map.Entry<String, ModuleReference>[] map) {
this.mrefs = Set.of(array);
this.nameToModule = Map.ofEntries(map);
}
SystemModuleFinder(Set<ModuleReference> mrefs,
Map<String, ModuleReference> nameToModule) {
this.mrefs = Set.copyOf(mrefs);
this.nameToModule = Map.copyOf(nameToModule);
}
@Override
public Optional<ModuleReference> find(String name) {
Objects.requireNonNull(name);
return Optional.ofNullable(nameToModule.get(name));
}
@Override
public Set<ModuleReference> findAll() {
return mrefs;
}
}
/**
* Creates a ModuleReference to the system module.
*/
static ModuleReference toModuleReference(ModuleDescriptor descriptor,
ModuleTarget target,
ModuleHashes recordedHashes,
HashSupplier hasher,
ModuleResolution mres) {
String mn = descriptor.name();
URI uri = JNUA.create("jrt", "/".concat(mn));
Supplier<ModuleReader> readerSupplier = new Supplier<>() {
@Override
public ModuleReader get() {
return new SystemModuleReader(mn);
}
};
ModuleReference mref = new ModuleReferenceImpl(descriptor,
uri,
readerSupplier,
null,
target,
recordedHashes,
hasher,
mres);
// may need a reference to a patched module if --patch-module specified
mref = ModuleBootstrap.patcher().patchIfNeeded(mref);
return mref;
}
/**
* Generates a map of module name to hash value.
*/
static Map<String, byte[]> generateNameToHash(ModuleHashes[] recordedHashes) {
Map<String, byte[]> nameToHash = null;
boolean secondSeen = false;
// record the hashes to build HashSupplier
for (ModuleHashes mh : recordedHashes) {
if (mh != null) {
// if only one module contain ModuleHashes, use it
if (nameToHash == null) {
nameToHash = mh.hashes();
} else {
if (!secondSeen) {
nameToHash = new HashMap<>(nameToHash);
secondSeen = true;
}
nameToHash.putAll(mh.hashes());
}
}
}
return (nameToHash != null) ? nameToHash : Map.of();
}
/**
* Returns a HashSupplier that returns the hash of the given module.
*/
static HashSupplier hashSupplier(Map<String, byte[]> nameToHash, String name) {
byte[] hash = nameToHash.get(name);
if (hash != null) {
// avoid lambda here
return new HashSupplier() {
@Override
public byte[] generate(String algorithm) {
return hash;
}
};
} else {
return null;
}
}
/**
* Holder class for the ImageReader.
*/
private static class SystemImage {
static final ImageReader READER = ImageReaderFactory.getImageReader();
static ImageReader reader() {
return READER;
}
}
/**
* A ModuleReader for reading resources from a module linked into the
* run-time image.
*/
private static class SystemModuleReader implements ModuleReader {
private final String module;
private volatile boolean closed;
SystemModuleReader(String module) {
this.module = module;
}
/**
* Returns {@code true} if the given resource exists, {@code false}
* if not found.
*/
private boolean containsResource(String module, String name) throws IOException {
Objects.requireNonNull(name);
if (closed)
throw new IOException("ModuleReader is closed");
ImageReader imageReader = SystemImage.reader();
return imageReader != null && imageReader.containsResource(module, name);
}
@Override
public Optional<URI> find(String name) throws IOException {
if (containsResource(module, name)) {
URI u = JNUA.create("jrt", "/" + module + "/" + name);
return Optional.of(u);
} else {
return Optional.empty();
}
}
@Override
public Optional<InputStream> open(String name) throws IOException {
return read(name).map(this::toInputStream);
}
private InputStream toInputStream(ByteBuffer bb) { // ## -> ByteBuffer?
try {
int rem = bb.remaining();
byte[] bytes = new byte[rem];
bb.get(bytes);
return new ByteArrayInputStream(bytes);
} finally {
release(bb);
}
}
/**
* Returns the node for the given resource if found. If the name references
* a non-resource node, then {@code null} is returned.
*/
private ImageReader.Node findResource(ImageReader reader, String name) throws IOException {
Objects.requireNonNull(name);
if (closed) {
throw new IOException("ModuleReader is closed");
}
return reader.findResourceNode(module, name);
}
@Override
public Optional<ByteBuffer> read(String name) throws IOException {
ImageReader reader = SystemImage.reader();
return Optional.ofNullable(findResource(reader, name))
.map(reader::getResourceBuffer);
}
@Override
public Stream<String> list() throws IOException {
if (closed)
throw new IOException("ModuleReader is closed");
Spliterator<String> s = new ModuleContentSpliterator(module);
return StreamSupport.stream(s, false);
}
@Override
public void close() {
// nothing else to do
closed = true;
}
}
/**
* A Spliterator for traversing the resources of a module linked into the
* run-time image.
*/
private static class ModuleContentSpliterator implements Spliterator<String> {
final String moduleRoot;
final Deque<ImageReader.Node> stack;
Iterator<String> iterator;
ModuleContentSpliterator(String module) throws IOException {
moduleRoot = "/modules/" + module;
stack = new ArrayDeque<>();
// push the root node to the stack to get started
ImageReader.Node dir = SystemImage.reader().findNode(moduleRoot);
if (dir == null || !dir.isDirectory())
throw new IOException(moduleRoot + " not a directory");
stack.push(dir);
iterator = Collections.emptyIterator();
}
/**
* Returns the name of the next non-directory node or {@code null} if
* there are no remaining nodes to visit.
*/
private String next() throws IOException {
for (;;) {
while (iterator.hasNext()) {
String name = iterator.next();
ImageReader.Node node = SystemImage.reader().findNode(name);
if (node.isDirectory()) {
stack.push(node);
} else {
// strip /modules/$MODULE/ prefix
return name.substring(moduleRoot.length() + 1);
}
}
if (stack.isEmpty()) {
return null;
} else {
ImageReader.Node dir = stack.poll();
assert dir.isDirectory();
iterator = dir.getChildNames().iterator();
}
}
}
@Override
public boolean tryAdvance(Consumer<? super String> action) {
String next;
try {
next = next();
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
if (next != null) {
action.accept(next);
return true;
} else {
return false;
}
}
@Override
public Spliterator<String> trySplit() {
return null;
}
@Override
public int characteristics() {
return Spliterator.DISTINCT + Spliterator.NONNULL + Spliterator.IMMUTABLE;
}
@Override
public long estimateSize() {
return Long.MAX_VALUE;
}
}
}

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2015, 2021, 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;
import java.lang.module.ModuleDescriptor;
import java.util.Map;
import java.util.Set;
/**
* A SystemModules object reconstitutes module descriptors and other modules
* attributes in an efficient way to avoid parsing module-info.class files at
* startup. Implementations of this class are generated by the "system modules"
* jlink plugin.
*
* @see SystemModuleFinders
* @see jdk.tools.jlink.internal.plugins.SystemModulesPlugin
*/
interface SystemModules {
/**
* Returns false if the module reconstituted by this SystemModules object
* have no overlapping packages. Returns true if there are overlapping
* packages or unknown.
*/
boolean hasSplitPackages();
/**
* Return false if the modules reconstituted by this SystemModules object
* do not include any incubator modules. Returns true if there are
* incubating modules or unknown.
*/
boolean hasIncubatorModules();
/**
* Returns the non-empty array of ModuleDescriptor objects.
*/
ModuleDescriptor[] moduleDescriptors();
/**
* Returns the array of ModuleTarget objects. The array elements correspond
* to the array of ModuleDescriptor objects.
*/
ModuleTarget[] moduleTargets();
/**
* Returns the array of ModuleHashes objects. The array elements correspond
* to the array of ModuleDescriptor objects.
*/
ModuleHashes[] moduleHashes();
/**
* Returns the array of ModuleResolution objects. The array elements correspond
* to the array of ModuleDescriptor objects.
*/
ModuleResolution[] moduleResolutions();
/**
* Returns the map representing readability graph for the modules reconstituted
* by this SystemModules object.
*/
Map<String, Set<String>> moduleReads();
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2017, 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;
/**
* This class is generated/overridden at link time to return the names of the
* SystemModules classes generated at link time.
*
* @see SystemModuleFinders
* @see jdk.tools.jlink.internal.plugins.SystemModulesPlugin
*/
class SystemModulesMap {
/**
* Returns the SystemModules object to reconstitute all modules or null
* if this is an exploded build.
*/
static SystemModules allSystemModules() {
return null;
}
/**
* Returns the SystemModules object to reconstitute default modules or null
* if this is an exploded build.
*/
static SystemModules defaultSystemModules() {
return null;
}
/**
* Returns the array of initial module names identified at link time.
*/
static String[] moduleNames() {
return new String[0];
}
/**
* Returns the array of SystemModules class names. The elements
* correspond to the elements in the array returned by moduleNames().
*/
static String[] classNames() {
return new String[0];
}
}

View file

@ -0,0 +1,408 @@
/*
* Copyright (c) 2014, 2025, 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.
*/
/**
* Defines the foundational APIs of the Java SE Platform.
*
* <dl class="notes">
* <dt>Providers:</dt>
* <dd> The JDK implementation of this module provides an implementation of
* the {@index jrt jrt} {@linkplain java.nio.file.spi.FileSystemProvider
* file system provider} to enumerate and read the class and resource
* files in a run-time image.
* The jrt file system can be created by calling
* {@link java.nio.file.FileSystems#getFileSystem
* FileSystems.getFileSystem(URI.create("jrt:/"))}.
* </dd>
* </dl>
*
* @toolGuide java java launcher
* @toolGuide keytool
*
* @provides java.nio.file.spi.FileSystemProvider
*
* @uses java.lang.System.LoggerFinder
* @uses java.net.ContentHandlerFactory
* @uses java.net.spi.URLStreamHandlerProvider
* @uses java.nio.channels.spi.AsynchronousChannelProvider
* @uses java.nio.channels.spi.SelectorProvider
* @uses java.nio.charset.spi.CharsetProvider
* @uses java.nio.file.spi.FileSystemProvider
* @uses java.nio.file.spi.FileTypeDetector
* @uses java.security.Provider
* @uses java.text.spi.BreakIteratorProvider
* @uses java.text.spi.CollatorProvider
* @uses java.text.spi.DateFormatProvider
* @uses java.text.spi.DateFormatSymbolsProvider
* @uses java.text.spi.DecimalFormatSymbolsProvider
* @uses java.text.spi.NumberFormatProvider
* @uses java.time.chrono.AbstractChronology
* @uses java.time.chrono.Chronology
* @uses java.time.zone.ZoneRulesProvider
* @uses java.util.spi.CalendarDataProvider
* @uses java.util.spi.CalendarNameProvider
* @uses java.util.spi.CurrencyNameProvider
* @uses java.util.spi.LocaleNameProvider
* @uses java.util.spi.ResourceBundleControlProvider
* @uses java.util.spi.ResourceBundleProvider
* @uses java.util.spi.TimeZoneNameProvider
* @uses java.util.spi.ToolProvider
* @uses javax.security.auth.spi.LoginModule
*
* @moduleGraph
* @since 9
*/
module java.base {
exports java.io;
exports java.lang;
exports java.lang.annotation;
exports java.lang.classfile;
exports java.lang.classfile.attribute;
exports java.lang.classfile.constantpool;
exports java.lang.classfile.instruction;
exports java.lang.constant;
exports java.lang.foreign;
exports java.lang.invoke;
exports java.lang.module;
exports java.lang.ref;
exports java.lang.reflect;
exports java.lang.runtime;
exports java.math;
exports java.net;
exports java.net.spi;
exports java.nio;
exports java.nio.channels;
exports java.nio.channels.spi;
exports java.nio.charset;
exports java.nio.charset.spi;
exports java.nio.file;
exports java.nio.file.attribute;
exports java.nio.file.spi;
exports java.security;
exports java.security.cert;
exports java.security.interfaces;
exports java.security.spec;
exports java.text;
exports java.text.spi;
exports java.time;
exports java.time.chrono;
exports java.time.format;
exports java.time.temporal;
exports java.time.zone;
exports java.util;
exports java.util.concurrent;
exports java.util.concurrent.atomic;
exports java.util.concurrent.locks;
exports java.util.function;
exports java.util.jar;
exports java.util.random;
exports java.util.regex;
exports java.util.spi;
exports java.util.stream;
exports java.util.zip;
exports javax.crypto;
exports javax.crypto.interfaces;
exports javax.crypto.spec;
exports javax.net;
exports javax.net.ssl;
exports javax.security.auth;
exports javax.security.auth.callback;
exports javax.security.auth.login;
exports javax.security.auth.spi;
exports javax.security.auth.x500;
exports javax.security.cert;
// additional qualified exports may be inserted at build time
// see make/gensrc/GenModuleInfo.gmk
exports com.sun.crypto.provider to
jdk.crypto.cryptoki;
exports sun.invoke.util to
jdk.compiler;
exports com.sun.security.ntlm to
java.security.sasl;
exports jdk.internal to
jdk.incubator.vector;
// Note: all modules in the exported list participate in preview features,
// normal or reflective. They do not need to be compiled with "--enable-preview"
// to use preview features and do not need to suppress "preview" warnings.
// It is recommended for any modules that do participate that their
// module declaration be annotated with jdk.internal.javac.ParticipatesInPreview.
exports jdk.internal.javac to
java.compiler,
jdk.compiler;
exports jdk.internal.access to
java.desktop,
java.logging,
java.management,
java.rmi,
jdk.charsets,
jdk.jartool,
jdk.jlink,
jdk.jfr,
jdk.management,
jdk.net,
jdk.sctp,
jdk.crypto.cryptoki;
exports jdk.internal.classfile.components to
jdk.jfr;
exports jdk.internal.foreign to
jdk.incubator.vector;
exports jdk.internal.event to
jdk.jfr;
exports jdk.internal.io to
jdk.internal.le,
jdk.jshell;
exports jdk.internal.jimage to
jdk.jlink;
exports jdk.internal.jimage.decompressor to
jdk.jlink;
exports jdk.internal.loader to
java.instrument,
java.logging,
java.naming;
exports jdk.internal.jmod to
jdk.compiler,
jdk.jlink;
exports jdk.internal.logger to
java.logging;
exports jdk.internal.net.quic to
java.net.http;
exports jdk.internal.org.xml.sax to
jdk.jfr;
exports jdk.internal.org.xml.sax.helpers to
jdk.jfr;
exports jdk.internal.misc to
java.desktop,
java.logging,
java.management,
java.naming,
java.net.http,
java.rmi,
java.security.jgss,
jdk.attach,
jdk.charsets,
jdk.compiler,
jdk.crypto.cryptoki,
jdk.incubator.vector,
jdk.jfr,
jdk.jshell,
jdk.nio.mapmode,
jdk.unsupported,
jdk.internal.vm.ci,
jdk.graal.compiler;
exports jdk.internal.module to
java.instrument,
java.management.rmi,
jdk.jartool,
jdk.compiler,
jdk.jfr,
jdk.jlink,
jdk.jpackage;
exports jdk.internal.perf to
java.management,
jdk.management.agent,
jdk.internal.jvmstat;
exports jdk.internal.platform to
jdk.management,
jdk.jfr;
exports jdk.internal.ref to
java.desktop,
java.net.http,
jdk.naming.dns;
exports jdk.internal.reflect to
java.logging,
java.sql,
java.sql.rowset,
jdk.dynalink,
jdk.internal.vm.ci,
jdk.unsupported;
exports jdk.internal.vm to
java.management,
jdk.internal.jvmstat,
jdk.management,
jdk.management.agent,
jdk.internal.vm.ci,
jdk.jfr;
exports jdk.internal.vm.annotation to
java.instrument,
jdk.internal.vm.ci,
jdk.incubator.vector,
jdk.jfr,
jdk.unsupported;
exports jdk.internal.vm.vector to
jdk.incubator.vector;
exports jdk.internal.util.xml to
jdk.jfr;
exports jdk.internal.util.xml.impl to
jdk.jfr;
exports jdk.internal.util to
java.desktop,
java.prefs,
java.security.jgss,
java.smartcardio,
java.naming,
java.rmi,
java.net.http,
jdk.charsets,
jdk.incubator.vector,
jdk.internal.vm.ci,
jdk.httpserver,
jdk.jlink,
jdk.jpackage,
jdk.net,
jdk.security.auth;
exports sun.net to
java.net.http,
jdk.naming.dns;
exports sun.net.ext to
jdk.net;
exports sun.net.dns to
java.security.jgss,
jdk.naming.dns;
exports sun.net.util to
java.net.http,
jdk.jconsole,
jdk.sctp;
exports sun.net.www to
java.net.http,
jdk.jartool;
exports sun.net.www.protocol.http to
java.security.jgss;
exports sun.nio.ch to
java.management,
jdk.crypto.cryptoki,
jdk.net,
jdk.sctp;
exports sun.nio.cs to
jdk.charsets;
exports sun.nio.fs to
jdk.net;
exports sun.reflect.annotation to
jdk.compiler;
exports sun.reflect.generics.reflectiveObjects to
java.desktop;
exports sun.reflect.misc to
java.desktop,
java.management;
exports sun.security.internal.interfaces to
jdk.crypto.cryptoki;
exports sun.security.internal.spec to
jdk.crypto.cryptoki;
exports sun.security.jca to
java.security.sasl,
java.smartcardio,
jdk.crypto.cryptoki,
jdk.naming.dns;
exports sun.security.pkcs to
jdk.jartool;
exports sun.security.provider to
java.security.jgss,
jdk.crypto.cryptoki,
jdk.security.auth;
exports sun.security.provider.certpath to
java.naming,
jdk.jartool;
exports sun.security.rsa to
jdk.crypto.cryptoki;
exports sun.security.timestamp to
jdk.jartool;
exports sun.security.tools to
jdk.jartool;
exports sun.security.util to
java.naming,
java.security.jgss,
java.security.sasl,
java.smartcardio,
java.xml.crypto,
jdk.crypto.cryptoki,
jdk.jartool,
jdk.security.auth,
jdk.security.jgss;
exports sun.security.x509 to
jdk.crypto.cryptoki,
jdk.jartool;
exports sun.security.validator to
jdk.jartool;
exports sun.util.cldr to
jdk.jlink;
exports sun.util.locale.provider to
java.desktop,
jdk.jlink,
jdk.localedata;
exports sun.util.logging to
java.desktop,
java.logging,
java.prefs;
exports sun.util.resources to
jdk.localedata;
// the service types defined by the APIs in this module
uses java.lang.System.LoggerFinder;
uses java.net.ContentHandlerFactory;
uses java.net.spi.InetAddressResolverProvider;
uses java.net.spi.URLStreamHandlerProvider;
uses java.nio.channels.spi.AsynchronousChannelProvider;
uses java.nio.channels.spi.SelectorProvider;
uses java.nio.charset.spi.CharsetProvider;
uses java.nio.file.spi.FileSystemProvider;
uses java.nio.file.spi.FileTypeDetector;
uses java.security.Provider;
uses java.text.spi.BreakIteratorProvider;
uses java.text.spi.CollatorProvider;
uses java.text.spi.DateFormatProvider;
uses java.text.spi.DateFormatSymbolsProvider;
uses java.text.spi.DecimalFormatSymbolsProvider;
uses java.text.spi.NumberFormatProvider;
uses java.time.chrono.AbstractChronology;
uses java.time.chrono.Chronology;
uses java.time.zone.ZoneRulesProvider;
uses java.util.spi.CalendarDataProvider;
uses java.util.spi.CalendarNameProvider;
uses java.util.spi.CurrencyNameProvider;
uses java.util.spi.LocaleNameProvider;
uses java.util.spi.ResourceBundleControlProvider;
uses java.util.spi.ResourceBundleProvider;
uses java.util.spi.TimeZoneNameProvider;
uses java.util.spi.ToolProvider;
uses javax.security.auth.spi.LoginModule;
// JDK-internal service types
uses jdk.internal.io.JdkConsoleProvider;
uses jdk.internal.logger.DefaultLoggerFinder;
uses sun.text.spi.JavaTimeDateTimePatternProvider;
uses sun.util.spi.CalendarProvider;
uses sun.util.locale.provider.LocaleDataMetaInfo;
uses sun.util.resources.LocaleData.LocaleDataResourceBundleProvider;
// Built-in service providers that are located via ServiceLoader
provides java.nio.file.spi.FileSystemProvider with
jdk.internal.jrtfs.JrtFileSystemProvider;
}