Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
[OpenJDK] invokedynamic-based String-switch + base for future indy-based switch implementations
diff -r 5573a7098439 src/java.base/share/classes/java/lang/invoke/SwitchTableFactory.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/invoke/SwitchTableFactory.java Mon Nov 04 18:07:43 2019 +0300
@@ -0,0 +1,580 @@
+/*
+ * Copyright (c) 2008, 2019, 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.invoke;
+
+import jdk.internal.misc.Unsafe;
+import jdk.internal.org.objectweb.asm.ClassWriter;
+import jdk.internal.org.objectweb.asm.Label;
+import jdk.internal.org.objectweb.asm.MethodVisitor;
+
+import java.lang.invoke.MethodHandles.Lookup;
+import java.util.*;
+
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+
+/**
+ * <p>Methods used to facilitate effective switching by values whose relation to ints is not bijective.</p>
+ *
+ * @author Petr Portnov
+ * @since 14
+ */
+public class SwitchTableFactory {
+
+ /**
+ * Radix used for integers with {@code alt}-variants of methods which rely on String dictionaries
+ *
+ * @implNote this value should never be changed once this class becomes part of the standard
+ * as it may lead to backwards-compatibility issues
+ */
+ public static final int BRANCH_DICTIONARY_INTEGER_RADIX = 16;
+
+ /**
+ * Instance of {@link Unsafe} used for internal purposes
+ */
+ private static final Unsafe UNSAFE = Unsafe.getUnsafe();
+ /**
+ * Instance of {@link Lookup} given full access
+ */
+ private static final Lookup LOOKUP = Lookup.IMPL_LOOKUP;
+
+ /**
+ * Version of generated class files
+ */
+ private static final int CLASS_FILE_VERSION = V1_8;
+ /**
+ * Name used for generated methods for which the call-sites get created
+ */
+ private static final String GENERATED_METHOD_NAME = "getId";
+
+ /**
+ * Method handle referencing {@link #constantId(int)} used for fast implementation of empty switches
+ */
+ private static final MethodHandle CONSTANT_ID_METHOD_HANDLE;
+
+ static {
+ // initialize `CONSTANT_ID_METHOD_HANDLE`
+ try {
+ CONSTANT_ID_METHOD_HANDLE = LOOKUP.findStatic(
+ SwitchTableFactory.class, "constantId", MethodType.methodType(int.class, int.class, String.class)
+ );
+ } catch (final NoSuchMethodException | IllegalAccessException e) {
+ // this should never happen but the exception is still a checked one
+ // this may be rewritten once JEP 303 makes its way into production
+ throw new InternalError("Unable to create a method handle for own method `static int constantId(int)`");
+ }
+ }
+
+ private static String getClassName(final Class<?> hostClass, final String name) {
+ return hostClass.getName().replace('.', '/') + "$$SwitchTable$$" + name;
+ }
+
+ private static void pushInt(final MethodVisitor method, final int value) {
+ switch (value) {
+ case -1: {
+ method.visitInsn(ICONST_M1);
+ return;
+ }
+ case 0: {
+ method.visitInsn(ICONST_0);
+ return;
+ }
+ case 1: {
+ method.visitInsn(ICONST_1);
+ return;
+ }
+ case 2: {
+ method.visitInsn(ICONST_2);
+ return;
+ }
+ case 3: {
+ method.visitInsn(ICONST_3);
+ return;
+ }
+ case 4: {
+ method.visitInsn(ICONST_4);
+ return;
+ }
+ case 5: {
+ method.visitInsn(ICONST_5);
+ return;
+ }
+ default: {
+ if (Byte.MAX_VALUE <= value && value <= Byte.MAX_VALUE) method.visitIntInsn(BIPUSH, value);
+ else if (Short.MAX_VALUE <= value && value <= Short.MAX_VALUE) method.visitIntInsn(SIPUSH, value);
+ else method.visitLdcInsn(value);
+ }
+ }
+ }
+
+ private static int constantId(final int id, final String unused) {
+ return id;
+ }
+
+ private static CallSite constantIdCallSite(final int defaultBranchId) {
+ return new ConstantCallSite(CONSTANT_ID_METHOD_HANDLE.rebind().bindArgumentI(0, defaultBranchId));
+ }
+
+ /**
+ * <p>Facilitates the creation of optimized {@link String}-switch table,
+ * whose values can later be used in {@code int}-switch.
+ * Typically used as <em>bootstrap method</em> for {@code invokedynamic} call sites,
+ * to support the <em>string switch</em> feature of the Java Programming Language.</p>
+ * <p>This method is an alternative to {@link #altCreateStringSwitchTable(Lookup, String, MethodType, Object...)}
+ * which is simpler but allows limited (max method argument slot size -specific) amount of values used.</p>
+ *
+ * <p>Effective signature is the following:</p>
+ * <pre>
+ * CallSite createStringSwitchTable(Lookup lookup,
+ * String name,
+ * MethodType type,
+ * int defaultBranchId,
+ * int labelCount, // 0 is allowed though this is useless
+ * // BEGIN repeated part
+ * int branchId,
+ * int count,
+ * String... labels, // `count` times
+ * // END repeated part
+ * )
+ * </pre>
+ *
+ * @param lookup Lookup context with access privileges.
+ * @param name The name of the method to implement.
+ * @param type The expected signature of the {@link CallSite}. Normally it is {@code (String)I}
+ * @param bootstrapArguments special bootstrap arguments describing the dictionary used
+ *
+ * @return a CallSite whose target can be used to find the ID
+ * of the {@link String} passed to it as a dynamic parameter.
+ *
+ * @throws NullPointerException if {@code lookup} or {@code name} or {@code type} or {@code dictionary}
+ * or any of {@code dictionary}'s elements is {@code null}
+ * @throws SwitchTableFactoryException if any parameter is illegal
+ */
+ public static CallSite createStringSwitchTable(final Lookup lookup,
+ final String name,
+ final MethodType type,
+ final Object... bootstrapArguments)
+ throws SwitchTableFactoryException {
+ // Check JVM-passed parameters not to have undefined behaviour if (somehow) incorrect values get passed
+ Objects.requireNonNull(lookup, "Lookup is null");
+ Objects.requireNonNull(name, "Name is null");
+ Objects.requireNonNull(type, "Type is null");
+
+ // check bootstrap arguments array general nullability
+ Objects.requireNonNull(bootstrapArguments, "Bootstrap arguments array is null");
+
+ // Validate type details
+ if (type.parameterCount() != 1
+ || type.returnType() != int.class
+ || type.parameterType(0) != String.class) throw new SwitchTableFactoryException(
+ "Parameter type should be `(String)int`"
+ );
+
+ // Note: cast to primitives will also perform null-check
+
+ final int defaultId = (int) bootstrapArguments[0];
+
+ final int labelCount = (int) bootstrapArguments[1];
+ if (labelCount < 0) throw new SwitchTableFactoryException("Branch count cannot be negative");
+ if (labelCount == 0 /* somehow */) return constantIdCallSite(defaultId);
+
+ final StringSwitchNode[] nodes = new StringSwitchNode[labelCount];
+ { // add nodes according to the dictionary
+ int readArgumentIndex = 1, nodeIndex = 0;
+ while (nodeIndex < labelCount) {
+ final int branchId = (int) bootstrapArguments[++readArgumentIndex];
+ final int count = (int) bootstrapArguments[++readArgumentIndex];
+ if (count < 0) throw new SwitchTableFactoryException(
+ "Count of branch by ID " + branchId + " cannot be negative"
+ );
+ for (int i = 0; i < count; i++) nodes[nodeIndex++] = new StringSwitchNode(
+ branchId,
+ (String) Objects.requireNonNull(bootstrapArguments[++readArgumentIndex], "Branch label is null")
+ );
+ }
+ }
+
+ return generateStringSwitchTable(lookup, name, type, defaultId, nodes);
+ }
+
+ /**
+ * <p>Facilitates the creation of optimized {@link String}-switch table,
+ * whose values can later be used in {@code int}-switch.
+ * Typically used as <em>bootstrap method</em> for {@code invokedynamic} call sites,
+ * to support the <em>string switch</em> feature of the Java Programming Language.</p>
+ * <p>This method is an alternative to {@link #createStringSwitchTable(Lookup, String, MethodType, Object...)}
+ * allowing practically unlimited amount of values used.</p>
+ *
+ * <p>Effective signature is the following:</p>
+ * <pre>
+ * CallSite altCreateStringSwitchTable(Lookup lookup,
+ * String name,
+ * MethodType type,
+ * int defaultBranchId,
+ * int labelCount, // 0 is allowed though this is useless
+ * int dictionarySize, // 0 is allowed though this is useless
+ * String... dictionaryPages, // `dictionarySize` times
+ * )
+ * </pre>
+ * <p>Dictionary pages have the following format (all elements go not separated):</p>
+ * <dl>
+ * <dt>ID{@code \0}</dt>
+ * <dd>ID returned for the following <i>size</i> values</dd>
+ *
+ * <dt>size</dt>
+ * <dd>Amount of value-labels described in the block</dd>
+ *
+ * <dt>value_length (N times, each followed by its <i>value</i>)</dt>
+ * <dd>Length of the following <i>value</i></dd>
+ *
+ * <dt>value</dt>
+ * <dd>Value-label of length <i>value_length</i></dd>
+ * </dl>
+ * <p><em>Important</em>: all numeric values have the radix {@value #BRANCH_DICTIONARY_INTEGER_RADIX}
+ * available to the externals tools via constant {@link #BRANCH_DICTIONARY_INTEGER_RADIX}.</p>
+ *
+ * <p>For example {@code "0\0002\000\3\000Lol4\000ABCD1\0001\000\5\000Hello"}
+ * stands for 2 labels, {@code "lol"} and {@code "ABCD"}, to which ID {@code 0} corresponds
+ * and 1 label, {@code "Hello"}, to which ID {@code 1} corresponds.</p>
+ *
+ * @param lookup Lookup context with access privileges.
+ * @param name The name of the method to implement.
+ * @param type The expected signature of the {@link CallSite}. Normally it is {@code (String)I}
+ * @param bootstrapArguments special bootstrap arguments describing the dictionary used
+ *
+ * @return a CallSite whose target can be used to find the ID
+ * of the {@link String} passed to it as a dynamic parameter.
+ *
+ * @throws NullPointerException if {@code lookup} or {@code name} or {@code type} or {@code dictionary}
+ * or any of {@code dictionary}'s elements is {@code null}
+ * @throws SwitchTableFactoryException if any parameter is illegal
+ */
+ public static CallSite altCreateStringSwitchTable(final Lookup lookup,
+ final String name,
+ final MethodType type,
+ final Object... bootstrapArguments)
+ throws SwitchTableFactoryException {
+ // Check JVM-passed parameters not to have undefined behaviour if (somehow) incorrect values get passed
+ Objects.requireNonNull(lookup, "Lookup is null");
+ Objects.requireNonNull(name, "Name is null");
+ Objects.requireNonNull(type, "Type is null");
+
+ // check bootstrap arguments array general nullability
+ Objects.requireNonNull(bootstrapArguments, "Bootstrap arguments array is null");
+
+ // Validate type details
+ if (type.parameterCount() != 1
+ || type.returnType() != int.class
+ || type.parameterType(0) != String.class) throw new SwitchTableFactoryException(
+ "Parameter type should be `(String)int`"
+ );
+
+ // Note: cast to primitives will also perform null-check
+
+ final int defaultId = (int) bootstrapArguments[0];
+
+ final int labelCount = (int) bootstrapArguments[1];
+ if (labelCount < 0) throw new SwitchTableFactoryException("Label count cannot be negative");
+ if (labelCount == 0 /* somehow */) return constantIdCallSite(defaultId);
+
+ final int dictionarySize = (int) bootstrapArguments[2];
+ if (dictionarySize < 0) throw new SwitchTableFactoryException("Dictionary count cannot be negative");
+ // the following just checks for an absurd case which may in rare cases help the user
+ if (dictionarySize > labelCount) return constantIdCallSite(defaultId);
+
+ final StringSwitchNode[] nodes = new StringSwitchNode[labelCount];
+
+ {
+ int nodeId = -1;
+ for (int dictionaryPageIndex = 0; dictionaryPageIndex < dictionarySize; dictionaryPageIndex++) {
+ final String page = (String) Objects.requireNonNull(
+ bootstrapArguments[dictionaryPageIndex + 3 /* amount of previously read arguments */],
+ "Dictionary page is null"
+ );
+
+ int startIndex = 0, delimiterIndex;
+ while ((delimiterIndex = page.indexOf('\0', startIndex)) != -1) {
+ final int id = Integer.parseInt(page, startIndex, delimiterIndex, BRANCH_DICTIONARY_INTEGER_RADIX);
+
+ startIndex = delimiterIndex + 1;
+ delimiterIndex = page.indexOf('\0', startIndex);
+ if (delimiterIndex == -1) throw new SwitchTableFactoryException(
+ "Missing amount of labels for branch " + id + " in dictionary page #" + dictionaryPageIndex
+ );
+
+ final int count = Integer.parseInt(
+ page, startIndex, delimiterIndex, BRANCH_DICTIONARY_INTEGER_RADIX
+ );
+
+ startIndex = delimiterIndex + 1;
+
+ for (int elementIndex = 0; elementIndex < count; elementIndex++) {
+ delimiterIndex = page.indexOf('\0', startIndex);
+ final int elementLength = Integer.parseInt(
+ page, startIndex, delimiterIndex, BRANCH_DICTIONARY_INTEGER_RADIX
+ );
+
+ nodes[++nodeId] = new StringSwitchNode(id, page.substring(
+ delimiterIndex + 1, startIndex = delimiterIndex + 1 + elementLength
+ ));
+ }
+ }
+ }
+ }
+
+ return generateStringSwitchTable(lookup, name, type, defaultId, nodes);
+ }
+
+ // Note: this method does not perform any validations
+ private static CallSite generateStringSwitchTable(final Lookup lookup,
+ /* var */ String name,
+ final MethodType type,
+ final int defaultId,
+ final StringSwitchNode[] nodes)
+ throws SwitchTableFactoryException {
+ /*
+ * Here the black magic begins with class generation.
+ *
+ * Lasciate ogni speranza, voi ch'entrate
+ */
+
+ /* ********************************************************************************************************** */
+
+ // Frames and Maximums are computes manually as everything here is predictable
+ final ClassWriter clazz = new ClassWriter(0);
+ // get the caller class (needed for generating an informative class-name and
+ final Class<?> hostClass = lookup.lookupClass();
+ name = getClassName(hostClass, name);
+ // initiate class file generation using `java.lang.Object` as its super-class
+ clazz.visit(
+ CLASS_FILE_VERSION, ACC_SUPER | ACC_PUBLIC | ACC_FINAL | ACC_SYNTHETIC,
+ name, null, "java/lang/Object", null
+ );
+
+ // start the visit of the class
+
+ // Note: there is no need for the constructor to be present in the generated class as it can't be instantiated
+
+ {
+ // start visiting the method being generated
+ final MethodVisitor method = clazz.visitMethod(
+ ACC_PUBLIC | ACC_STATIC | ACC_FINAL, GENERATED_METHOD_NAME, "(Ljava/lang/String;)I", null, null
+ );
+ // modify method's bytecode
+ method.visitCode();
+
+ // label corresponding to no match found for the `String`
+ final Label defaultLabel = new Label();
+ if (nodes.length == 1) {
+ final StringSwitchNode node = nodes[0];
+ // push the `String` checked
+ method.visitVarInsn(ALOAD, 0);
+ // push the `String` to compare the value with
+ method.visitLdcInsn(node.string);
+ // invoke `equals(Object)` method
+ method.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false
+ );
+ // jump to non-equality label if `equals(Object)` returns false
+ method.visitJumpInsn(IFEQ, defaultLabel);
+ // return ID of the case when the strings are equal
+ pushInt(method, node.id);
+ method.visitInsn(IRETURN);
+ } else {
+ // push the String onto the stack
+ method.visitVarInsn(ALOAD, 0);
+ method.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "hashCode", "()I", false);
+
+ int nonDefaultBranchCount = 0;
+ final NavigableMap<Integer, List<StringSwitchNode>> branches = new TreeMap<>();
+ // put the nodes into the map
+ for (final StringSwitchNode node : nodes) branches
+ .computeIfAbsent(node.stringHashCode, ArrayList::new).add(node);
+
+ final int branchCount = branches.size();
+ final Label[] labels = new Label[branchCount];
+ {
+ final int[] hashCodes = new int[branchCount];
+
+ { // fill labels and hashCodes
+ int i = -1;
+ for (final Integer hashCode : branches.keySet()) {
+ labels[++i] = new Label();
+ hashCodes[i] = hashCode;
+ }
+ }
+
+
+ // Lookup is used because the chance of ints being close is extremely small
+ method.visitLookupSwitchInsn(defaultLabel, hashCodes, labels);
+ }
+
+ // now add all switch branches
+ {
+ int labelId = -1;
+ // traverse through all groups of nodes with equal hash-codes
+ for (final List<StringSwitchNode> nodeGroup : branches.values()) {
+ // amount of nodes in current group
+ final int nodeGroupSize = nodeGroup.size();
+
+ // visit the label of the current switch-branch
+ method.visitLabel(labels[++labelId]);
+ method.visitFrame(F_SAME, 0, null, 0, null);
+
+ StringSwitchNode node;
+ // if there is only one node then simply generate typical check for it
+ if (nodeGroupSize == 1) node = nodeGroup.get(0);
+ // otherwise generate a chain of if-else statements to reach the needed string,
+ // finally using typical check for the last one
+ else {
+ // index of the last node
+ final int lastNodeIndex = nodeGroupSize - 1;
+ // traverse through all nodes except for the last one
+ for (int i = 0; i < lastNodeIndex; i++) {
+ // label of the next node to be visited
+ final Label nextLabel = new Label();
+ // get the current node
+ node = nodeGroup.get(i);
+ // push the `String` checked
+ method.visitVarInsn(ALOAD, 0);
+ // push the `String` to compare the value with
+ method.visitLdcInsn(node.string);
+ // invoke `equals(Object)` method
+ method.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false
+ );
+ // jump to next check label if `equals(Object)` returns false
+ method.visitJumpInsn(IFEQ, nextLabel);
+ // return ID of the case when the strings are equal
+ pushInt(method, node.id);
+ method.visitInsn(IRETURN);
+
+ // start the visit of the next label
+ method.visitLabel(nextLabel);
+ }
+
+ node = nodeGroup.get(lastNodeIndex);
+ }
+ // push the `String` checked
+ method.visitVarInsn(ALOAD, 0);
+ // push the `String` to compare the value with
+ method.visitLdcInsn(node.string);
+ // invoke `equals(Object)` method
+ method.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false
+ );
+ method.visitJumpInsn(IFEQ, defaultLabel);
+ pushInt(method, node.id);
+ method.visitInsn(IRETURN);
+ }
+ }
+ }
+ // and finally handle default label (the case in which no match was found for the `String`)
+ method.visitLabel(defaultLabel);
+ method.visitFrame(F_SAME, 0, null, 0, null);
+ // returning default ID
+ pushInt(method, defaultId);
+ method.visitInsn(IRETURN);
+
+ method.visitMaxs(2 /* worst: two compared Strings */, 1 /* no local variables except fot the parameter */);
+
+ // end method modification
+ method.visitEnd();
+ }
+
+ // end class modification
+ clazz.visitEnd();
+
+ /* ********************************************************************************************************** */
+
+ /*
+ * Now produce the result of all this black magic
+ */
+
+ final MethodHandle methodHandle;
+ { // class loading and method handle lookup
+ // Define the created class and make sure that it was initialized
+ final Class<?> generatedClass = UNSAFE.defineAnonymousClass(hostClass, clazz.toByteArray(), null);
+ UNSAFE.ensureClassInitialized(generatedClass);
+
+ // Find the method handle referencing the method
+ try {
+ methodHandle = LOOKUP.findStatic(generatedClass, GENERATED_METHOD_NAME, type);
+ } catch (final NoSuchMethodException | IllegalAccessException e) {
+ throw new SwitchTableFactoryException(
+ "Could not find the generated method in the generated class (somehow)", e
+ );
+ }
+ }
+
+ // And finally return a constant call site of the method handle
+ return new ConstantCallSite(methodHandle);
+ }
+
+ // no need for getters as the class is for internal use only
+ private static final class StringSwitchNode implements Comparable<StringSwitchNode> {
+ private final int id;
+ private final /* effectively non-null*/ String string;
+ private final int stringHashCode;
+ private final int hashCode;
+
+ private StringSwitchNode(/* effectively non-null*/ final int id, final String string) {
+ this.id = id;
+ this.string = string;
+ stringHashCode = string.hashCode();
+ hashCode = string.hashCode() * 31 + id;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (this == other) return true;
+ if (other == null || other.getClass() != StringSwitchNode.class) return false;
+ final StringSwitchNode otherNode = (StringSwitchNode) other;
+ return hashCode == otherNode.hashCode // fast check (also contract guarantee)
+ && id == otherNode.id && string.equals(otherNode.string);
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public int compareTo(final StringSwitchNode other) {
+ return Integer.compare(this.stringHashCode, other.stringHashCode);
+ }
+
+ /* This one is here for debug purposes */
+ @Override
+ public String toString() {
+ return "StringSwitchNode{" +
+ "id=" + id +
+ ", string='" + string + '\'' +
+ ", stringHashCode=" + stringHashCode +
+ ", hashCode=" + hashCode +
+ '}';
+ }
+ }
+}
diff -r 5573a7098439 src/java.base/share/classes/java/lang/invoke/SwitchTableFactoryException.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/invoke/SwitchTableFactoryException.java Mon Nov 04 18:07:43 2019 +0300
@@ -0,0 +1,37 @@
+package java.lang.invoke;
+
+/**
+ * An exception thrown whenever {@link SwitchTableFactory} is unable to do its work
+ * (in most cases, due to incorrect parameters passed).
+ *
+ * @author Petr Portnov
+ *
+ * @since 14
+ */
+public class SwitchTableFactoryException extends Exception {
+
+ /**
+ * Serial version unique ID (as required by {@link java.io.Serializable} specification)
+ */
+ @java.io.Serial
+ private static final long serialVersionUID = 292L + 10L;
+
+ /**
+ * Constructs a new exception with a message describing the problem.
+ *
+ * @param message message describing the problem
+ */
+ public SwitchTableFactoryException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new exception with a message describing the problem and a cause of this exception.
+ *
+ * @param message message describing the problem
+ * @param cause cause of this exception
+ */
+ public SwitchTableFactoryException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff -r 5573a7098439 test/jdk/java/lang/invoke/SwitchTableFactory/BasicTest.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/SwitchTableFactory/BasicTest.java Mon Nov 04 18:07:43 2019 +0300
@@ -0,0 +1,356 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.SwitchTableFactory;
+import java.lang.invoke.SwitchTableFactoryException;
+
+/**
+ * This test simply tests logical behaviour of {@link SwitchTableFactory}.
+ */
+public class BasicTest {
+
+ public static void main(final String[] args) {
+ testPreconditionsOfCreateStringSwitchTableTest();
+ testCreateStringSwitchTableTest();
+ testPreconditionsOfAltCreateStringSwitchTableTest();
+ testAltCreateStringSwitchTableTest();
+ testCornerCase0Branches();
+ testCornerCase1Branch();
+ }
+
+ static void assertSame(final int expected, final int actual) {
+ if (expected != actual) throw new AssertionError("Expected " + expected + " but found " + actual);
+ }
+
+ static void assertThrows(final Class<? extends Throwable> exceptionType,
+ final TestRunnable task) {
+ try {
+ task.run();
+ throw new AssertionError("Exception of type " + exceptionType + " was not thrown");
+ } catch (final Throwable x) {
+ if (!exceptionType.isAssignableFrom(x.getClass())) throw new AssertionError(
+ "Thwown exception " + x + " is not of type " + exceptionType
+ );
+ }
+ }
+
+ static void testPreconditionsOfCreateStringSwitchTableTest() {
+ // invokedynamic universal parameters
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.createStringSwitchTable(
+ null,
+ "foo",
+ MethodType.methodType(int.class, String.class),
+ -1, 1,
+ 1, 1, "a"
+ ));
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.createStringSwitchTable(
+ MethodHandles.lookup(),
+ null,
+ MethodType.methodType(int.class, String.class),
+ -1, 1,
+ 1, 1, "a"
+ ));
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.createStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ null,
+ -1, 1,
+ 1, 1, "a"
+ ));
+ // bootstrap arguments array
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.createStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(int.class, String.class),
+ (Object[]) null
+ ));
+ // bootstrap arguments
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.createStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(int.class, String.class),
+ null, 1,
+ 1, 1, "a"
+ ));
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.createStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(int.class, String.class),
+ -1, null,
+ 1, 1, "a"
+ ));
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.createStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(int.class, String.class),
+ -1, 1,
+ null, 1, "a"
+ ));
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.createStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(int.class, String.class),
+ -1, 1,
+ 1, null, "a"
+ ));
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.createStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(int.class, String.class),
+ -1, 1,
+ 1, 1, null
+ ));
+ // type validation
+ assertThrows(SwitchTableFactoryException.class, () -> SwitchTableFactory.createStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(Object.class, String.class),
+ -1, 1,
+ 1, 1, "a"
+ ));
+ assertThrows(SwitchTableFactoryException.class, () -> SwitchTableFactory.createStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(int.class, Object.class),
+ -1, 1,
+ 1, 1, "a"
+ ));
+ }
+
+ static void testCreateStringSwitchTableTest() {
+ final MethodHandle methodHandle;
+ try {
+ methodHandle = SwitchTableFactory.createStringSwitchTable(
+ MethodHandles.lookup(),
+ "somename",
+ MethodType.methodType(int.class, String.class),
+ -1, 5,
+ 0, 2, "Hello", "World",
+ 1, 3, "One", "Two", "Three"
+ ).getTarget();
+ } catch (final Throwable e) {
+ throw new AssertionError("An exception was thrown while bootstrapping", e);
+ }
+
+ try {
+ assertSame(0, (int) methodHandle.invokeExact("Hello"));
+ assertSame(0, (int) methodHandle.invokeExact("World"));
+ assertSame(1, (int) methodHandle.invokeExact("One"));
+ assertSame(1, (int) methodHandle.invokeExact("Two"));
+ assertSame(1, (int) methodHandle.invokeExact("Three"));
+ assertSame(-1, (int) methodHandle.invokeExact("Something other"));
+ assertSame(-1, (int) methodHandle.invokeExact(""));
+ assertSame(-1, (int) methodHandle.invokeExact("Nope"));
+ } catch (final Throwable e) {
+ throw new AssertionError("An exception was thrown while testing", e);
+ }
+ }
+
+ static void testPreconditionsOfAltCreateStringSwitchTableTest() {
+ // invokedynamic universal parameters
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.altCreateStringSwitchTable(
+ null,
+ "foo",
+ MethodType.methodType(int.class, String.class),
+ -2, 1,
+ 1, "1\0001\0001\000a"
+ ));
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.altCreateStringSwitchTable(
+ MethodHandles.lookup(),
+ null,
+ MethodType.methodType(int.class, String.class),
+ -2, 1,
+ 1, "1\0001\0001\000a"
+ ));
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.altCreateStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ null,
+ -2, 1,
+ 1, "1\0001\0001\000a"
+ ));
+ // bootstrap arguments array
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.altCreateStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(int.class, String.class),
+ (Object[]) null
+ ));
+ // bootstrap arguments
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.altCreateStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(int.class, String.class),
+ null, 1,
+ 1, "1\0001\0001\000a"
+ ));
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.altCreateStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(int.class, String.class),
+ -2, null,
+ 1, "1\0001\0001\000a"
+ ));
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.altCreateStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(int.class, String.class),
+ -2, 1,
+ null, "1\0001\0001\000a"
+ ));
+ assertThrows(NullPointerException.class, () -> SwitchTableFactory.altCreateStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(int.class, String.class),
+ -2, 1,
+ 1, null
+ ));
+ // type validation
+ assertThrows(SwitchTableFactoryException.class, () -> SwitchTableFactory.altCreateStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(Object.class, String.class),
+ -2, 1,
+ 1, "1\0001\0001\000a"
+ ));
+ assertThrows(SwitchTableFactoryException.class, () -> SwitchTableFactory.altCreateStringSwitchTable(
+ MethodHandles.lookup(),
+ "foo",
+ MethodType.methodType(int.class, Object.class),
+ -2, 1,
+ 1, "1\0001\0001\000a"
+ ));
+ }
+
+ static void testAltCreateStringSwitchTableTest() {
+ final MethodHandle methodHandle;
+ try {
+ methodHandle = SwitchTableFactory.altCreateStringSwitchTable(
+ MethodHandles.lookup(),
+ "othername",
+ MethodType.methodType(int.class, String.class),
+ -10, 5, 2,
+ "12\0002\000"
+ + "3\000Lol"
+ + "3\000Lul",
+ "34\0003\000"
+ + "4\000abcd"
+ + "3\000omg"
+ + "5\000qwert"
+ ).getTarget();
+ } catch (final Throwable e) {
+ throw new AssertionError("An exception was thrown while bootstrapping", e);
+ }
+
+ try {
+ assertSame(-10, (int) methodHandle.invokeExact("wait"));
+ assertSame(-10, (int) methodHandle.invokeExact("is"));
+ assertSame(0x12, (int) methodHandle.invokeExact("Lol"));
+ assertSame(0x12, (int) methodHandle.invokeExact("Lul"));
+ assertSame(0x34, (int) methodHandle.invokeExact("abcd"));
+ assertSame(0x34, (int) methodHandle.invokeExact("omg"));
+ assertSame(0x34, (int) methodHandle.invokeExact("qwert"));
+ assertSame(-10, (int) methodHandle.invokeExact("this"));
+ assertSame(-10, (int) methodHandle.invokeExact("even legal"));
+ assertSame(-10, (int) methodHandle.invokeExact("?"));
+ assertSame(-10, (int) methodHandle.invokeExact(""));
+ assertSame(-10, (int) methodHandle.invokeExact("mb"));
+ } catch (final Throwable e) {
+ throw new AssertionError("An exception was thrown while testing", e);
+ }
+ }
+
+ // Note: there is no need to test both factories here as they differ only from point of passing arguments
+ static void testCornerCase0Branches() {
+ { // 0 branches
+ final MethodHandle methodHandle;
+ try {
+ methodHandle = SwitchTableFactory.createStringSwitchTable(
+ MethodHandles.lookup(),
+ "somename",
+ MethodType.methodType(int.class, String.class),
+ -1, 0
+ ).getTarget();
+ } catch (final Throwable e) {
+ throw new AssertionError("An exception was thrown while bootstrapping", e);
+ }
+
+ try {
+ assertSame(-1, (int) methodHandle.invokeExact("Hello"));
+ assertSame(-1, (int) methodHandle.invokeExact("World"));
+ assertSame(-1, (int) methodHandle.invokeExact("One"));
+ assertSame(-1, (int) methodHandle.invokeExact("Two"));
+ assertSame(-1, (int) methodHandle.invokeExact("Three"));
+ assertSame(-1, (int) methodHandle.invokeExact("Something other"));
+ assertSame(-1, (int) methodHandle.invokeExact(""));
+ assertSame(-1, (int) methodHandle.invokeExact("Nope"));
+ } catch (final Throwable e) {
+ throw new AssertionError("An exception was thrown while testing", e);
+ }
+ }
+ }
+
+ // Note: there is no need to test both factories here as they differ only from point of passing arguments
+ // Note: this case is tested because it is logical to treat it specially
+ static void testCornerCase1Branch() {
+ { // 0 branches
+ final MethodHandle methodHandle;
+ try {
+ methodHandle = SwitchTableFactory.createStringSwitchTable(
+ MethodHandles.lookup(),
+ "somename",
+ MethodType.methodType(int.class, String.class),
+ -1, 1,
+ 100, 1, "foo"
+ ).getTarget();
+ } catch (final Throwable e) {
+ throw new AssertionError("An exception was thrown while bootstrapping", e);
+ }
+
+ try {
+ assertSame(-1, (int) methodHandle.invokeExact("Hello"));
+ assertSame(-1, (int) methodHandle.invokeExact("World"));
+ assertSame(-1, (int) methodHandle.invokeExact("One"));
+ assertSame(-1, (int) methodHandle.invokeExact("Two"));
+ assertSame(100, (int) methodHandle.invokeExact("foo"));
+ assertSame(-1, (int) methodHandle.invokeExact("Three"));
+ assertSame(-1, (int) methodHandle.invokeExact("Something other"));
+ assertSame(-1, (int) methodHandle.invokeExact(""));
+ assertSame(-1, (int) methodHandle.invokeExact("Nope"));
+ assertSame(100, (int) methodHandle.invokeExact("foo"));
+ assertSame(-1, (int) methodHandle.invokeExact("Nope"));
+ assertSame(100, (int) methodHandle.invokeExact("foo"));
+ } catch (final Throwable e) {
+ throw new AssertionError("An exception was thrown while testing", e);
+ }
+ }
+ }
+
+ @FunctionalInterface
+ interface TestRunnable {
+ void run() throws Throwable;
+ }
+}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment