From 09d05284e87a7113b94fb47a0ce72a696aada7e2 Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Wed, 31 Jul 2024 05:22:18 -0500 Subject: [PATCH] fix remaining api-internal "missing" warnings --- .../j12/stub/java_base/J_L_C_ClassDesc.java | 222 +++++ .../stub/java_base/J_L_C_ConstantDesc.java | 10 + .../stub/java_base/J_L_I_TypeDescriptor.java | 50 + .../wagyourtail/jvmdg/j9/intl/NameChecks.java | 146 +++ .../java_base/J_L_M_ModuleDescriptor.java | 903 ++++++++++++++++++ .../jvmdg/providers/Java12Downgrader.java | 3 + .../jvmdg/providers/Java9Downgrader.java | 2 +- 7 files changed, 1335 insertions(+), 1 deletion(-) create mode 100644 java-api/src/java12/java/xyz/wagyourtail/jvmdg/j12/stub/java_base/J_L_C_ClassDesc.java create mode 100644 java-api/src/java12/java/xyz/wagyourtail/jvmdg/j12/stub/java_base/J_L_C_ConstantDesc.java create mode 100644 java-api/src/java12/java/xyz/wagyourtail/jvmdg/j12/stub/java_base/J_L_I_TypeDescriptor.java create mode 100644 java-api/src/java9/java/xyz/wagyourtail/jvmdg/j9/intl/NameChecks.java create mode 100644 java-api/src/java9/java/xyz/wagyourtail/jvmdg/j9/stub/java_base/J_L_M_ModuleDescriptor.java diff --git a/java-api/src/java12/java/xyz/wagyourtail/jvmdg/j12/stub/java_base/J_L_C_ClassDesc.java b/java-api/src/java12/java/xyz/wagyourtail/jvmdg/j12/stub/java_base/J_L_C_ClassDesc.java new file mode 100644 index 00000000..8bde1249 --- /dev/null +++ b/java-api/src/java12/java/xyz/wagyourtail/jvmdg/j12/stub/java_base/J_L_C_ClassDesc.java @@ -0,0 +1,222 @@ +package xyz.wagyourtail.jvmdg.j12.stub.java_base; + +import xyz.wagyourtail.jvmdg.version.Adapter; + +import java.lang.invoke.MethodHandles; +import java.util.Objects; + +import static java.util.Objects.requireNonNull; + +@Adapter("java/lang/constant/ClassDesc") +public interface J_L_C_ClassDesc extends J_L_C_ConstantDesc, J_L_I_TypeDescriptor.OfField { + + private static void validateBinaryClassName(String name) { + for (int i=0; i 255) { + throw new IllegalArgumentException("Cannot create an array type descriptor with more than 255 dimensions"); + } + return new ClassDescImpl(descriptor); + } + + default J_L_C_ClassDesc arrayType() { + int arrayCount = 0; + String descriptor = descriptorString(); + while (descriptor.charAt(arrayCount) == '[') { + arrayCount++; + } + if (arrayCount >= 255) { + throw new IllegalArgumentException("Cannot create an array type descriptor with more than 255 dimensions"); + } + return ofDescriptor("[" + descriptor); + } + + default J_L_C_ClassDesc arrayType(int rank) { + if (rank <= 0) { + throw new IllegalArgumentException("rank " + rank + " is not a positive value"); + } + int arrayCount = 0; + String descriptor = descriptorString(); + while (descriptor.charAt(arrayCount) == '[') { + arrayCount++; + } + if (arrayCount + rank > 255) { + throw new IllegalArgumentException("Cannot create an array type descriptor with more than 255 dimensions"); + } + return ofDescriptor("[".repeat(rank) + descriptorString()); + } + + default J_L_C_ClassDesc nested(String nestedName) { + validateMemberName(nestedName); + if (!isClassOrInterface()) { + throw new IllegalStateException("Outer class is not a class or interface type"); + } + String descriptor = descriptorString(); + return ofDescriptor(descriptor.substring(0, descriptor.length() - 1) + "$" + nestedName + ";"); + } + + default J_L_C_ClassDesc nested(String first, String... rest) { + StringBuilder name = new StringBuilder(first); + for (String s : rest) { + name.append("$").append(s); + } + return nested(name.toString()); + } + + default boolean isArray() { + return descriptorString().charAt(0) == '['; + } + + default boolean isPrimitive() { + return descriptorString().length() == 1; + } + + default boolean isClassOrInterface() { + return descriptorString().startsWith("L"); + } + + default J_L_C_ClassDesc componentType() { + if (!isArray()) return null; + return ofDescriptor(descriptorString().substring(1)); + } + + default String packageName() { + if (!isClassOrInterface()) { + return ""; + } + String descriptor = descriptorString().substring(1); + int slash = descriptor.lastIndexOf('/'); + if (slash == -1) { + return ""; + } + return descriptor.substring(0, slash).replace('/', '.'); + } + + default String displayName() { + if (isPrimitive()) { + switch (descriptorString()) { + case "B": return "byte"; + case "C": return "char"; + case "D": return "double"; + case "F": return "float"; + case "I": return "int"; + case "J": return "long"; + case "S": return "short"; + case "Z": return "boolean"; + case "V": return "void"; + default: throw new InternalError("Unexpected primitive type descriptor: " + descriptorString()); + } + } + if (isArray()) { + int arrayCount = 0; + String descriptor = descriptorString(); + while (descriptor.charAt(arrayCount) == '[') { + arrayCount++; + } + String component = descriptor.substring(arrayCount); + return J_L_C_ClassDesc.ofDescriptor(component).displayName() + "[]".repeat(arrayCount); + } else { + String descriptor = descriptorString(); + int lastSlash = descriptor.lastIndexOf('/'); + if (lastSlash == -1) { + return descriptor.substring(1, descriptor.length() - 1); + } else { + return descriptor.substring(lastSlash + 1, descriptor.length() - 1); + } + } + } + + @Override + Class resolveConstantDesc(MethodHandles.Lookup lookup) throws ReflectiveOperationException; + + boolean equals(Object obj); + + class ClassDescImpl implements J_L_C_ClassDesc { + private final String descriptor; + + private ClassDescImpl(String descriptor) { + this.descriptor = descriptor; + } + + @Override + public Class resolveConstantDesc(MethodHandles.Lookup lookup) throws ReflectiveOperationException { + if (isPrimitive()) { + switch (descriptorString()) { + case "B": return byte.class; + case "C": return char.class; + case "D": return double.class; + case "F": return float.class; + case "I": return int.class; + case "J": return long.class; + case "S": return short.class; + case "Z": return boolean.class; + case "V": return void.class; + default: throw new InternalError("Unexpected primitive type descriptor: " + descriptorString()); + } + } + if (isArray()) { + int arrayCount = 0; + String descriptor = descriptorString(); + while (descriptor.charAt(arrayCount) == '[') { + arrayCount++; + } + String component = descriptor.substring(arrayCount); + Class componentClass = J_L_C_ClassDesc.ofDescriptor(component).resolveConstantDesc(lookup); + return Class.forName(componentClass.getName() + "[]".repeat(arrayCount), false, lookup.lookupClass().getClassLoader()); + } else { + String descriptor = descriptorString(); + String className = descriptor.substring(1, descriptor.length() - 1).replace('/', '.'); + return Class.forName(className, false, lookup.lookupClass().getClassLoader()); + } + } + + @Override + public String descriptorString() { + return descriptor; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof J_L_C_ClassDesc)) return false; + return descriptor.equals(((J_L_C_ClassDesc) obj).descriptorString()); + } + } +} diff --git a/java-api/src/java12/java/xyz/wagyourtail/jvmdg/j12/stub/java_base/J_L_C_ConstantDesc.java b/java-api/src/java12/java/xyz/wagyourtail/jvmdg/j12/stub/java_base/J_L_C_ConstantDesc.java new file mode 100644 index 00000000..15469b4e --- /dev/null +++ b/java-api/src/java12/java/xyz/wagyourtail/jvmdg/j12/stub/java_base/J_L_C_ConstantDesc.java @@ -0,0 +1,10 @@ +package xyz.wagyourtail.jvmdg.j12.stub.java_base; + +import xyz.wagyourtail.jvmdg.version.Adapter; + +import java.lang.invoke.MethodHandles; + +@Adapter("java/lang/constant/ConstantDesc") +public interface J_L_C_ConstantDesc { + Object resolveConstantDesc(MethodHandles.Lookup lookup) throws ReflectiveOperationException; +} diff --git a/java-api/src/java12/java/xyz/wagyourtail/jvmdg/j12/stub/java_base/J_L_I_TypeDescriptor.java b/java-api/src/java12/java/xyz/wagyourtail/jvmdg/j12/stub/java_base/J_L_I_TypeDescriptor.java new file mode 100644 index 00000000..6cbf7c68 --- /dev/null +++ b/java-api/src/java12/java/xyz/wagyourtail/jvmdg/j12/stub/java_base/J_L_I_TypeDescriptor.java @@ -0,0 +1,50 @@ +package xyz.wagyourtail.jvmdg.j12.stub.java_base; + +import xyz.wagyourtail.jvmdg.version.Adapter; + +import java.util.List; + +@Adapter("java/lang/invoke/TypeDescriptor") +public interface J_L_I_TypeDescriptor { + + String descriptorString(); + + @Adapter("java/lang/invoke/TypeDescriptor$OfField") + interface OfField> extends J_L_I_TypeDescriptor { + + boolean isArray(); + + boolean isPrimitive(); + + T componentType(); + + T arrayType(); + + } + + @Adapter("java/lang/invoke/TypeDescriptor$OfMethod") + interface OfMethod, M extends J_L_I_TypeDescriptor.OfMethod> extends J_L_I_TypeDescriptor { + + int parameterCount(); + + F parameterType(int index); + + F returnType(); + + F[] parameterArray(); + + List parameterList(); + + M changeReturnType(F newReturnType); + + M changeParameterType(int index, F newParameterType); + + M dropParameterTypes(int fromIndex, int toIndex); + + M insertParameterTypes(int index, F... newParameterTypes); + + } + + + +} diff --git a/java-api/src/java9/java/xyz/wagyourtail/jvmdg/j9/intl/NameChecks.java b/java-api/src/java9/java/xyz/wagyourtail/jvmdg/j9/intl/NameChecks.java new file mode 100644 index 00000000..340cba42 --- /dev/null +++ b/java-api/src/java9/java/xyz/wagyourtail/jvmdg/j9/intl/NameChecks.java @@ -0,0 +1,146 @@ +package xyz.wagyourtail.jvmdg.j9.intl; + +import xyz.wagyourtail.jvmdg.j9.stub.java_base.J_U_Set; + +import java.util.Set; + +public class NameChecks { + + public static void checkModuleName(String name) { + if (name == null) { + throw new IllegalArgumentException("Null module name"); + } + String[] parts = name.split("\\.", -1); + for (String part : parts) { + if (!isJavaIdentifier(part)) { + throw new IllegalArgumentException(name + ": Invalid module name: " + part + " is not a Java identifier"); + } + } + } + + public static void checkPackageName(String name) { + if (name == null) { + throw new IllegalArgumentException("Null package name"); + } + String[] parts = name.split("\\.", -1); + for (String part : parts) { + if (!isJavaIdentifier(part)) { + throw new IllegalArgumentException(name + ": Invalid package name: " + part + " is not a Java identifier"); + } + } + } + + public static void checkServiceTypeName(String name) { + if (name == null) { + throw new IllegalArgumentException("Null service type name"); + } + String[] parts = name.split("\\.", -1); + for (String part : parts) { + if (!isJavaIdentifier(part)) { + throw new IllegalArgumentException(name + ": Invalid service type name: " + part + " is not a Java identifier"); + } + } + } + + public static void checkServiceProviderName(String name) { + if (name == null) { + throw new IllegalArgumentException("Null service provider name"); + } + String[] parts = name.split("\\.", -1); + for (String part : parts) { + if (!isJavaIdentifier(part)) { + throw new IllegalArgumentException(name + ": Invalid service provider name: " + part + " is not a Java identifier"); + } + } + } + + public static void checkClassName(String title, String name) { + if (name == null) { + throw new IllegalArgumentException("Null " + title + " name"); + } + String[] parts = name.split("\\$", -1); + for (String part : parts) { + if (!isJavaIdentifier(part)) { + throw new IllegalArgumentException(name + ": Invalid " + title + " name: " + part + " is not a Java identifier"); + } + } + } + + public static void checkJavaIdentifier(String name) { + if (name == null) { + throw new IllegalArgumentException("Null identifier"); + } + if (!isJavaIdentifier(name)) { + throw new IllegalArgumentException(name + ": Invalid identifier"); + } + } + + public static boolean isJavaIdentifier(String name) { + if (name.isEmpty()) return false; + if (RESERVED.contains(name)) return false; + int firstCodePoint = name.codePointAt(0); + if (!Character.isJavaIdentifierStart(firstCodePoint)) return false; + for (int i = Character.charCount(firstCodePoint); i < name.length(); i += Character.charCount(name.codePointAt(i))) { + if (!Character.isJavaIdentifierPart(name.codePointAt(i))) return false; + } + return true; + } + + private static final Set RESERVED = J_U_Set.of( + "abstract", + "assert", + "boolean", + "break", + "byte", + "case", + "catch", + "char", + "class", + "const", + "continue", + "default", + "do", + "double", + "else", + "enum", + "extends", + "final", + "finally", + "float", + "for", + "goto", + "if", + "implements", + "import", + "instanceof", + "int", + "interface", + "long", + "native", + "new", + "package", + "private", + "protected", + "public", + "return", + "short", + "static", + "strictfp", + "super", + "switch", + "synchronized", + "this", + "throw", + "throws", + "transient", + "try", + "void", + "volatile", + "while", + "true", + "false", + "null", + "_" + ); + +} diff --git a/java-api/src/java9/java/xyz/wagyourtail/jvmdg/j9/stub/java_base/J_L_M_ModuleDescriptor.java b/java-api/src/java9/java/xyz/wagyourtail/jvmdg/j9/stub/java_base/J_L_M_ModuleDescriptor.java new file mode 100644 index 00000000..4a93d41a --- /dev/null +++ b/java-api/src/java9/java/xyz/wagyourtail/jvmdg/j9/stub/java_base/J_L_M_ModuleDescriptor.java @@ -0,0 +1,903 @@ +package xyz.wagyourtail.jvmdg.j9.stub.java_base; + +import org.jetbrains.annotations.NotNull; +import xyz.wagyourtail.jvmdg.j9.intl.NameChecks; +import xyz.wagyourtail.jvmdg.version.Adapter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +@Adapter("java/lang/module/ModuleDescriptor") +public class J_L_M_ModuleDescriptor implements Comparable { + private final String name; + private final Version version; + private final String rawVersion; + private final EnumSet modifiers; + private final Set requiresSet; + private final Set exportsSet; + private final Set opensSet; + private final Set uses; + private final Set provides; + private final Set packages; + private final String mainClass; + + private J_L_M_ModuleDescriptor( + String name, + Version version, + String rawVersion, + Set modifiers, + Set requiresSet, + Set exportsSet, + Set opensSet, + Set uses, + Set provides, + Set packages, + String mainClass + ) { + this.name = name; + this.version = version; + this.rawVersion = rawVersion; + this.modifiers = EnumSet.copyOf(modifiers); + this.requiresSet = Collections.unmodifiableSet(requiresSet); + this.exportsSet = Collections.unmodifiableSet(exportsSet); + this.opensSet = Collections.unmodifiableSet(opensSet); + this.uses = Collections.unmodifiableSet(uses); + this.provides = Collections.unmodifiableSet(provides); + this.packages = Collections.unmodifiableSet(packages); + this.mainClass = mainClass; + } + + public String name() { + return name; + } + + public Set modifiers() { + return Collections.unmodifiableSet(modifiers); + } + + public boolean isOpen() { + return modifiers.contains(Modifier.OPEN); + } + + public boolean isAutomatic() { + return modifiers.contains(Modifier.AUTOMATIC); + } + + public Set requires() { + return requiresSet; + } + + public Set exports() { + return exportsSet; + } + + public Set opens() { + return opensSet; + } + + public Set uses() { + return uses; + } + + public Set provides() { + return provides; + } + + public Optional version() { + return Optional.ofNullable(version); + } + + public Optional rawVersion() { + if (version != null) { + return Optional.of(version.toString()); + } else { + return Optional.ofNullable(rawVersion); + } + } + + public String toNameAndVersion() { + return name + (version != null ? "@" + version : ""); + } + + public Optional mainClass() { + return Optional.ofNullable(mainClass); + } + + public Set packages() { + return packages; + } + + @Override + public int compareTo(@NotNull J_L_M_ModuleDescriptor o) { + if (this == o) return 0; + int i = name.compareTo(o.name); + if (i != 0) return i; + + i = compare(version, o.version); + if (i != 0) return i; + + i = compare(rawVersion, o.rawVersion); + if (i != 0) return i; + + i = Long.compare(longHash(modifiers), longHash(o.modifiers)); + if (i != 0) return i; + + i = compare(requiresSet, o.requiresSet); + if (i != 0) return i; + + i = compare(packages, o.packages); + if (i != 0) return i; + + i = compare(exportsSet, o.exportsSet); + if (i != 0) return i; + + i = compare(opensSet, o.opensSet); + if (i != 0) return i; + + i = compare(uses, o.uses); + if (i != 0) return i; + + i = compare(provides, o.provides); + if (i != 0) return i; + + return compare(mainClass, o.mainClass); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof J_L_M_ModuleDescriptor)) return false; + J_L_M_ModuleDescriptor that = (J_L_M_ModuleDescriptor) o; + return Objects.equals(name, that.name) && Objects.equals(version, that.version) && Objects.equals(rawVersion, that.rawVersion) && Objects.equals(modifiers, that.modifiers) && Objects.equals(requiresSet, that.requiresSet) && Objects.equals(exportsSet, that.exportsSet) && Objects.equals(opensSet, that.opensSet) && Objects.equals(uses, that.uses) && Objects.equals(provides, that.provides) && Objects.equals(packages, that.packages) && Objects.equals(mainClass, that.mainClass); + } + + private transient int hash = 0; + + @Override + public int hashCode() { + if (hash != 0) { + return hash; + } + int hash = name.hashCode() * 43; + hash = hash * 43 + modifiers.hashCode(); + hash = hash * 43 + requiresSet.hashCode(); + hash = hash * 43 + packages.hashCode(); + hash = hash * 43 + exportsSet.hashCode(); + hash = hash * 43 + opensSet.hashCode(); + hash = hash * 43 + uses.hashCode(); + hash = hash * 43 + provides.hashCode(); + if (version != null) { + hash = hash * 43 + version.hashCode(); + } else { + hash *= 43; + } + if (rawVersion != null) { + hash = hash * 43 + rawVersion.hashCode(); + } else { + hash *= 43; + } + if (mainClass != null) { + hash = hash * 43 + mainClass.hashCode(); + } else { + hash *= 43; + } + if (hash == 0) { + hash = -1; + } + this.hash = hash; + return hash; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (isOpen()) { + sb.append("open "); + } + sb.append("module { name: ").append(toNameAndVersion()); + if (!requiresSet.isEmpty()) { + sb.append(", requires: ").append(requiresSet); + } + if (!uses.isEmpty()) { + sb.append(", uses: ").append(uses); + } + if (!exportsSet.isEmpty()) { + sb.append(", exports: ").append(exportsSet); + } + if (!opensSet.isEmpty()) { + sb.append(", opens: ").append(opensSet); + } + if (!provides.isEmpty()) { + sb.append(", provides: ").append(provides); + } + sb.append(" }"); + return sb.toString(); + } + + private static long longHash(EnumSet enums) { + long hash = 0; + for (Enum anEnum : enums) { + hash |= 1L << anEnum.ordinal(); + } + return hash; + } + + private static > int compare(T a, T b) { + if (a == b) return 0; + if (a == null) return -1; + if (b == null) return 1; + return a.compareTo(b); + } + + private static > int compare(Set a, Set b) { + if (a == b) return 0; + if (a == null) return -1; + if (b == null) return 1; + T[] aArray = (T[]) a.toArray(); + T[] bArray = (T[]) b.toArray(); + Arrays.sort(aArray); + Arrays.sort(bArray); + return J_U_Arrays.compare(aArray, bArray); + } + + @Adapter("java/lang/module/ModuleDescriptor$Modifier") + public enum Modifier { + OPEN, AUTOMATIC, SYNTHETIC, MANDATED + } + + @Adapter("java/lang/module/ModuleDescriptor$Builder") + public static final class Builder { + final String name; + final boolean strict; + final Set modifiers; + 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 rawVersion; + String mainClass; + + Builder(String name, boolean strict, Set modifiers) { + this.name = name; + this.strict = strict; + this.modifiers = modifiers; + assert !(modifiers.contains(Modifier.OPEN) && modifiers.contains(Modifier.AUTOMATIC)); + } + + public Builder requires(Requires requires) { + if (modifiers.contains(Modifier.AUTOMATIC)) { + throw new IllegalStateException("Automatic modules cannot declare dependencies"); + } + if (name.equals(requires.name)) { + throw new IllegalArgumentException("Dependence on self"); + } + if (this.requires.containsKey(requires.name)) { + throw new IllegalArgumentException("Dependence upon " + requires.name + " already declared"); + } + this.requires.put(requires.name, requires); + return this; + } + + public Builder requires(Set mods, String name, Version version) { + Objects.requireNonNull(version); + if (strict) { + NameChecks.checkModuleName(name); + } + return requires(new Requires(mods, name, version, null)); + } + + public Builder requires(Set mods, String name) { + if (strict) { + NameChecks.checkModuleName(name); + } + return requires(new Requires(mods, name, null, null)); + } + + public Builder requires(String name) { + return requires(EnumSet.noneOf(Requires.Modifier.class), name); + } + + public Builder exports(Exports exports) { + if (this.modifiers.contains(Modifier.AUTOMATIC)) { + throw new IllegalStateException("Automatic modules cannot export packages"); + } + if (this.exports.containsKey(exports.source)) { + throw new IllegalArgumentException("Exported package " + exports.source + " already declared"); + } + this.exports.put(exports.source, exports); + this.packages.add(exports.source); + return this; + } + + public Builder exports(Set mods, String source, Set targets) { + if (strict) { + NameChecks.checkPackageName(source); + for (String target : targets) { + NameChecks.checkModuleName(target); + } + } + return exports(new Exports(mods, source, targets)); + } + + public Builder exports(Set mods, String source) { + if (strict) { + NameChecks.checkPackageName(source); + } + return exports(new Exports(mods, source, Collections.emptySet())); + } + + public Builder exports(String source, Set targets) { + return exports(EnumSet.noneOf(Exports.Modifier.class), source, targets); + } + + public Builder exports(String source) { + return exports(EnumSet.noneOf(Exports.Modifier.class), source); + } + + public Builder opens(Opens opens) { + if (this.modifiers.contains(Modifier.AUTOMATIC) || this.modifiers.contains(Modifier.OPEN)) { + throw new IllegalStateException("Open or Automatic modules cannot open packages"); + } + if (this.opens.containsKey(opens.source)) { + throw new IllegalArgumentException("Open package " + opens.source + " already declared"); + } + this.opens.put(opens.source, opens); + this.packages.add(opens.source); + return this; + } + + public Builder opens(Set mods, String source, Set targets) { + if (strict) { + NameChecks.checkPackageName(source); + for (String target : targets) { + NameChecks.checkModuleName(target); + } + } + return opens(new Opens(mods, source, targets)); + } + + public Builder opens(Set mods, String source) { + if (strict) { + NameChecks.checkPackageName(source); + } + return opens(new Opens(mods, source, Collections.emptySet())); + } + + public Builder opens(String source, Set targets) { + return opens(EnumSet.noneOf(Opens.Modifier.class), source, targets); + } + + public Builder opens(String source) { + return opens(EnumSet.noneOf(Opens.Modifier.class), source); + } + + public Builder uses(String service) { + if (modifiers.contains(Modifier.AUTOMATIC)) { + throw new IllegalStateException("Automatic modules cannot declare service dependences"); + } + NameChecks.checkServiceTypeName(service); + if (this.uses.contains(service)) { + throw new IllegalArgumentException("Service " + service + " already declared"); + } + this.uses.add(service); + return this; + } + + public Builder provides(Provides provides) { + if (this.provides.containsKey(provides.service)) { + throw new IllegalArgumentException("Providers of service " + provides.service + " already provided"); + } + this.provides.put(provides.service, provides); + for (String provider : provides.providers()) { + packages.add(packageName(provider)); + } + return this; + } + + private static String packageName(String cn) { + int index = cn.lastIndexOf('.'); + return (index == -1) ? "" : cn.substring(0, index); + } + + public Builder provides(String service, List providers) { + if (providers.isEmpty()) { + throw new IllegalArgumentException("Empty providers set"); + } + if (strict) { + NameChecks.checkServiceTypeName(service); + for (String provider : providers) { + NameChecks.checkServiceProviderName(provider); + } + } else { + if (packageName(service).isEmpty()) { + throw new IllegalArgumentException(service + ": unnamed package"); + } + for (String provider : providers) { + if (packageName(provider).isEmpty()) { + throw new IllegalArgumentException(provider + ": unnamed package"); + } + } + } + return provides(new Provides(service, providers)); + } + + public Builder packages(Set packages) { + if (strict) { + for (String pkg : packages) { + NameChecks.checkPackageName(pkg); + } + } + this.packages.addAll(packages); + return this; + } + + public Builder version(Version version) { + this.version = Objects.requireNonNull(version); + this.rawVersion = null; + return this; + } + + public Builder version(String version) { + try { + this.version = Version.parse(version); + this.rawVersion = null; + } catch (IllegalArgumentException e) { + if (strict) { + throw e; + } + this.version = null; + this.rawVersion = version; + } + return this; + } + + public Builder mainClass(String mainClass) { + if (strict) { + NameChecks.checkClassName("main class name", mainClass); + } else { + if (packageName(mainClass).isEmpty()) { + throw new IllegalArgumentException(mainClass + ": unnamed package"); + } + } + packages.add(packageName(mainClass)); + this.mainClass = mainClass; + return this; + } + + public J_L_M_ModuleDescriptor build() { + Set requires = new HashSet<>(this.requires.values()); + Set exports = new HashSet<>(this.exports.values()); + Set opens = new HashSet<>(this.opens.values()); + Set provides = new HashSet<>(this.provides.values()); + if (strict && !name.equals("java.base") && !this.requires.containsKey("java.base")) { + requires.add(new Requires(EnumSet.of(Requires.Modifier.MANDATED), "java.base", null, null)); + } + + return new J_L_M_ModuleDescriptor( + name, + version, + rawVersion, + modifiers, + requires, + exports, + opens, + uses, + provides, + packages, + mainClass + ); + } + } + + @Adapter("java/lang/module/ModuleDescriptor$Requires") + public final static class Requires implements Comparable { + private final EnumSet modifiers; + private final String name; + private final Version version; + private final String rawVersion; + + private Requires(Set modifiers, String name, Version version, String rawVersion) { + this.modifiers = EnumSet.copyOf(modifiers); + this.name = name; + this.version = version; + this.rawVersion = rawVersion; + } + + public Set modifiers() { + return Collections.unmodifiableSet(modifiers); + } + + public String name() { + return name; + } + + public Optional compiledVersion() { + return Optional.ofNullable(version); + } + + public Optional rawCompiledVersion() { + if (version != null) { + return Optional.of(version.toString()); + } else { + return Optional.ofNullable(rawVersion); + } + } + + @Override + public int compareTo(@NotNull Requires o) { + if (this == o) return 0; + int i = name.compareTo(o.name); + if (i != 0) return i; + + i = Long.compare(longHash(modifiers), longHash(o.modifiers)); + if (i != 0) return i; + + i = compare(version, o.version); + if (i != 0) return i; + + return compare(rawVersion, o.rawVersion); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Requires)) return false; + Requires requires = (Requires) o; + return Objects.equals(modifiers, requires.modifiers) && Objects.equals(name, requires.name) && Objects.equals(version, requires.version) && Objects.equals(rawVersion, requires.rawVersion); + } + + @Override + public int hashCode() { + int hash = name.hashCode() * 43 + modifiers.hashCode(); + if (version != null) { + hash = hash * 43 + version.hashCode(); + } + if (rawVersion != null) { + hash = hash * 43 + rawVersion.hashCode(); + } + return hash; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Modifier mod : modifiers) { + sb.append(mod.toString().toLowerCase()).append(" "); + } + if (version != null) { + sb.append(name).append("@(").append(version).append(")"); + } else { + sb.append(name); + } + return sb.toString(); + } + + @Adapter("java/lang/module/ModuleDescriptor$Requires$Modifier") + public enum Modifier { + TRANSITIVE, STATIC, SYNTHETIC, MANDATED + } + } + + @Adapter("java/lang/module/ModuleDescriptor$Exports") + public final static class Exports implements Comparable { + private final EnumSet modifiers; + private final String source; + private final Set targets; + + private Exports(Set modifiers, String source, Set targets) { + this.modifiers = EnumSet.copyOf(modifiers); + this.source = source; + this.targets = Collections.unmodifiableSet(targets); + } + + public Set modifiers() { + return Collections.unmodifiableSet(modifiers); + } + + public boolean isQualified() { + return !targets.isEmpty(); + } + + public String source() { + return source; + } + + public Set targets() { + return targets; + } + + @Override + public int compareTo(@NotNull Exports o) { + if (this == o) return 0; + int i = source.compareTo(o.source); + if (i != 0) return i; + + i = Long.compare(longHash(modifiers), longHash(o.modifiers)); + if (i != 0) return i; + + return compare(targets, o.targets); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Exports)) return false; + Exports exports = (Exports) o; + return Objects.equals(modifiers, exports.modifiers) && Objects.equals(source, exports.source) && Objects.equals(targets, exports.targets); + } + + @Override + public int hashCode() { + int hash = source.hashCode() * 43 + modifiers.hashCode(); + hash = hash * 43 + targets.hashCode(); + return hash; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Modifier mod : modifiers) { + sb.append(mod.toString().toLowerCase()).append(" "); + } + sb.append(source); + if (!targets.isEmpty()) { + sb.append(" to ").append(targets); + } + return sb.toString(); + } + + @Adapter("java/lang/module/ModuleDescriptor$Exports$Modifier") + public enum Modifier { + SYNTHETIC, MANDATED + } + } + + @Adapter("java/lang/module/ModuleDescriptor$Opens") + public final static class Opens implements Comparable { + private final EnumSet modifiers; + private final String source; + private final Set targets; + + private Opens(Set modifiers, String source, Set targets) { + this.modifiers = EnumSet.copyOf(modifiers); + this.source = source; + this.targets = Collections.unmodifiableSet(targets); + } + + public Set modifiers() { + return Collections.unmodifiableSet(modifiers); + } + + public boolean isQualified() { + return !targets.isEmpty(); + } + + public String source() { + return source; + } + + public Set targets() { + return targets; + } + + @Override + public int compareTo(@NotNull Opens o) { + if (this == o) return 0; + int i = source.compareTo(o.source); + if (i != 0) return i; + + i = Long.compare(longHash(modifiers), longHash(o.modifiers)); + if (i != 0) return i; + + return compare(targets, o.targets); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Opens)) return false; + Opens opens = (Opens) o; + return Objects.equals(modifiers, opens.modifiers) && Objects.equals(source, opens.source) && Objects.equals(targets, opens.targets); + } + + @Override + public int hashCode() { + int hash = source.hashCode() * 43 + modifiers.hashCode(); + hash = hash * 43 + targets.hashCode(); + return hash; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Modifier mod : modifiers) { + sb.append(mod.toString().toLowerCase()).append(" "); + } + sb.append(source); + if (!targets.isEmpty()) { + sb.append(" to ").append(targets); + } + return sb.toString(); + } + + @Adapter("java/lang/module/ModuleDescriptor$Opens$Modifier") + public enum Modifier { + SYNTHETIC, MANDATED + } + } + + @Adapter("java/lang/module/ModuleDescriptor$Provides") + 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); + } + + public String service() { + return service; + } + + public List providers() { + return providers; + } + + @Override + public int compareTo(@NotNull Provides o) { + if (this == o) return 0; + int i = service.compareTo(o.service); + if (i != 0) return i; + + return J_U_Arrays.compare(providers.toArray(new String[0]), o.providers.toArray(new String[0])); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Provides)) return false; + Provides provides = (Provides) o; + return Objects.equals(service, provides.service) && Objects.equals(providers, provides.providers); + } + + @Override + public int hashCode() { + int hash = service.hashCode() * 43; + hash = hash * 43 + providers.hashCode(); + return hash; + } + + @Override + public String toString() { + return service + " with " + providers; + } + } + + @Adapter("java/lang/module/ModuleDescriptor$Version") + public final static class Version implements Comparable { + private final String version; + private final List sequence; + private final List pre; + private final List build; + + private Version(String version) { + if (version == null) { + throw new IllegalArgumentException("Null version string"); + } + if (version.isEmpty()) { + throw new IllegalArgumentException("Empty version string"); + } + this.version = version; + int i = 0; + int l = version.length() - 1; + char c = version.charAt(i); + if (!Character.isDigit(c)) { + throw new IllegalArgumentException(version + ": Version string does not start with a number"); + } + List seq = new ArrayList<>(4); + StringBuilder sb = new StringBuilder(); + while (c != '-' && c != '+' && i < l) { + while (c != '.' && c != '-' && c != '+' && i < l) { + sb.append(c); + c = version.charAt(++i); + } + try { + seq.add(Integer.parseInt(sb.toString())); + } catch (NumberFormatException e) { + seq.add(sb.toString()); + } + sb.setLength(0); + } + sequence = Collections.unmodifiableList(seq); + if (c == '-' && i >= version.length()) { + throw new IllegalArgumentException(version + ": Empty pre-release"); + } + + List pre = new ArrayList<>(2); + while (c != '+' && i < l) { + sb.setLength(0); + c = version.charAt(++i); + while (c != '.' && c != '-' && c != '+' && i < l) { + sb.append(c); + c = version.charAt(++i); + } + try { + pre.add(Integer.parseInt(sb.toString())); + } catch (NumberFormatException e) { + pre.add(sb.toString()); + } + } + this.pre = Collections.unmodifiableList(pre); + if (c == '+' && i >= version.length()) { + throw new IllegalArgumentException(version + ": Empty pre-release"); + } + + List build = new ArrayList<>(2); + while (i < l) { + sb.setLength(0); + c = version.charAt(++i); + while (c != '.' && c != '-' && c != '+' && i < l) { + sb.append(c); + c = version.charAt(++i); + } + try { + build.add(Integer.parseInt(sb.toString())); + } catch (NumberFormatException e) { + build.add(sb.toString()); + } + } + this.build = Collections.unmodifiableList(build); + } + + public static Version parse(String version) { + return new Version(version); + } + + private static int cmp(Object a, Object b) { + if (a instanceof Integer && b instanceof Integer) { + return Integer.compare((Integer) a, (Integer) b); + } else { + return a.toString().compareTo(b.toString()); + } + } + + @Override + public int compareTo(@NotNull Version o) { + int c = J_U_Arrays.compare(sequence.toArray(), o.sequence.toArray(), Version::cmp); + if (c != 0) return c; + if (this.pre.isEmpty() && !o.pre.isEmpty()) return 1; + if (!this.pre.isEmpty() && o.pre.isEmpty()) return -1; + c = J_U_Arrays.compare(pre.toArray(), o.pre.toArray(), Version::cmp); + if (c != 0) return c; + return J_U_Arrays.compare(build.toArray(), o.build.toArray(), Version::cmp); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Version && compareTo((Version) obj) == 0; + } + + @Override + public int hashCode() { + return version.hashCode(); + } + + @Override + public String toString() { + return version; + } + } +} diff --git a/java-api/src/main/java/xyz/wagyourtail/jvmdg/providers/Java12Downgrader.java b/java-api/src/main/java/xyz/wagyourtail/jvmdg/providers/Java12Downgrader.java index 4f8061c0..25705bc6 100644 --- a/java-api/src/main/java/xyz/wagyourtail/jvmdg/providers/Java12Downgrader.java +++ b/java-api/src/main/java/xyz/wagyourtail/jvmdg/providers/Java12Downgrader.java @@ -13,10 +13,13 @@ public void init() { // -- java.base -- stub(J_I_InputStream.class); // Character$UnicodeBlock (more unicode spaces); + stub(J_L_C_ClassDesc.class); + stub(J_L_C_ConstantDesc.class); stub(J_L_Class.class); // Double // Enum // Float + stub(J_L_I_TypeDescriptor.class); // Integer // Long stub(J_L_String.class); diff --git a/java-api/src/main/java/xyz/wagyourtail/jvmdg/providers/Java9Downgrader.java b/java-api/src/main/java/xyz/wagyourtail/jvmdg/providers/Java9Downgrader.java index 478f8a4b..41eb117f 100644 --- a/java-api/src/main/java/xyz/wagyourtail/jvmdg/providers/Java9Downgrader.java +++ b/java-api/src/main/java/xyz/wagyourtail/jvmdg/providers/Java9Downgrader.java @@ -71,7 +71,7 @@ public void init() { // Configuration // FindException // InvalidModuleDescriptorException - // ModuleDescriptor + stub(J_L_M_ModuleDescriptor.class); // ModuleFinder // ModuleReader // ModuleReference