/* * Copyright (c) 2009, 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 java.lang.module; import java.io.InputStream; import java.io.IOException; import java.io.PrintStream; import java.io.UncheckedIOException; import java.net.URI; import java.nio.ByteBuffer; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; import static jdk.internal.module.Checks.*; import static java.util.Objects.*; import jdk.internal.module.Checks; import jdk.internal.module.ModuleHashes; import jdk.internal.module.WarnIfResolvedReason; /** * A module descriptor. * *

A {@code ModuleDescriptor} is typically created from the binary form * of a module declaration. Alternatively, the {@link ModuleDescriptor.Builder} * class can be used to create a {@code ModuleDescriptor} from its components. * The {@link #module module}, {@link #openModule openModule}, and {@link * #automaticModule automaticModule} methods create builders for building * different kinds of modules.

* *

{@code ModuleDescriptor} objects are immutable and safe for use by * multiple concurrent threads.

* * @since 9 * @see java.lang.reflect.Module */ public class ModuleDescriptor implements Comparable { /** *

A dependence upon a module

* * @see ModuleDescriptor#requires() * @since 9 */ public final static class Requires implements Comparable { /** * A modifier on a module dependence. * * @since 9 */ public static enum Modifier { /** * The dependence causes any module which depends on the current * module to have an implicitly declared dependence on the module * named by the {@code Requires}. */ TRANSITIVE, /** * The dependence is mandatory in the static phase, during compilation, * but is optional in the dynamic phase, during execution. */ STATIC, /** * The dependence was not explicitly or implicitly declared in the * source of the module declaration. */ SYNTHETIC, /** * The dependence was implicitly declared in the source of the module * declaration. */ MANDATED; } private final Set mods; private final String name; private Requires(Set ms, String mn) { if (ms.isEmpty()) { ms = Collections.emptySet(); } else { ms = Collections.unmodifiableSet(EnumSet.copyOf(ms)); } this.mods = ms; this.name = mn; } private Requires(Set ms, String mn, boolean unused) { this.mods = ms; this.name = mn; } /** * Returns the set of modifiers. * * @return A possibly-empty unmodifiable set of modifiers */ public Set modifiers() { return mods; } /** * Return the module name. * * @return The module name */ public String name() { return name; } /** * Compares this module dependence to another. * *

Two {@code Requires} objects are compared by comparing their * module name lexicographically. Where the module names are equal then * the sets of modifiers are compared based on a value computed from the * ordinal of each modifier.

* * @return A negative integer, zero, or a positive integer if this module * dependence is less than, equal to, or greater than the given * module dependence */ @Override public int compareTo(Requires that) { int c = this.name().compareTo(that.name()); if (c != 0) return c; // same name, compare by modifiers return Long.compare(this.modsValue(), that.modsValue()); } /** * Return a value for the modifiers to allow sets of modifiers to be * compared. */ private long modsValue() { long value = 0; for (Modifier m : mods) { value += 1 << m.ordinal(); } return value; } /** * Tests this module dependence for equality with the given object. * *

If the given object is not a {@code Requires} then this method * returns {@code false}. Two module dependence objects are equal if * the module names are equal and set of modifiers are equal.

* *

This method satisfies the general contract of the {@link * java.lang.Object#equals(Object) Object.equals} method.

* * @param ob * the object to which this object is to be compared * * @return {@code true} if, and only if, the given object is a module * dependence that is equal to this module dependence */ @Override public boolean equals(Object ob) { if (!(ob instanceof Requires)) return false; Requires that = (Requires)ob; return (name.equals(that.name) && mods.equals(that.mods)); } /** * Computes a hash code for this module dependence. * *

The hash code is based upon the module name and modifiers. It * satisfies the general contract of the {@link Object#hashCode * Object.hashCode} method.

* * @return The hash-code value for this module dependence */ @Override public int hashCode() { return name.hashCode() * 43 + mods.hashCode(); } /** * Returns a string describing module dependence. * * @return A string describing module dependence */ @Override public String toString() { return ModuleDescriptor.toString(mods, name); } } /** *

A module export, may be qualified or unqualified.

* * @see ModuleDescriptor#exports() * @since 9 */ public final static class Exports { /** * A modifier on a module export. * * @since 9 */ public static enum Modifier { /** * The export was not explicitly or implicitly declared in the * source of the module declaration. */ SYNTHETIC, /** * The export was implicitly declared in the source of the module * declaration. */ MANDATED; } private final Set mods; private final String source; private final Set targets; // empty if unqualified export /** * Constructs an export */ private Exports(Set ms, String source, Set targets) { if (ms.isEmpty()) { ms = Collections.emptySet(); } else { ms = Collections.unmodifiableSet(EnumSet.copyOf(ms)); } this.mods = ms; this.source = source; this.targets = emptyOrUnmodifiableSet(targets); } private Exports(Set ms, String source, Set targets, boolean unused) { this.mods = ms; this.source = source; this.targets = targets; } /** * Returns the set of modifiers. * * @return A possibly-empty unmodifiable set of modifiers */ public Set modifiers() { return mods; } /** * Returns {@code true} if this is a qualified export. * * @return {@code true} if this is a qualified export */ public boolean isQualified() { return !targets.isEmpty(); } /** * Returns the package name. * * @return The package name */ public String source() { return source; } /** * For a qualified export, returns the non-empty and immutable set * of the module names to which the package is exported. For an * unqualified export, returns an empty set. * * @return The set of target module names or for an unqualified * export, an empty set */ public Set targets() { return targets; } /** * Computes a hash code for this module export. * *

The hash code is based upon the modifiers, the package name, * and for a qualified export, the set of modules names to which the * package is exported. It satisfies the general contract of the * {@link Object#hashCode Object.hashCode} method. * * @return The hash-code value for this module export */ @Override public int hashCode() { int hash = mods.hashCode(); hash = hash * 43 + source.hashCode(); return hash * 43 + targets.hashCode(); } /** * Tests this module export for equality with the given object. * *

If the given object is not an {@code Exports} then this method * returns {@code false}. Two module exports objects are equal if their * set of modifiers is equal, the package names are equal and the set * of target module names is equal.

* *

This method satisfies the general contract of the {@link * java.lang.Object#equals(Object) Object.equals} method.

* * @param ob * the object to which this object is to be compared * * @return {@code true} if, and only if, the given object is a module * dependence that is equal to this module dependence */ @Override public boolean equals(Object ob) { if (!(ob instanceof Exports)) return false; Exports other = (Exports)ob; return Objects.equals(this.mods, other.mods) && Objects.equals(this.source, other.source) && Objects.equals(this.targets, other.targets); } /** * Returns a string describing module export. * * @return A string describing module export */ @Override public String toString() { String s = ModuleDescriptor.toString(mods, source); if (targets.isEmpty()) return s; else return s + " to " + targets; } } /** *

Represents a module opens directive, may be qualified or * unqualified.

* *

The opens directive in a module declaration declares a * package to be open to allow all types in the package, and all their * members, not just public types and their public members to be reflected * on by APIs that support private access or a way to bypass or suppress * default Java language access control checks.

* * @see ModuleDescriptor#opens() * @since 9 */ public final static class Opens { /** * A modifier on a module opens directive. * * @since 9 */ public static enum Modifier { /** * The opens was not explicitly or implicitly declared in the * source of the module declaration. */ SYNTHETIC, /** * The opens was implicitly declared in the source of the module * declaration. */ MANDATED; } private final Set mods; private final String source; private final Set targets; // empty if unqualified export /** * Constructs an Opens */ private Opens(Set ms, String source, Set targets) { if (ms.isEmpty()) { ms = Collections.emptySet(); } else { ms = Collections.unmodifiableSet(EnumSet.copyOf(ms)); } this.mods = ms; this.source = source; this.targets = emptyOrUnmodifiableSet(targets); } private Opens(Set ms, String source, Set targets, boolean unused) { this.mods = ms; this.source = source; this.targets = targets; } /** * Returns the set of modifiers. * * @return A possibly-empty unmodifiable set of modifiers */ public Set modifiers() { return mods; } /** * Returns {@code true} if this is a qualified opens. * * @return {@code true} if this is a qualified opens */ public boolean isQualified() { return !targets.isEmpty(); } /** * Returns the package name. * * @return The package name */ public String source() { return source; } /** * For a qualified opens, returns the non-empty and immutable set * of the module names to which the package is open. For an * unqualified opens, returns an empty set. * * @return The set of target module names or for an unqualified * opens, an empty set */ public Set targets() { return targets; } /** * Computes a hash code for this module opens. * *

The hash code is based upon the modifiers, the package name, * and for a qualified opens, the set of modules names to which the * package is opened. It satisfies the general contract of the * {@link Object#hashCode Object.hashCode} method. * * @return The hash-code value for this module opens */ @Override public int hashCode() { int hash = mods.hashCode(); hash = hash * 43 + source.hashCode(); return hash * 43 + targets.hashCode(); } /** * Tests this module opens for equality with the given object. * *

If the given object is not an {@code Opens} then this method * returns {@code false}. Two {@code Opens} objects are equal if their * set of modifiers is equal, the package names are equal and the set * of target module names is equal.

* *

This method satisfies the general contract of the {@link * java.lang.Object#equals(Object) Object.equals} method.

* * @param ob * the object to which this object is to be compared * * @return {@code true} if, and only if, the given object is a module * dependence that is equal to this module dependence */ @Override public boolean equals(Object ob) { if (!(ob instanceof Opens)) return false; Opens other = (Opens)ob; return Objects.equals(this.mods, other.mods) && Objects.equals(this.source, other.source) && Objects.equals(this.targets, other.targets); } /** * Returns a string describing module opens. * * @return A string describing module opens */ @Override public String toString() { String s = ModuleDescriptor.toString(mods, source); if (targets.isEmpty()) return s; else return s + " to " + targets; } } /** *

A service that a module provides one or more implementations of.

* * @see ModuleDescriptor#provides() * @since 9 */ public final static class Provides { private final String service; private final List providers; private Provides(String service, List providers) { this.service = service; this.providers = Collections.unmodifiableList(providers); } private Provides(String service, List providers, boolean unused) { this.service = service; this.providers = providers; } /** * Returns the fully qualified class name of the service type. * * @return The fully qualified class name of the service type. */ public String service() { return service; } /** * Returns the list of the fully qualified class names of the providers * or provider factories. * * @return A non-empty and unmodifiable list of the fully qualified class * names of the providers or provider factories */ public List providers() { return providers; } /** * Computes a hash code for this provides. * *

The hash code is based upon the service type and the set of * providers. It satisfies the general contract of the {@link * Object#hashCode Object.hashCode} method.

* * @return The hash-code value for this module provides */ @Override public int hashCode() { return service.hashCode() * 43 + providers.hashCode(); } /** * Tests this provides for equality with the given object. * *

If the given object is not a {@code Provides} then this method * returns {@code false}. Two {@code Provides} objects are equal if the * service type is equal and the list of providers is equal.

* *

This method satisfies the general contract of the {@link * java.lang.Object#equals(Object) Object.equals} method.

* * @param ob * the object to which this object is to be compared * * @return {@code true} if, and only if, the given object is a * {@code Provides} that is equal to this {@code Provides} */ @Override public boolean equals(Object ob) { if (!(ob instanceof Provides)) return false; Provides other = (Provides)ob; return Objects.equals(this.service, other.service) && Objects.equals(this.providers, other.providers); } /** * Returns a string describing this provides. * * @return A string describing this provides */ @Override public String toString() { return service + " with " + providers; } } /** * A module's version string. * *

A version string has three components: The version number itself, an * optional pre-release version, and an optional build version. Each * component is sequence of tokens; each token is either a non-negative * integer or a string. Tokens are separated by the punctuation characters * {@code '.'}, {@code '-'}, or {@code '+'}, or by transitions from a * sequence of digits to a sequence of characters that are neither digits * nor punctuation characters, or vice versa. * *

    * *
  • The version number is a sequence of tokens separated by * {@code '.'} characters, terminated by the first {@code '-'} or {@code * '+'} character.
  • * *
  • The pre-release version is a sequence of tokens separated * by {@code '.'} or {@code '-'} characters, terminated by the first * {@code '+'} character.
  • * *
  • The build version is a sequence of tokens separated by * {@code '.'}, {@code '-'}, or {@code '+'} characters. * *
* *

When comparing two version strings, the elements of their * corresponding components are compared in pointwise fashion. If one * component is longer than the other, but otherwise equal to it, then the * first component is considered the greater of the two; otherwise, if two * corresponding elements are integers then they are compared as such; * otherwise, at least one of the elements is a string, so the other is * converted into a string if it is an integer and the two are compared * lexicographically. Trailing integer elements with the value zero are * ignored. * *

Given two version strings, if their version numbers differ then the * result of comparing them is the result of comparing their version * numbers; otherwise, if one of them has a pre-release version but the * other does not then the first is considered to precede the second, * otherwise the result of comparing them is the result of comparing their * pre-release versions; otherwise, the result of comparing them is the * result of comparing their build versions. * * @see ModuleDescriptor#version() * @since 9 */ public final static class Version implements Comparable { private final String version; // If Java had disjunctive types then we'd write List here // private final List sequence; private final List pre; private final List build; // Take a numeric token starting at position i // Append it to the given list // Return the index of the first character not taken // Requires: s.charAt(i) is (decimal) numeric // private static int takeNumber(String s, int i, List acc) { char c = s.charAt(i); int d = (c - '0'); int n = s.length(); while (++i < n) { c = s.charAt(i); if (c >= '0' && c <= '9') { d = d * 10 + (c - '0'); continue; } break; } acc.add(d); return i; } // Take a string token starting at position i // Append it to the given list // Return the index of the first character not taken // Requires: s.charAt(i) is not '.' // private static int takeString(String s, int i, List acc) { int b = i; int n = s.length(); while (++i < n) { char c = s.charAt(i); if (c != '.' && c != '-' && c != '+' && !(c >= '0' && c <= '9')) continue; break; } acc.add(s.substring(b, i)); return i; } // Syntax: tok+ ( '-' tok+)? ( '+' tok+)? // First token string is sequence, second is pre, third is build // Tokens are separated by '.' or '-', or by changes between alpha & numeric // Numeric tokens are compared as decimal integers // Non-numeric tokens are compared lexicographically // A version with a non-empty pre is less than a version with same seq but no pre // Tokens in build may contain '-' and '+' // private Version(String v) { if (v == null) throw new IllegalArgumentException("Null version string"); int n = v.length(); if (n == 0) throw new IllegalArgumentException("Empty version string"); int i = 0; char c = v.charAt(i); if (!(c >= '0' && c <= '9')) throw new IllegalArgumentException(v + ": Version string does not start" + " with a number"); List sequence = new ArrayList<>(4); List pre = new ArrayList<>(2); List build = new ArrayList<>(2); i = takeNumber(v, i, sequence); while (i < n) { c = v.charAt(i); if (c == '.') { i++; continue; } if (c == '-' || c == '+') { i++; break; } if (c >= '0' && c <= '9') i = takeNumber(v, i, sequence); else i = takeString(v, i, sequence); } if (c == '-' && i >= n) throw new IllegalArgumentException(v + ": Empty pre-release"); while (i < n) { c = v.charAt(i); if (c >= '0' && c <= '9') i = takeNumber(v, i, pre); else i = takeString(v, i, pre); if (i >= n) break; c = v.charAt(i); if (c == '.' || c == '-') { i++; continue; } if (c == '+') { i++; break; } } if (c == '+' && i >= n) throw new IllegalArgumentException(v + ": Empty pre-release"); while (i < n) { c = v.charAt(i); if (c >= '0' && c <= '9') i = takeNumber(v, i, build); else i = takeString(v, i, build); if (i >= n) break; c = v.charAt(i); if (c == '.' || c == '-' || c == '+') { i++; continue; } } this.version = v; this.sequence = sequence; this.pre = pre; this.build = build; } /** * Parses the given string as a version string. * * @param v * The string to parse * * @return The resulting {@code Version} * * @throws IllegalArgumentException * If {@code v} is {@code null}, an empty string, or cannot be * parsed as a version string */ public static Version parse(String v) { return new Version(v); } @SuppressWarnings("unchecked") private int cmp(Object o1, Object o2) { return ((Comparable)o1).compareTo(o2); } private int compareTokens(List ts1, List ts2) { int n = Math.min(ts1.size(), ts2.size()); for (int i = 0; i < n; i++) { Object o1 = ts1.get(i); Object o2 = ts2.get(i); if ((o1 instanceof Integer && o2 instanceof Integer) || (o1 instanceof String && o2 instanceof String)) { int c = cmp(o1, o2); if (c == 0) continue; return c; } // Types differ, so convert number to string form int c = o1.toString().compareTo(o2.toString()); if (c == 0) continue; return c; } List rest = ts1.size() > ts2.size() ? ts1 : ts2; int e = rest.size(); for (int i = n; i < e; i++) { Object o = rest.get(i); if (o instanceof Integer && ((Integer)o) == 0) continue; return ts1.size() - ts2.size(); } return 0; } /** * Compares this module version to another module version. Module * versions are compared as described in the class description. * * @param that * The module version to compare * * @return A negative integer, zero, or a positive integer as this * module version is less than, equal to, or greater than the * given module version */ @Override public int compareTo(Version that) { int c = compareTokens(this.sequence, that.sequence); if (c != 0) return c; if (this.pre.isEmpty()) { if (!that.pre.isEmpty()) return +1; } else { if (that.pre.isEmpty()) return -1; } c = compareTokens(this.pre, that.pre); if (c != 0) return c; return compareTokens(this.build, that.build); } /** * Tests this module version for equality with the given object. * *

If the given object is not a {@code Version} then this method * returns {@code false}. Two module version are equal if their * corresponding components are equal.

* *

This method satisfies the general contract of the {@link * java.lang.Object#equals(Object) Object.equals} method.

* * @param ob * the object to which this object is to be compared * * @return {@code true} if, and only if, the given object is a module * reference that is equal to this module reference */ @Override public boolean equals(Object ob) { if (!(ob instanceof Version)) return false; return compareTo((Version)ob) == 0; } /** * Computes a hash code for this module version. * *

The hash code is based upon the components of the version and * satisfies the general contract of the {@link Object#hashCode * Object.hashCode} method.

* * @return The hash-code value for this module version */ @Override public int hashCode() { return version.hashCode(); } /** * Returns the string from which this version was parsed. * * @return The string from which this version was parsed. */ @Override public String toString() { return version; } } // From module declarations private final String name; private final boolean open; // Indicates if synthesised for a JAR file found on the module path private final boolean automatic; // Not generated from a module-info.java private final boolean synthetic; private final Set requires; private final Set exports; private final Set opens; private final Set uses; private final Set provides; // "Extended" information, added post-compilation by tools private final Version version; private final String mainClass; private final String osName; private final String osArch; private final String osVersion; private final Set packages; private final ModuleHashes hashes; private final boolean doNotResolveByDefault; private final WarnIfResolvedReason warnIfResolvedReason; private ModuleDescriptor(String name, boolean open, boolean automatic, boolean synthetic, Set requires, Set exports, Set opens, Set uses, Set provides, Version version, String mainClass, String osName, String osArch, String osVersion, Set packages, ModuleHashes hashes, boolean doNotResolveByDefault, WarnIfResolvedReason warnIfResolvedReason) { this.name = name; this.open = open; this.automatic = automatic; this.synthetic = synthetic; assert (requires.stream().map(Requires::name).distinct().count() == requires.size()); this.requires = emptyOrUnmodifiableSet(requires); this.exports = emptyOrUnmodifiableSet(exports); this.opens = emptyOrUnmodifiableSet(opens); this.uses = emptyOrUnmodifiableSet(uses); this.provides = emptyOrUnmodifiableSet(provides); this.version = version; this.mainClass = mainClass; this.osName = osName; this.osArch = osArch; this.osVersion = osVersion; this.hashes = hashes; this.packages = emptyOrUnmodifiableSet(packages); this.doNotResolveByDefault = doNotResolveByDefault; this.warnIfResolvedReason = warnIfResolvedReason; } /** * Clones the given module descriptor with an augmented set of packages */ ModuleDescriptor(ModuleDescriptor md, Set pkgs) { this.name = md.name; this.open = md.open; this.automatic = md.automatic; this.synthetic = md.synthetic; this.requires = md.requires; this.exports = md.exports; this.opens = md.opens; this.uses = md.uses; this.provides = md.provides; this.version = md.version; this.mainClass = md.mainClass; this.osName = md.osName; this.osArch = md.osArch; this.osVersion = md.osVersion; this.hashes = null; // need to ignore this.doNotResolveByDefault = md.doNotResolveByDefault; this.warnIfResolvedReason = md.warnIfResolvedReason; Set packages = new HashSet<>(md.packages); packages.addAll(pkgs); this.packages = emptyOrUnmodifiableSet(packages); } /** * Creates a module descriptor from its components. * The arguments are pre-validated and sets are unmodifiable sets. */ ModuleDescriptor(String name, boolean open, boolean automatic, boolean synthetic, Set requires, Set exports, Set opens, Set uses, Set provides, Version version, String mainClass, String osName, String osArch, String osVersion, Set packages, ModuleHashes hashes, int hashCode, boolean doNotResolveByDefault, WarnIfResolvedReason warnIfResolvedReason, boolean unused) { this.name = name; this.open = open; this.automatic = automatic; this.synthetic = synthetic; this.requires = requires; this.exports = exports; this.opens = opens; this.uses = uses; this.provides = provides; this.packages = packages; this.version = version; this.mainClass = mainClass; this.osName = osName; this.osArch = osArch; this.osVersion = osVersion; this.hashes = hashes; this.hash = hashCode; this.doNotResolveByDefault = doNotResolveByDefault; this.warnIfResolvedReason = warnIfResolvedReason; } /** *

The module name.

* * @return The module name */ public String name() { return name; } /** *

Returns {@code true} if this is an open module.

* *

An open module does not declare any open packages (the {@link #opens() * opens} method returns an empty set) but the resulting module is treated * as if all packages are open.

* * @return {@code true} if this is an open module */ public boolean isOpen() { return open; } /** *

Returns {@code true} if this is an automatic module.

* *

An automatic module is defined implicitly rather than explicitly * and therefore does not have a module declaration. JAR files located on * the application module path, or by the {@link ModuleFinder} returned by * {@link ModuleFinder#of(java.nio.file.Path[]) ModuleFinder.of}, are * treated as automatic modules if they do have not have a module * declaration.

* * @return {@code true} if this is an automatic module */ public boolean isAutomatic() { return automatic; } /** *

Returns {@code true} if this module descriptor was not generated * from an explicit module declaration ({@code module-info.java}) * or an implicit module declaration (an {@link #isAutomatic() automatic} * module).

* * @return {@code true} if this module descriptor was not generated by * an explicit or implicit module declaration */ public boolean isSynthetic() { return synthetic; } /** *

The dependences of this module.

* * @return A possibly-empty unmodifiable set of {@link Requires} objects */ public Set requires() { return requires; } /** *

The module exports.

* * @return A possibly-empty unmodifiable set of exported packages */ public Set exports() { return exports; } /** *

The module opens directives.

* *

Each {@code Opens} object in the set represents a package (and * the set of target module names when qualified) where all types in the * package, and all their members, not just public types and their public * members, can be reflected on when using APIs that bypass or suppress * default Java language access control checks.

* *

This method returns an empty set when invoked on {@link #isOpen() * open} module.

* * @return A possibly-empty unmodifiable set of open packages */ public Set opens() { return opens; } /** *

The service dependences of this module.

* * @return A possibly-empty unmodifiable set of the fully qualified class * names of the service types used */ public Set uses() { return uses; } /** *

The services that this module provides.

* * @return The possibly-empty unmodifiable set of the services that this * module provides */ public Set provides() { return provides; } /** * Returns this module's version. * * @return This module's version */ public Optional version() { return Optional.ofNullable(version); } /** * Returns a string containing this module's name and, if present, its * version. * * @return A string containing this module's name and, if present, its * version. */ public String toNameAndVersion() { if (version != null) { return name() + "@" + version; } else { return name(); } } /** * Returns the module's main class. * * @return The fully qualified class name of this module's main class */ public Optional mainClass() { return Optional.ofNullable(mainClass); } /** * Returns the operating system name if this module is operating system * specific. * * @return The operating system name or an empty {@code Optional} * if this module is not operating system specific */ public Optional osName() { return Optional.ofNullable(osName); } /** * Returns the operating system architecture if this module is operating * system architecture specific. * * @return The operating system architecture or an empty {@code Optional} * if this module is not operating system architecture specific */ public Optional osArch() { return Optional.ofNullable(osArch); } /** * Returns the operating system version if this module is operating * system version specific. * * @return The operating system version or an empty {@code Optional} * if this module is not operating system version specific */ public Optional osVersion() { return Optional.ofNullable(osVersion); } /** * Returns the names of all packages in this module. * * @return A possibly-empty unmodifiable set of all packages in the module */ public Set packages() { return packages; } /** * Returns the object with the hashes of other modules */ Optional hashes() { return Optional.ofNullable(hashes); } /* package */ boolean doNotResolveByDefault() { return doNotResolveByDefault; } /** * A builder used for building {@link ModuleDescriptor} objects. * *

{@code ModuleDescriptor} defines the {@link #module module}, {@link * #openModule openModule}, and {@link #automaticModule automaticModule} * methods to create builders for building different kinds of modules.

* *

Example usage:

*
{@code    ModuleDescriptor descriptor = ModuleDescriptor.module("m1")
     *         .exports("p")
     *         .requires("m2")
     *         .build();
     * }
* * @apiNote A {@code Builder} checks the components and invariants as * components are added to the builder. The rational for this is to detect * errors as early as possible and not defer all validation to the * {@link #build build} method. A {@code Builder} cannot be used to create * a {@link ModuleDescriptor#isSynthetic() synthetic} module. * * @since 9 */ public static final class Builder { final String name; final boolean strict; // true if module names are checked boolean open; boolean automatic; boolean synthetic; final Map requires = new HashMap<>(); final Map exports = new HashMap<>(); final Map opens = new HashMap<>(); final Set concealedPackages = new HashSet<>(); final Set uses = new HashSet<>(); final Map provides = new HashMap<>(); Version version; String osName; String osArch; String osVersion; String mainClass; ModuleHashes hashes; boolean doNotResolveByDefault; WarnIfResolvedReason warnIfResolvedReason = WarnIfResolvedReason.NONE; /** * Initializes a new builder with the given module name. * * @param strict * Indicates whether module names are checked or not */ Builder(String name, boolean strict) { this.strict = strict; this.name = (strict) ? requireModuleName(name) : name; } /* package */ Builder open(boolean open) { this.open = open; return this; } /* package */ Builder automatic(boolean automatic) { this.automatic = automatic; return this; } /* package */ boolean isOpen() { return open; } /* package */ boolean isAutomatic() { return automatic; } /** * Adds a dependence on a module. * * @param req * The dependence * * @return This builder * * @throws IllegalArgumentException * If the dependence is on the module that this builder was * initialized to build * @throws IllegalStateException * If the dependence on the module has already been declared */ public Builder requires(Requires req) { String mn = req.name(); if (name.equals(mn)) throw new IllegalArgumentException("Dependence on self"); if (requires.containsKey(mn)) throw new IllegalStateException("Dependence upon " + mn + " already declared"); requires.put(mn, req); return this; } /** * Adds a dependence on a module with the given (and possibly empty) * set of modifiers. * * @param ms * The set of modifiers * @param mn * The module name * * @return This builder * * @throws IllegalArgumentException * If the module name is {@code null}, is not a legal Java * identifier, or is equal to the module name that this builder * was initialized to build * @throws IllegalStateException * If the dependence on the module has already been declared */ public Builder requires(Set ms, String mn) { if (strict) mn = requireModuleName(mn); return requires(new Requires(ms, mn)); } /** * Adds a dependence on a module with an empty set of modifiers. * * @param mn * The module name * * @return This builder * * @throws IllegalArgumentException * If the module name is {@code null}, is not a legal Java * identifier, or is equal to the module name that this builder * was initialized to build * @throws IllegalStateException * If the dependence on the module has already been declared */ public Builder requires(String mn) { return requires(EnumSet.noneOf(Requires.Modifier.class), mn); } /** * Adds an export. * * @param e * The export * * @return This builder * * @throws IllegalStateException * If the package is already declared as a package with the * {@link #contains contains} method or the package is already * declared as exported */ public Builder exports(Exports e) { // can't be exported and concealed String source = e.source(); if (concealedPackages.contains(source)) { throw new IllegalStateException("Package " + source + " already declared"); } if (exports.containsKey(source)) { throw new IllegalStateException("Exported package " + source + " already declared"); } exports.put(source, e); return this; } /** * Adds an export, with the given (and possibly empty) set of modifiers, * to export a package to a set of target modules. * * @param ms * The set of modifiers * @param pn * The package name * @param targets * The set of target modules names * * @return This builder * * @throws IllegalArgumentException * If the package name or any of the target modules is {@code * null} or is not a legal Java identifier, or the set of * targets is empty * @throws IllegalStateException * If the package is already declared as a package with the * {@link #contains contains} method or the package is already * declared as exported */ public Builder exports(Set ms, String pn, Set targets) { Exports e = new Exports(ms, requirePackageName(pn), targets); // check targets targets = e.targets(); if (targets.isEmpty()) throw new IllegalArgumentException("Empty target set"); if (strict) targets.stream().forEach(Checks::requireModuleName); return exports(e); } /** * Adds an unqualified export with the given (and possibly empty) set * of modifiers. * * @param ms * The set of modifiers * @param pn * The package name * * @return This builder * * @throws IllegalArgumentException * If the package name is {@code null} or is not a legal Java * identifier * @throws IllegalStateException * If the package is already declared as a package with the * {@link #contains contains} method or the package is already * declared as exported */ public Builder exports(Set ms, String pn) { Exports e = new Exports(ms, requirePackageName(pn), Collections.emptySet()); return exports(e); } /** * Adds an export to export a package to a set of target modules. * * @param pn * The package name * @param targets * The set of target modules names * * @return This builder * * @throws IllegalArgumentException * If the package name or any of the target modules is {@code * null} or is not a legal Java identifier, or the set of * targets is empty * @throws IllegalStateException * If the package is already declared as a package with the * {@link #contains contains} method or the package is already * declared as exported */ public Builder exports(String pn, Set targets) { return exports(Collections.emptySet(), pn, targets); } /** * Adds an unqualified export. * * @param pn * The package name * * @return This builder * * @throws IllegalArgumentException * If the package name is {@code null} or is not a legal Java * identifier * @throws IllegalStateException * If the package is already declared as a package with the * {@link #contains contains} method or the package is already * declared as exported */ public Builder exports(String pn) { return exports(Collections.emptySet(), pn); } /** * Adds an opens directive. * * @param obj * The {@code Opens} object * * @return This builder * * @throws IllegalStateException * If the package is already declared as a package with the * {@link #contains contains} method, the package is already * declared as open, or this is a builder for an open module */ public Builder opens(Opens obj) { if (open) { throw new IllegalStateException("open modules cannot declare" + " open packages"); } // can't be open and concealed String source = obj.source(); if (concealedPackages.contains(source)) { throw new IllegalStateException("Package " + source + " already declared"); } if (opens.containsKey(source)) { throw new IllegalStateException("Open package " + source + " already declared"); } opens.put(source, obj); return this; } /** * Adds an opens directive, with the given (and possibly empty) * set of modifiers, to open a package to a set of target modules. * * @param ms * The set of modifiers * @param pn * The package name * @param targets * The set of target modules names * * @return This builder * * @throws IllegalArgumentException * If the package name or any of the target modules is {@code * null} or is not a legal Java identifier, or the set of * targets is empty * @throws IllegalStateException * If the package is already declared as a package with the * {@link #contains contains} method, the package is already * declared as open, or this is a builder for an open module */ public Builder opens(Set ms, String pn, Set targets) { Opens e = new Opens(ms, requirePackageName(pn), targets); // check targets targets = e.targets(); if (targets.isEmpty()) throw new IllegalArgumentException("Empty target set"); if (strict) targets.stream().forEach(Checks::requireModuleName); return opens(e); } /** * Adds an opens directive to open a package with the given (and * possibly empty) set of modifiers. * * @param ms * The set of modifiers * @param pn * The package name * * @return This builder * * @throws IllegalArgumentException * If the package name is {@code null} or is not a legal Java * identifier * @throws IllegalStateException * If the package is already declared as a package with the * {@link #contains contains} method, the package is already * declared as open, or this is a builder for an open module */ public Builder opens(Set ms, String pn) { Opens e = new Opens(ms, requirePackageName(pn), Collections.emptySet()); return opens(e); } /** * Adds an opens directive to open a package to a set of target * modules. * * @param pn * The package name * @param targets * The set of target modules names * * @return This builder * * @throws IllegalArgumentException * If the package name or any of the target modules is {@code * null} or is not a legal Java identifier, or the set of * targets is empty * @throws IllegalStateException * If the package is already declared as a package with the * {@link #contains contains} method, the package is already * declared as open, or this is a builder for an open module */ public Builder opens(String pn, Set targets) { return opens(Collections.emptySet(), pn, targets); } /** * Adds an opens directive to open a package. * * @param pn * The package name * * @return This builder * * @throws IllegalArgumentException * If the package name is {@code null} or is not a legal Java * identifier * @throws IllegalStateException * If the package is already declared as a package with the * {@link #contains contains} method, the package is already * declared as open, or this is a builder for an open module */ public Builder opens(String pn) { return opens(Collections.emptySet(), pn); } /* package */ Builder doNotResolveByDefault(boolean value) { this.doNotResolveByDefault = value; return this; } /* package */ Builder warnIfResolved(WarnIfResolvedReason reason) { this.warnIfResolvedReason = reason; return this; } // Used by ModuleInfo, after a packageFinder is invoked /* package */ Set exportedAndOpenPackages() { if (opens.isEmpty()) return exports.keySet(); Set result = new HashSet<>(); result.addAll(exports.keySet()); result.addAll(opens.keySet()); return result; } /** * Adds a service dependence. * * @param service * The service type * * @return This builder * * @throws IllegalArgumentException * If the service type is {@code null} or is not a legal Java * identifier * @throws IllegalStateException * If a dependency on the service type has already been declared */ public Builder uses(String service) { if (uses.contains(requireServiceTypeName(service))) throw new IllegalStateException("Dependence upon service " + service + " already declared"); uses.add(service); return this; } /** * Provides a service with one or more implementations. * * @param p * The provides * * @return This builder * * @throws IllegalStateException * If the providers for the service type have already been * declared */ public Builder provides(Provides p) { String st = p.service(); if (provides.containsKey(st)) throw new IllegalStateException("Providers of service " + st + " already declared"); provides.put(st, p); return this; } /** * Provides implementations of a service. * * @param service * The service type * @param providers * The list of provider or provider factory class names * * @return This builder * * @throws IllegalArgumentException * If the service type or any of the provider class names is * {@code null} or is not a legal Java identifier, or the list * of provider class names is empty * @throws IllegalStateException * If the providers for the service type have already been * declared */ public Builder provides(String service, List providers) { if (provides.containsKey(service)) throw new IllegalStateException("Providers of service " + service + " already declared by " + name); Provides p = new Provides(requireServiceTypeName(service), providers); // check providers after the set has been copied. List providerNames = p.providers(); if (providerNames.isEmpty()) throw new IllegalArgumentException("Empty providers set"); providerNames.forEach(Checks::requireServiceProviderName); provides.put(service, p); return this; } /** * Provides an implementation of a service. * * @param service * The service type * @param provider * The provider or provider factory class name * * @return This builder * * @throws IllegalArgumentException * If the service type or the provider class name is {@code * null} or is not a legal Java identifier * @throws IllegalStateException * If the providers for the service type have already been * declared */ public Builder provides(String service, String provider) { if (provider == null) throw new IllegalArgumentException("'provider' is null"); return provides(service, List.of(provider)); } /** * Adds a (possible empty) set of packages to the module * * @param pns * The set of package names * * @return This builder * * @throws IllegalArgumentException * If any of the package names is {@code null} or is not a * legal Java identifier * @throws IllegalStateException * If any of packages are already declared as packages in * the module. This includes packages that are already * declared as exported or open packages. */ public Builder contains(Set pns) { pns.forEach(this::contains); return this; } /** * Adds a package to the module. * * @param pn * The package name * * @return This builder * * @throws IllegalArgumentException * If the package name is {@code null}, or is not a legal Java * identifier * @throws IllegalStateException * If the package is already declared as a package in the * module. This includes the package already declared as an * exported or open package. */ public Builder contains(String pn) { Checks.requirePackageName(pn); if (concealedPackages.contains(pn)) { throw new IllegalStateException("Package " + pn + " already declared"); } if (exports.containsKey(pn)) { throw new IllegalStateException("Exported package " + pn + " already declared"); } if (opens.containsKey(pn)) { throw new IllegalStateException("Open package " + pn + " already declared"); } concealedPackages.add(pn); return this; } /** * Sets the module version. * * @param v * The version * * @return This builder */ public Builder version(Version v) { version = requireNonNull(v); return this; } /** * Sets the module version. * * @param v * The version string to parse * * @return This builder * * @throws IllegalArgumentException * If {@code v} is null or cannot be parsed as a version string * * @see Version#parse(String) */ public Builder version(String v) { return version(Version.parse(v)); } /** * Sets the module main class. * * @param mc * The module main class * * @return This builder * * @throws IllegalArgumentException * If {@code mainClass} is null or is not a legal Java identifier */ public Builder mainClass(String mc) { mainClass = requireJavaIdentifier("main class name", mc); return this; } /** * Sets the operating system name. * * @param name * The operating system name * * @return This builder * * @throws IllegalArgumentException * If {@code name} is null or the empty String */ public Builder osName(String name) { if (name == null || name.isEmpty()) throw new IllegalArgumentException("OS name is null or empty"); osName = name; return this; } /** * Sets the operating system architecture. * * @param arch * The operating system architecture * * @return This builder * * @throws IllegalArgumentException * If {@code name} is null or the empty String */ public Builder osArch(String arch) { if (arch == null || arch.isEmpty()) throw new IllegalArgumentException("OS arch is null or empty"); osArch = arch; return this; } /** * Sets the operating system version. * * @param version * The operating system version * * @return This builder * * @throws IllegalArgumentException * If {@code name} is null or the empty String */ public Builder osVersion(String version) { if (version == null || version.isEmpty()) throw new IllegalArgumentException("OS version is null or empty"); osVersion = version; return this; } /* package */ Builder hashes(ModuleHashes hashes) { this.hashes = hashes; return this; } /* package */ Builder synthetic(boolean v) { this.synthetic = v; return this; } /** * Builds and returns a {@code ModuleDescriptor} from its components. * * @return The module descriptor */ public ModuleDescriptor build() { Set requires = new HashSet<>(this.requires.values()); Set packages = new HashSet<>(exportedAndOpenPackages()); packages.addAll(concealedPackages); Set exports = new HashSet<>(this.exports.values()); Set opens = new HashSet<>(this.opens.values()); Set provides = new HashSet<>(this.provides.values()); return new ModuleDescriptor(name, open, automatic, synthetic, requires, exports, opens, uses, provides, version, mainClass, osName, osArch, osVersion, packages, hashes, doNotResolveByDefault, warnIfResolvedReason); } } /** * Compares this module descriptor to another. * *

Two {@code ModuleDescriptor} objects are compared by comparing their * module name lexicographically. Where the module names are equal then * the versions, if present, are compared.

* * @apiNote For now, the natural ordering is not consistent with equals. * If two module descriptors have equal module names, equal versions if * present, but their corresponding components are not equal, then they * will be considered equal by this method. * * @param that * The object to which this module descriptor is to be compared * * @return A negative integer, zero, or a positive integer if this module * descriptor is less than, equal to, or greater than the given * module descriptor */ @Override public int compareTo(ModuleDescriptor that) { int c = this.name().compareTo(that.name()); if (c != 0) return c; if (version == null) { if (that.version == null) return 0; return -1; } if (that.version == null) return +1; return version.compareTo(that.version); } /** * Tests this module descriptor for equality with the given object. * *

If the given object is not a {@code ModuleDescriptor} then this * method returns {@code false}. Two module descriptors are equal if each * of their corresponding components is equal.

* *

This method satisfies the general contract of the {@link * java.lang.Object#equals(Object) Object.equals} method.

* * @param ob * the object to which this object is to be compared * * @return {@code true} if, and only if, the given object is a module * descriptor that is equal to this module descriptor */ @Override public boolean equals(Object ob) { if (ob == this) return true; if (!(ob instanceof ModuleDescriptor)) return false; ModuleDescriptor that = (ModuleDescriptor)ob; return (name.equals(that.name) && open == that.open && automatic == that.automatic && synthetic == that.synthetic && requires.equals(that.requires) && exports.equals(that.exports) && opens.equals(that.opens) && uses.equals(that.uses) && provides.equals(that.provides) && Objects.equals(version, that.version) && Objects.equals(mainClass, that.mainClass) && Objects.equals(osName, that.osName) && Objects.equals(osArch, that.osArch) && Objects.equals(osVersion, that.osVersion) && Objects.equals(packages, that.packages) && Objects.equals(hashes, that.hashes) && doNotResolveByDefault == that.doNotResolveByDefault && warnIfResolvedReason == that.warnIfResolvedReason); } private transient int hash; // cached hash code /** * Computes a hash code for this module descriptor. * *

The hash code is based upon the components of the module descriptor, * and satisfies the general contract of the {@link Object#hashCode * Object.hashCode} method.

* * @return The hash-code value for this module descriptor */ @Override public int hashCode() { int hc = hash; if (hc == 0) { hc = name.hashCode(); hc = hc * 43 + Boolean.hashCode(open); hc = hc * 43 + Boolean.hashCode(automatic); hc = hc * 43 + Boolean.hashCode(synthetic); hc = hc * 43 + requires.hashCode(); hc = hc * 43 + exports.hashCode(); hc = hc * 43 + opens.hashCode(); hc = hc * 43 + uses.hashCode(); hc = hc * 43 + provides.hashCode(); hc = hc * 43 + Objects.hashCode(version); hc = hc * 43 + Objects.hashCode(mainClass); hc = hc * 43 + Objects.hashCode(osName); hc = hc * 43 + Objects.hashCode(osArch); hc = hc * 43 + Objects.hashCode(osVersion); hc = hc * 43 + Objects.hashCode(packages); hc = hc * 43 + Objects.hashCode(hashes); hc = hc * 43 + Objects.hashCode(doNotResolveByDefault); hc = hc * 43 + Objects.hashCode(warnIfResolvedReason); if (hc == 0) hc = -1; hash = hc; } return hc; } /** * Returns a string describing this descriptor. * * @return A string describing this descriptor */ @Override public String toString() { StringBuilder sb = new StringBuilder(); if (isOpen()) sb.append("open "); sb.append("module { name: ").append(toNameAndVersion()); if (!requires.isEmpty()) sb.append(", ").append(requires); if (!uses.isEmpty()) sb.append(", ").append(uses); if (!exports.isEmpty()) sb.append(", exports: ").append(exports); if (!opens.isEmpty()) sb.append(", opens: ").append(opens); if (!provides.isEmpty()) { sb.append(", provides: ").append(provides); } sb.append(" }"); return sb.toString(); } /** * Instantiates a builder to build a module descriptor. * * @param name * The module name * * @return A new builder * * @throws IllegalArgumentException * If the module name is {@code null} or is not a legal Java * identifier */ public static Builder module(String name) { return new Builder(name, true); } /** * Instantiates a builder to build a module descriptor for an open module. * An open module does not declare any open packages but the resulting * module is treated as if all packages are open. * *

As an example, the following creates a module descriptor for an open * name "{@code m}" containing two packages, one of which is exported.

*
{@code
     *     ModuleDescriptor descriptor = ModuleDescriptor.openModule("m")
     *         .requires("java.base")
     *         .exports("p")
     *         .contains("q")
     *         .build();
     * }
* * @param name * The module name * * @return A new builder that builds an open module * * @throws IllegalArgumentException * If the module name is {@code null} or is not a legal Java * identifier */ public static Builder openModule(String name) { return new Builder(name, true).open(true); } /** * Instantiates a builder to build a module descriptor for an automatic * module. Automatic modules receive special treatment during resolution * (see {@link Configuration}) so that they read all other modules. When * Instantiated in the Java virtual machine as a {@link java.lang.reflect.Module} * then the Module reads every unnamed module in the Java virtual machine. * * @param name * The module name * * @return A new builder that builds an automatic module * * @throws IllegalArgumentException * If the module name is {@code null} or is not a legal Java * identifier * * @see ModuleFinder#of(Path[]) */ public static Builder automaticModule(String name) { return new Builder(name, true).automatic(true); } /** * Reads the binary form of a module declaration from an input stream * as a module descriptor. * *

If the descriptor encoded in the input stream does not indicate a * set of packages in the module then the {@code packageFinder} will be * invoked. If the {@code packageFinder} throws an {@link UncheckedIOException} * then {@link IOException} cause will be re-thrown.

* *

If there are bytes following the module descriptor then it is * implementation specific as to whether those bytes are read, ignored, * or reported as an {@code InvalidModuleDescriptorException}. If this * method fails with an {@code InvalidModuleDescriptorException} or {@code * IOException} then it may do so after some, but not all, bytes have * been read from the input stream. It is strongly recommended that the * stream be promptly closed and discarded if an exception occurs.

* * @apiNote The {@code packageFinder} parameter is for use when reading * module descriptors from legacy module-artifact formats that do not * record the set of packages in the descriptor itself. * * @param in * The input stream * @param packageFinder * A supplier that can produce the set of packages * * @return The module descriptor * * @throws InvalidModuleDescriptorException * If an invalid module descriptor is detected * @throws IOException * If an I/O error occurs reading from the input stream or {@code * UncheckedIOException} is thrown by the package finder */ public static ModuleDescriptor read(InputStream in, Supplier> packageFinder) throws IOException { return ModuleInfo.read(in, requireNonNull(packageFinder)); } /** * Reads the binary form of a module declaration from an input stream * as a module descriptor. * * @param in * The input stream * * @return The module descriptor * * @throws InvalidModuleDescriptorException * If an invalid module descriptor is detected * @throws IOException * If an I/O error occurs reading from the input stream */ public static ModuleDescriptor read(InputStream in) throws IOException { return ModuleInfo.read(in, null); } /** * Reads the binary form of a module declaration from a byte buffer * as a module descriptor. * *

If the descriptor encoded in the byte buffer does not indicate a * set of packages then the {@code packageFinder} will be invoked.

* *

The module descriptor is read from the buffer stating at index * {@code p}, where {@code p} is the buffer's {@link ByteBuffer#position() * position} when this method is invoked. Upon return the buffer's position * will be equal to {@code p + n} where {@code n} is the number of bytes * read from the buffer.

* *

If there are bytes following the module descriptor then it is * implementation specific as to whether those bytes are read, ignored, * or reported as an {@code InvalidModuleDescriptorException}. If this * method fails with an {@code InvalidModuleDescriptorException} then it * may do so after some, but not all, bytes have been read.

* * @apiNote The {@code packageFinder} parameter is for use when reading * module descriptors from legacy module-artifact formats that do not * record the set of packages in the descriptor itself. * * @param bb * The byte buffer * @param packageFinder * A supplier that can produce the set of packages * * @return The module descriptor * * @throws InvalidModuleDescriptorException * If an invalid module descriptor is detected */ public static ModuleDescriptor read(ByteBuffer bb, Supplier> packageFinder) { return ModuleInfo.read(bb, requireNonNull(packageFinder)); } /** * Reads the binary form of a module declaration from a byte buffer * as a module descriptor. * * @param bb * The byte buffer * * @return The module descriptor * * @throws InvalidModuleDescriptorException * If an invalid module descriptor is detected */ public static ModuleDescriptor read(ByteBuffer bb) { return ModuleInfo.read(bb, null); } private static Map emptyOrUnmodifiableMap(Map map) { if (map.isEmpty()) { return Collections.emptyMap(); } else if (map.size() == 1) { Map.Entry entry = map.entrySet().iterator().next(); return Collections.singletonMap(entry.getKey(), entry.getValue()); } else { return Collections.unmodifiableMap(map); } } private static Set emptyOrUnmodifiableSet(Set set) { if (set.isEmpty()) { return Collections.emptySet(); } else if (set.size() == 1) { return Collections.singleton(set.iterator().next()); } else { return Collections.unmodifiableSet(set); } } /** * Returns a string containing the given set of modifiers and label. */ private static String toString(Set mods, String what) { return (Stream.concat(mods.stream().map(e -> e.toString().toLowerCase()), Stream.of(what))) .collect(Collectors.joining(" ")); } static { /** * Setup the shared secret to allow code in other packages access * private package methods in java.lang.module. */ jdk.internal.misc.SharedSecrets .setJavaLangModuleAccess(new jdk.internal.misc.JavaLangModuleAccess() { @Override public Builder newModuleBuilder(String mn, boolean strict) { return new Builder(mn, strict); } @Override public Builder newOpenModuleBuilder(String mn, boolean strict) { return new Builder(mn, strict).open(true); } @Override public Requires newRequires(Set ms, String mn) { return new Requires(ms, mn, true); } @Override public Exports newExports(Set ms, String source) { return new Exports(ms, source, Collections.emptySet(), true); } @Override public Exports newExports(Set ms, String source, Set targets) { return new Exports(ms, source, targets, true); } @Override public Opens newOpens(Set ms, String source, Set targets) { return new Opens(ms, source, targets, true); } @Override public Opens newOpens(Set ms, String source) { return new Opens(ms, source, Collections.emptySet(), true); } @Override public Provides newProvides(String service, List providers) { return new Provides(service, providers, true); } @Override public Version newVersion(String v) { return new Version(v); } @Override public ModuleDescriptor newModuleDescriptor(ModuleDescriptor md, Set pkgs) { return new ModuleDescriptor(md, pkgs); } @Override public ModuleDescriptor newModuleDescriptor(String name, boolean open, boolean automatic, boolean synthetic, Set requires, Set exports, Set opens, Set uses, Set provides, Version version, String mainClass, String osName, String osArch, String osVersion, Set packages, ModuleHashes hashes, int hashCode, boolean doNotResolveByDefault, WarnIfResolvedReason reason) { return new ModuleDescriptor(name, open, automatic, synthetic, requires, exports, opens, uses, provides, version, mainClass, osName, osArch, osVersion, packages, hashes, hashCode, doNotResolveByDefault, reason, false); } @Override public Optional hashes(ModuleDescriptor descriptor) { return descriptor.hashes(); } @Override public Configuration resolveRequiresAndUses(ModuleFinder finder, Collection roots, boolean check, PrintStream traceOutput) { return Configuration.resolveRequiresAndUses(finder, roots, check, traceOutput); } @Override public ModuleReference newPatchedModule(ModuleDescriptor descriptor, URI location, Supplier s) { return new ModuleReference(descriptor, location, s, true, null); } @Override public boolean doNotResolveByDefault(ModuleDescriptor descriptor) { return descriptor.doNotResolveByDefault; } @Override public WarnIfResolvedReason warnIfResolvedReason(ModuleDescriptor descriptor) { return descriptor.warnIfResolvedReason; } @Override public ModuleFinder newModulePath(Runtime.Version version, boolean isLinkPhase, Path... entries) { return new ModulePath(version, isLinkPhase, entries); } }); } }