/* * Copyright (c) 2009, 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 java.lang.module; import java.io.InputStream; import java.io.IOException; import java.io.PrintStream; import java.io.UncheckedIOException; 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.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.TreeSet; 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.ModuleInfo; /** * A module descriptor. * *

A module descriptor describes a named module and defines methods to * obtain each of its components. The module descriptor for a named module * in the Java virtual machine is obtained by invoking the {@link * java.lang.reflect.Module Module}'s {@link java.lang.reflect.Module#getDescriptor * getDescriptor} method. Module descriptors can also be created using the * {@link ModuleDescriptor.Builder} class or by reading the binary form of a * module declaration ({@code module-info.class}) using the {@link * #read(InputStream,Supplier) read} methods defined here.

* *

A module descriptor describes a normal, open, or automatic * module. Normal modules and open modules describe their {@link * #requires() dependences}, {@link #exports() exported-packages}, the services * that they {@link #uses() use} or {@link #provides() provide}, and other * components. Normal modules may {@link #opens() open} specific * packages. The module descriptor for an open modules does not declare any * open packages (its {@code opens} method returns an empty set) but when * instantiated in the Java virtual machine then it is treated as if all * packages are open. The module descriptor for an automatic module does not * declare any dependences (except for the mandatory dependency on {@code * java.base}), and does not declare any exported or open packages. Automatic * module receive special treatment during resolution so that they read all * other modules in the configuration. When an automatic module is instantiated * in the Java virtual machine then it reads every unnamed module and is * treated as if all packages are exported and open.

* *

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

* * @see java.lang.reflect.Module * @since 9 * @spec JPMS */ public class ModuleDescriptor implements Comparable { /** * A modifier on a module. * * @see ModuleDescriptor#modifiers() * @since 9 */ public static enum Modifier { /** * An open module. An open module does not declare any open packages * but the resulting module is treated as if all packages are open. */ OPEN, /** * An automatic module. An automatic module is treated as if it exports * and opens all packages. * * @apiNote This modifier does not correspond to a module flag in the * binary form of a module declaration ({@code module-info.class}). */ AUTOMATIC, /** * The module was not explicitly or implicitly declared. */ SYNTHETIC, /** * The module was implicitly declared. */ MANDATED; } /** *

A dependence upon a module

* * @see ModuleDescriptor#requires() * @since 9 * @spec JPMS */ public final static class Requires implements Comparable { /** * A modifier on a module dependence. * * @see Requires#modifiers() * @since 9 * @spec JPMS */ 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 final Version compiledVersion; private Requires(Set ms, String mn, Version v) { if (ms.isEmpty()) { ms = Collections.emptySet(); } else { ms = Collections.unmodifiableSet(EnumSet.copyOf(ms)); } this.mods = ms; this.name = mn; this.compiledVersion = v; } private Requires(Set ms, String mn, Version v, boolean unused) { this.mods = ms; this.name = mn; this.compiledVersion = v; } /** * 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; } /** * Returns the version of the module if recorded at compile-time. * * @return The version of the module if recorded at compile-time */ public Optional compiledVersion() { return Optional.ofNullable(compiledVersion); } /** * Compares this module dependence to another. * *

Two {@code Requires} objects are compared by comparing their * module names lexicographically. Where the module names are equal * then the sets of modifiers are compared in the same way that * module modifiers are compared (see {@link ModuleDescriptor#compareTo * ModuleDescriptor.compareTo}). Where the module names are equal and * the set of modifiers are equal then the version of the modules * recorded at compile-time are compared. When comparing the versions * recorded at compile-time then a dependence that has a recorded * version is considered to succeed a dependence that does not have a * recorded version.

* * @param that * The module dependence to compare * * @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) { if (this == that) return 0; int c = this.name().compareTo(that.name()); if (c != 0) return c; // modifiers long v1 = modsValue(this.modifiers()); long v2 = modsValue(that.modifiers()); c = Long.compare(v1, v2); if (c != 0) return c; // compiledVersion c = compare(this.compiledVersion, that.compiledVersion); if (c != 0) return c; return 0; } /** * 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, set of modifiers are equal, and the * compiled version of both modules is equal or not recorded for * both modules.

* *

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) && Objects.equals(compiledVersion, that.compiledVersion); } /** * Computes a hash code for this module dependence. * *

The hash code is based upon the module name, modifiers, and the * module version if recorded at compile time. 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() { int hash = name.hashCode() * 43 + mods.hashCode(); if (compiledVersion != null) hash = hash * 43 + compiledVersion.hashCode(); return hash; } /** * Returns a string describing this module dependence. * * @return A string describing this module dependence */ @Override public String toString() { String what; if (compiledVersion != null) { what = name() + " (@" + compiledVersion + ")"; } else { what = name(); } return ModuleDescriptor.toString(mods, what); } } /** *

A package exported by a module, may be qualified or unqualified.

* * @see ModuleDescriptor#exports() * @since 9 * @spec JPMS */ public final static class Exports implements Comparable { /** * A modifier on an exported package. * * @see Exports#modifiers() * @since 9 * @spec JPMS */ 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; } /** * Compares this module export to another. * *

Two {@code Exports} objects are compared by comparing the package * names lexicographically. Where the packages names are equal then the * sets of modifiers are compared in the same way that module modifiers * are compared (see {@link ModuleDescriptor#compareTo * ModuleDescriptor.compareTo}). Where the package names are equal and * the set of modifiers are equal then the set of target modules are * compared. This is done by sorting the sets, iterating over both sets * in ascending order, and comparing the corresponding elements * lexicographically. Where the sets differ in size, and the larger set * contains all elements of the smaller set, then the larger set is * considered to succeed the smaller set.

* * @param that * The module export to compare * * @return A negative integer, zero, or a positive integer if this module * export is less than, equal to, or greater than the given * export dependence */ @Override public int compareTo(Exports that) { if (this == that) return 0; int c = source.compareTo(that.source); if (c != 0) return c; // modifiers long v1 = modsValue(this.modifiers()); long v2 = modsValue(that.modifiers()); c = Long.compare(v1, v2); if (c != 0) return c; // targets c = compare(targets, that.targets); if (c != 0) return c; return 0; } /** * 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 the exported package. * * @return A string describing the exported package */ @Override public String toString() { String s = ModuleDescriptor.toString(mods, source); if (targets.isEmpty()) return s; else return s + " to " + targets; } } /** *

A package opened by a module, 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 * @spec JPMS */ public final static class Opens implements Comparable { /** * A modifier on an open package. * * @see Opens#modifiers() * @since 9 * @spec JPMS */ public static enum Modifier { /** * The open package was not explicitly or implicitly declared in * the source of the module declaration. */ SYNTHETIC, /** * The open package 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; } /** * Compares this module opens to another. * *

Two {@code Opens} objects are compared by comparing the package * names lexicographically. Where the packages names are equal then the * sets of modifiers are compared in the same way that module modifiers * are compared (see {@link ModuleDescriptor#compareTo * ModuleDescriptor.compareTo}). Where the package names are equal and * the set of modifiers are equal then the set of target modules are * compared. This is done by sorting the sets, iterating over both sets * in ascending order, and comparing the corresponding elements * lexicographically. Where the sets differ in size, and the larger set * contains all elements of the smaller set, then the larger set is * considered to succeed the smaller set.

* * @param that * The module opens to compare * * @return A negative integer, zero, or a positive integer if this module * opens is less than, equal to, or greater than the given * module opens */ @Override public int compareTo(Opens that) { if (this == that) return 0; int c = source.compareTo(that.source); if (c != 0) return c; // modifiers long v1 = modsValue(this.modifiers()); long v2 = modsValue(that.modifiers()); c = Long.compare(v1, v2); if (c != 0) return c; // targets c = compare(targets, that.targets); if (c != 0) return c; return 0; } /** * 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 the open package. * * @return A string describing the open package */ @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 * @spec JPMS */ public final static class Provides implements Comparable { 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; } /** * Compares this provides to another. * *

Two {@code Provides} objects are compared by comparing the fully * qualified class name of the service type lexicographically. Where the * class names are equal then the list of the provider class names are * compared by comparing the corresponding elements of both lists * lexicographically and in sequence. Where the lists differ in size, * {@code N} is the size of the shorter list, and the first {@code N} * corresponding elements are equal, then the longer list is considered * to succeed the shorter list.

* * @param that * The {@code Provides} to compare * * @return A negative integer, zero, or a positive integer if this provides * is less than, equal to, or greater than the given provides */ public int compareTo(Provides that) { if (this == that) return 0; int c = service.compareTo(that.service); if (c != 0) return c; // compare provider class names in sequence int size1 = this.providers.size(); int size2 = that.providers.size(); for (int index=0; index size2) ? 1 : -1; } } /** * 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 a 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 * @spec JPMS */ 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; } } private final String name; private final Version version; private final Set modifiers; private final boolean open; // true if modifiers contains OPEN private final boolean automatic; // true if modifiers contains AUTOMATIC private final Set requires; private final Set exports; private final Set opens; private final Set uses; private final Set provides; private final Set packages; private final String mainClass; private final String osName; private final String osArch; private final String osVersion; private ModuleDescriptor(String name, Version version, Set modifiers, Set requires, Set exports, Set opens, Set uses, Set provides, Set packages, String mainClass, String osName, String osArch, String osVersion) { this.name = name; this.version = version; this.modifiers = emptyOrUnmodifiableSet(modifiers); this.open = modifiers.contains(Modifier.OPEN); this.automatic = modifiers.contains(Modifier.AUTOMATIC); 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.packages = emptyOrUnmodifiableSet(packages); this.mainClass = mainClass; this.osName = osName; this.osArch = osArch; this.osVersion = osVersion; } /** * Creates a module descriptor from its components. * The arguments are pre-validated and sets are unmodifiable sets. */ ModuleDescriptor(String name, Version version, Set modifiers, Set requires, Set exports, Set opens, Set uses, Set provides, Set packages, String mainClass, String osName, String osArch, String osVersion, int hashCode, boolean unused) { this.name = name; this.version = version; this.modifiers = modifiers; this.open = modifiers.contains(Modifier.OPEN); this.automatic = modifiers.contains(Modifier.AUTOMATIC); this.requires = requires; this.exports = exports; this.opens = opens; this.uses = uses; this.provides = provides; this.packages = packages; this.mainClass = mainClass; this.osName = osName; this.osArch = osArch; this.osVersion = osVersion; this.hash = hashCode; } /** *

Returns the module name.

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

Returns the set of module modifiers.

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

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

* *

This method is equivalent to testing if the set of {@link #modifiers * modifiers} contains the {@link Modifier#OPEN OPEN} modifier.

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

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

* *

This method is equivalent to testing if the set of {@link #modifiers * modifiers} contains the {@link Modifier#OPEN AUTOMATIC} modifier.

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

Returns the set of module dependences.

* *

The set includes a dependency on "{@code java.base}" when this * module is not named "{@code java.base}". If this module is an automatic * module then it does not have a dependency on any module other than * "{@code java.base}".

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

Returns the set of exported packages.

* *

If this module is an automatic module then the set of exports * is empty.

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

Returns the set of open packages.

* *

If this module is an open module or an automatic module then the * set of open packages is empty.

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

Returns the set of service dependences.

* *

If this module is an automatic module then the set of service * dependences is empty.

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

Returns the set of services that the module provides.

* * @return The possibly-empty unmodifiable set of the services that this * module provides */ public Set provides() { return provides; } /** *

Returns the module version.

* * @return This module's version */ public Optional version() { return Optional.ofNullable(version); } /** *

Returns a string containing the module name and, if present, its * version.

* * @return A string containing the module name and, if present, its * version. */ public String toNameAndVersion() { if (version != null) { return name() + "@" + version; } else { return name(); } } /** *

Returns the module main class.

* * @return The fully qualified class name of the module's main class */ public Optional mainClass() { return Optional.ofNullable(mainClass); } /** * Returns the operating system name if the module is operating system * specific. * * @return The operating system name or an empty {@code Optional} * if the module is not operating system specific */ public Optional osName() { return Optional.ofNullable(osName); } /** * Returns the operating system architecture if the module is operating * system architecture specific. * * @return The operating system architecture or an empty {@code Optional} * if the module is not operating system architecture specific */ public Optional osArch() { return Optional.ofNullable(osArch); } /** * Returns the operating system version if the module is operating * system version specific. * * @return The operating system version or an empty {@code Optional} * if the module is not operating system version specific */ public Optional osVersion() { return Optional.ofNullable(osVersion); } /** * Returns the set of packages in the module. * * @return A possibly-empty unmodifiable set of the packages in the module */ public Set packages() { return packages; } /** * A builder for building {@link ModuleDescriptor} objects. * *

{@code ModuleDescriptor} defines the {@link #newModule newModule}, * {@link #newOpenModule newOpenModule}, and {@link #newAutomaticModule * newAutomaticModule} methods to create builders for building * normal, open, and automatic modules.

* *

The set of packages in the module are accumulated by the {@code * Builder} as the {@link ModuleDescriptor.Builder#exports(String) exports}, * {@link ModuleDescriptor.Builder#opens(String) opens}, * {@link ModuleDescriptor.Builder#packages(Set) packages}, * {@link ModuleDescriptor.Builder#provides(String,List) provides}, and * {@link ModuleDescriptor.Builder#mainClass(String) mainClass} methods are * invoked.

* *

The module names, package names, and class names that are parameters * specified to the builder methods are the module names, package names, * and qualified names of classes (in named packages) as defined in the * The Java™ Language Specification.

* *

Example usage:

*
{@code    ModuleDescriptor descriptor = ModuleDescriptor.newModule("stats.core")
     *         .requires("java.base")
     *         .exports("org.acme.stats.core.clustering")
     *         .exports("org.acme.stats.core.regression")
     *         .packages(Set.of("org.acme.stats.core.internal"))
     *         .build();
     * }
* * @apiNote A {@code Builder} checks the components and invariants as * components are added to the builder. The rationale for this is to detect * errors as early as possible and not defer all validation to the * {@link #build build} method. * * @since 9 * @spec JPMS */ public static final class Builder { final String name; final boolean strict; final Set modifiers; final boolean open; final boolean automatic; final Set packages = new HashSet<>(); final Map requires = new HashMap<>(); final Map exports = new HashMap<>(); final Map opens = new HashMap<>(); final Set uses = new HashSet<>(); final Map provides = new HashMap<>(); Version version; String osName; String osArch; String osVersion; String mainClass; /** * Initializes a new builder with the given module name. * * If {@code strict} is {@code true} then module, package, and class * names are checked to ensure they are legal names. In addition, the * {@link #build buid} method will add "{@code requires java.base}" if * the dependency is not declared. */ Builder(String name, boolean strict, Set modifiers) { this.name = (strict) ? requireModuleName(name) : name; this.strict = strict; this.modifiers = modifiers; this.open = modifiers.contains(Modifier.OPEN); this.automatic = modifiers.contains(Modifier.AUTOMATIC); assert !open || !automatic; } /** * Returns a snapshot of the packages in the module. */ /* package */ Set packages() { return Collections.unmodifiableSet(packages); } /** * 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 * or this builder is for an automatic module */ public Builder requires(Requires req) { if (automatic) throw new IllegalStateException("Automatic modules cannot declare" + " dependences"); 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. The dependence includes the version of the * module that that was recorded at compile-time. * * @param ms * The set of modifiers * @param mn * The module name * @param compiledVersion * The version of the module recorded at compile-time * * @return This builder * * @throws IllegalArgumentException * If the module name is {@code null}, is not a legal module * name, 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 * or this builder is for an automatic module */ public Builder requires(Set ms, String mn, Version compiledVersion) { Objects.requireNonNull(compiledVersion); if (strict) mn = requireModuleName(mn); return requires(new Requires(ms, mn, compiledVersion)); } /* package */Builder requires(Set ms, String mn, String compiledVersion) { Version v = null; try { v = Version.parse(compiledVersion); } catch (IllegalArgumentException e) { // for now, drop un-parsable version when non-strict if (strict) throw e; } if (v == null) { return requires(ms, mn); } else { return requires(ms, mn, v); } } /** * 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 module * name, 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 * or this builder is for an automatic module */ public Builder requires(Set ms, String mn) { if (strict) mn = requireModuleName(mn); return requires(new Requires(ms, mn, null)); } /** * 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 module * name, 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 * or this builder is for an automatic module */ public Builder requires(String mn) { return requires(EnumSet.noneOf(Requires.Modifier.class), mn); } /** * Adds an exported package. * * @param e * The export * * @return This builder * * @throws IllegalStateException * If the {@link Exports#source package} is already declared as * exported or this builder is for an automatic module */ public Builder exports(Exports e) { if (automatic) { throw new IllegalStateException("Automatic modules cannot declare" + " exported packages"); } String source = e.source(); if (exports.containsKey(source)) { throw new IllegalStateException("Exported package " + source + " already declared"); } exports.put(source, e); packages.add(source); return this; } /** * Adds an exported package with the given (and possibly empty) set of * modifiers. The package is exported 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 is {@code null} or is not a legal * package name, the set of target modules is empty, or the set * of target modules contains a name that is not a legal module * name * @throws IllegalStateException * If the package is already declared as exported * or this builder is for an automatic module */ public Builder exports(Set ms, String pn, Set targets) { Exports e = new Exports(ms, pn, targets); // check targets targets = e.targets(); if (targets.isEmpty()) throw new IllegalArgumentException("Empty target set"); if (strict) { requirePackageName(e.source()); targets.stream().forEach(Checks::requireModuleName); } return exports(e); } /** * Adds an exported package with the given (and possibly empty) set of * modifiers. The package is exported to all modules. * * @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 * package name * @throws IllegalStateException * If the package is already declared as exported * or this builder is for an automatic module */ public Builder exports(Set ms, String pn) { if (strict) { requirePackageName(pn); } Exports e = new Exports(ms, pn, Collections.emptySet()); return exports(e); } /** * Adds an exported package. The package is exported 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 is {@code null} or is not a legal * package name, the set of target modules is empty, or the set * of target modules contains a name that is not a legal module * name * @throws IllegalStateException * If the package is already declared as exported * or this builder is for an automatic module */ public Builder exports(String pn, Set targets) { return exports(Collections.emptySet(), pn, targets); } /** * Adds an exported package. The package is exported to all modules. * * @param pn * The package name * * @return This builder * * @throws IllegalArgumentException * If the package name is {@code null} or is not a legal * package name * @throws IllegalStateException * If the package is already declared as exported * or this builder is for an automatic module */ public Builder exports(String pn) { return exports(Collections.emptySet(), pn); } /** * Adds an open package. * * @param obj * The {@code Opens} object * * @return This builder * * @throws IllegalStateException * If the package is already declared as open, or this is a * builder for an open module or automatic module */ public Builder opens(Opens obj) { if (open || automatic) { throw new IllegalStateException("Open or automatic modules cannot" + " declare open packages"); } String source = obj.source(); if (opens.containsKey(source)) { throw new IllegalStateException("Open package " + source + " already declared"); } opens.put(source, obj); packages.add(source); return this; } /** * Adds an open package with the given (and possibly empty) set of * modifiers. The package is open 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 is {@code null} or is not a legal * package name, the set of target modules is empty, or the set * of target modules contains a name that is not a legal module * name * @throws IllegalStateException * If the package is already declared as open, or this is a * builder for an open module or automatic module */ public Builder opens(Set ms, String pn, Set targets) { Opens opens = new Opens(ms, pn, targets); // check targets targets = opens.targets(); if (targets.isEmpty()) throw new IllegalArgumentException("Empty target set"); if (strict) { requirePackageName(opens.source()); targets.stream().forEach(Checks::requireModuleName); } return opens(opens); } /** * Adds an open package with the given (and possibly empty) set of * modifiers. The package is open to all modules. * * @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 * package name * @throws IllegalStateException * If the package is already declared as open, or this is a * builder for an open module or automatic module */ public Builder opens(Set ms, String pn) { if (strict) { requirePackageName(pn); } Opens e = new Opens(ms, pn, Collections.emptySet()); return opens(e); } /** * Adds an open package. The package is open 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 is {@code null} or is not a legal * package name, the set of target modules is empty, or the set * of target modules contains a name that is not a legal module * name * @throws IllegalStateException * If the package is already declared as open, or this is a * builder for an open module or automatic module */ public Builder opens(String pn, Set targets) { return opens(Collections.emptySet(), pn, targets); } /** * Adds an open package. The package is open to all modules. * * @param pn * The package name * * @return This builder * * @throws IllegalArgumentException * If the package name is {@code null} or is not a legal * package name * @throws IllegalStateException * If the package is already declared as open, or this is a * builder for an open module or automatic module */ public Builder opens(String pn) { return opens(Collections.emptySet(), pn); } /** * Adds a service dependence. * * @param service * The service type * * @return This builder * * @throws IllegalArgumentException * If the service type is {@code null} or not a qualified name of * a class in a named package * @throws IllegalStateException * If a dependency on the service type has already been declared * or this is a builder for an an automatic module */ public Builder uses(String service) { if (automatic) throw new IllegalStateException("Automatic modules can not declare" + " service dependences"); 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. The package for * each {@link Provides#providers provider} (or provider factory) is * added to the module if not already added. * * @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 service = p.service(); if (provides.containsKey(service)) throw new IllegalStateException("Providers of service " + service + " already declared"); provides.put(service, p); p.providers().forEach(name -> packages.add(packageName(name))); return this; } /** * Provides implementations of a service. The package for each provider * (or provider factory) is added to the module if not already added. * * @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 not a qualified name of a class in a named * package, 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) { Provides p = new Provides(service, providers); // check providers after the set has been copied. List providerNames = p.providers(); if (providerNames.isEmpty()) throw new IllegalArgumentException("Empty providers set"); if (strict) { requireServiceTypeName(p.service()); providerNames.forEach(Checks::requireServiceProviderName); } else { // Disallow service/providers in unnamed package String pn = packageName(service); if (pn.isEmpty()) { throw new IllegalArgumentException(service + ": unnamed package"); } for (String name : providerNames) { pn = packageName(name); if (pn.isEmpty()) { throw new IllegalArgumentException(name + ": unnamed package"); } } } return provides(p); } /** * Adds packages to the module. All packages in the set of package names * that are not in the module are added to module. * * @param pns * The (possibly empty) set of package names * * @return This builder * * @throws IllegalArgumentException * If any of the package names is {@code null} or is not a * legal package name */ public Builder packages(Set pns) { if (strict) { pns = new HashSet<>(pns); pns.forEach(Checks::requirePackageName); } this.packages.addAll(pns); 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 vs * The version string to parse * * @return This builder * * @throws IllegalArgumentException * If {@code vs} is {@code null} or cannot be parsed as a * version string * * @see Version#parse(String) */ public Builder version(String vs) { Version v; if (strict) { v = Version.parse(vs); } else { try { v = Version.parse(vs); } catch (IllegalArgumentException ignore) { // for now, ignore when non-strict return this; } } return version(v); } /** * Sets the module main class. The package for the main class is added * to the module if not already added. * * @param mc * The module main class * * @return This builder * * @throws IllegalArgumentException * If {@code mainClass} is {@code null} or not a qualified * name of a class in a named package */ public Builder mainClass(String mc) { String pn; if (strict) { mc = requireQualifiedClassName("main class name", mc); pn = packageName(mc); assert !pn.isEmpty(); } else { // Disallow main class in unnamed package pn = packageName(mc); if (pn.isEmpty()) { throw new IllegalArgumentException(mc + ": unnamed package"); } } mainClass = mc; packages.add(pn); return this; } /** * Sets the operating system name. * * @param name * The operating system name * * @return This builder * * @throws IllegalArgumentException * If {@code name} is {@code 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 {@code 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 {@code 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; } /** * Builds and returns a {@code ModuleDescriptor} from its components. * *

The module will require "{@code java.base}" even if the dependence * has not been declared (the exception is when building a module named * "{@code java.base}" as it cannot require itself). The dependence on * "{@code java.base}" will have the {@link * java.lang.module.ModuleDescriptor.Requires.Modifier#MANDATED MANDATED} * modifier if the dependence was not declared.

* * @return The module descriptor */ public ModuleDescriptor build() { Set requires = new HashSet<>(this.requires.values()); Set exports = new HashSet<>(this.exports.values()); Set opens = new HashSet<>(this.opens.values()); // add dependency on java.base if (strict && !name.equals("java.base") && !this.requires.containsKey("java.base")) { requires.add(new Requires(Set.of(Requires.Modifier.MANDATED), "java.base", null)); } Set provides = new HashSet<>(this.provides.values()); return new ModuleDescriptor(name, version, modifiers, requires, exports, opens, uses, provides, packages, mainClass, osName, osArch, osVersion); } } /** * Compares this module descriptor to another. * *

Two {@code ModuleDescriptor} objects are compared by comparing their * module names lexicographically. Where the module names are equal then the * module versions are compared. When comparing the module versions then a * module descriptor with a version is considered to succeed a module * descriptor that does not have a version. Where the module names are equal * and the versions are equal (or not present in both), then the set of * modifiers are compared. Sets of modifiers are compared by comparing * a binary value computed for each set. If a modifier is present * in the set then the bit at the position of its ordinal is {@code 1} * in the binary value, otherwise {@code 0}. If the two set of modifiers * are also equal then the other components of the module descriptors are * compared in a manner that is consistent with {@code equals}.

* * @param that * The module descriptor to compare * * @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) { if (this == that) return 0; int c = this.name().compareTo(that.name()); if (c != 0) return c; c = compare(this.version, that.version); if (c != 0) return c; long v1 = modsValue(this.modifiers()); long v2 = modsValue(that.modifiers()); c = Long.compare(v1, v2); if (c != 0) return c; c = compare(this.requires, that.requires); if (c != 0) return c; c = compare(this.packages, that.packages); if (c != 0) return c; c = compare(this.exports, that.exports); if (c != 0) return c; c = compare(this.opens, that.opens); if (c != 0) return c; c = compare(this.uses, that.uses); if (c != 0) return c; c = compare(this.provides, that.provides); if (c != 0) return c; c = compare(this.mainClass, that.mainClass); if (c != 0) return c; c = compare(this.osName, that.osName); if (c != 0) return c; c = compare(this.osArch, that.osArch); if (c != 0) return c; c = compare(this.osVersion, that.osVersion); if (c != 0) return c; return 0; } /** * 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) && modifiers.equals(that.modifiers) && requires.equals(that.requires) && Objects.equals(packages, that.packages) && 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)); } /** * 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 + Objects.hashCode(modifiers); hc = hc * 43 + requires.hashCode(); hc = hc * 43 + Objects.hashCode(packages); 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); if (hc == 0) hc = -1; hash = hc; } return hc; } private transient int hash; // cached hash code /** *

Returns a string describing the module.

* * @return A string describing the module */ @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(", uses: ").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 * @param ms * The set of module modifiers * * @return A new builder * * @throws IllegalArgumentException * If the module name is {@code null} or is not a legal module * name, or the set of modifiers contains both {@link Modifier#OPEN * OPEN} and {@link Modifier#AUTOMATIC AUTOMATIC} */ public static Builder newModule(String name, Set ms) { Set mods = new HashSet<>(ms); if (mods.contains(Modifier.OPEN) && mods.contains(Modifier.AUTOMATIC)) throw new IllegalArgumentException("OPEN and AUTOMATIC not allowed"); return new Builder(name, true, mods); } /** * 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 module * name */ public static Builder newModule(String name) { return new Builder(name, true, Set.of()); } /** * Instantiates a builder to build a module descriptor for an open module. * *

The builder for an open module cannot be used to declare any open * packages.

* * @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 module * name */ public static Builder newOpenModule(String name) { return new Builder(name, true, Set.of(Modifier.OPEN)); } /** * Instantiates a builder to build a module descriptor for an automatic * module. * *

The builder for an automatic module cannot be used to declare module * or service dependences. It also cannot be used to declare any exported * or open packages.

* * @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 module * name * * @see ModuleFinder#of(Path[]) */ public static Builder newAutomaticModule(String name) { return new Builder(name, true, Set.of(Modifier.AUTOMATIC)); } /** * 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. The set of packages that the {@code packageFinder} returns * must include all the packages that the module exports, opens, as well * as the packages of the service implementations that the module provides, * and the package of the main class (if the module has a main class). 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 or the set of * packages returned by the {@code packageFinder} does not include * all of the packages obtained from the module descriptor * @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)).descriptor(); } /** * Reads the binary form of a module declaration from an input stream as a * module descriptor. This method works exactly as specified by the 2-arg * {@link #read(InputStream,Supplier) read} method with the exception that * a packager finder is not used to find additional packages when the * module descriptor read from the stream does not indicate the set of * packages. * * @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).descriptor(); } /** * 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 in the module then the {@code packageFinder} will be * invoked. The set of packages that the {@code packageFinder} returns * must include all the packages that the module exports, opens, as well * as the packages of the service implementations that the module provides, * and the package of the main class (if the module has a main class). If * the {@code packageFinder} throws an {@link UncheckedIOException} then * {@link IOException} cause will be re-thrown.

* *

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 or the set of * packages returned by the {@code packageFinder} does not include * all of the packages obtained from the module descriptor */ public static ModuleDescriptor read(ByteBuffer bb, Supplier> packageFinder) { return ModuleInfo.read(bb, requireNonNull(packageFinder)).descriptor(); } /** * Reads the binary form of a module declaration from a byte buffer as a * module descriptor. This method works exactly as specified by the 2-arg * {@link #read(ByteBuffer,Supplier) read} method with the exception that a * packager finder is not used to find additional packages when the module * descriptor encoded in the buffer does not indicate the set of packages. * * @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).descriptor(); } 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); } } private static String packageName(String cn) { int index = cn.lastIndexOf('.'); return (index == -1) ? "" : cn.substring(0, index); } /** * 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(" ")); } private static > int compare(T obj1, T obj2) { if (obj1 != null) { return (obj2 != null) ? obj1.compareTo(obj2) : 1; } else { return (obj2 == null) ? 0 : -1; } } /** * Compares two sets of {@code Comparable} objects. */ private static > int compare(Set s1, Set s2) { Iterator iterator1 = new TreeSet<>(s1).iterator(); Iterator iterator2 = new TreeSet<>(s2).iterator(); while (iterator1.hasNext()) { if (!iterator2.hasNext()) return 1; // s1 has more elements T e1 = iterator1.next(); T e2 = iterator2.next(); int c = e1.compareTo(e2); if (c != 0) return c; } if (iterator2.hasNext()) { return -1; // s2 has more elements } else { return 0; } } private static > long modsValue(Set set) { long value = 0; for (Enum e : set) { value += 1 << e.ordinal(); } return value; } 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, Set modifiers) { return new Builder(mn, strict, modifiers); } @Override public Set packages(ModuleDescriptor.Builder builder) { return builder.packages(); } @Override public void requires(ModuleDescriptor.Builder builder, Set ms, String mn, String compiledVersion) { builder.requires(ms, mn, compiledVersion); } @Override public Requires newRequires(Set ms, String mn, Version v) { return new Requires(ms, mn, v, 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 ModuleDescriptor newModuleDescriptor(String name, Version version, Set modifiers, Set requires, Set exports, Set opens, Set uses, Set provides, Set packages, String mainClass, String osName, String osArch, String osVersion, int hashCode) { return new ModuleDescriptor(name, version, modifiers, requires, exports, opens, uses, provides, packages, mainClass, osName, osArch, osVersion, hashCode, false); } @Override public Configuration resolveAndBind(ModuleFinder finder, Collection roots, boolean check, PrintStream traceOutput) { return Configuration.resolveAndBind(finder, roots, check, traceOutput); } }); } }