Add MethodHandle.asSpreader(int, Class<?>, int) from OpenJDK 11.0.13
Bug: 191446452
Test: atest CtsLibcoreOjTestCases:java.lang.invoke.SpreadCollectTest
Change-Id: I75aea926d58e453081dd4e54930f7d1e769e8eee
diff --git a/api/current.txt b/api/current.txt
index 167e5bc..835ae00 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -4293,6 +4293,7 @@
method public java.lang.invoke.MethodHandle asCollector(int, Class<?>, int);
method public java.lang.invoke.MethodHandle asFixedArity();
method public java.lang.invoke.MethodHandle asSpreader(Class<?>, int);
+ method public java.lang.invoke.MethodHandle asSpreader(int, Class<?>, int);
method public java.lang.invoke.MethodHandle asType(java.lang.invoke.MethodType);
method public java.lang.invoke.MethodHandle asVarargsCollector(Class<?>);
method public java.lang.invoke.MethodHandle bindTo(Object);
diff --git a/ojluni/src/main/java/java/lang/invoke/MethodHandle.java b/ojluni/src/main/java/java/lang/invoke/MethodHandle.java
index 10282b8..cc82f54 100644
--- a/ojluni/src/main/java/java/lang/invoke/MethodHandle.java
+++ b/ojluni/src/main/java/java/lang/invoke/MethodHandle.java
@@ -896,9 +896,13 @@
* to the target method handle.
* (The array may also be null when zero elements are required.)
* <p>
- * If, when the adapter is called, the supplied array argument does
- * not have the correct number of elements, the adapter will throw
- * an {@link IllegalArgumentException} instead of invoking the target.
+ * When the adapter is called, the length of the supplied {@code array}
+ * argument is queried as if by {@code array.length} or {@code arraylength}
+ * bytecode. If the adapter accepts a zero-length trailing array argument,
+ * the supplied {@code array} argument can either be a zero-length array or
+ * {@code null}; otherwise, the adapter will throw a {@code NullPointerException}
+ * if the array is {@code null} and throw an {@link IllegalArgumentException}
+ * if the array does not have the correct number of elements.
* <p>
* Here are some simple examples of array-spreading method handles:
* <blockquote><pre>{@code
@@ -912,7 +916,7 @@
assert(!(boolean) eq2.invokeExact(new Object[]{ "me", "thee" }));
// try to spread from anything but a 2-array:
for (int n = 0; n <= 10; n++) {
- Object[] badArityArgs = (n == 2 ? null : new Object[n]);
+ Object[] badArityArgs = (n == 2 ? new Object[0] : new Object[n]);
try { assert((boolean) eq2.invokeExact(badArityArgs) && false); }
catch (IllegalArgumentException ex) { } // OK
}
@@ -959,23 +963,63 @@
* @see #asCollector
*/
public MethodHandle asSpreader(Class<?> arrayType, int arrayLength) {
- MethodType postSpreadType = asSpreaderChecks(arrayType, arrayLength);
+ return asSpreader(type().parameterCount() - arrayLength, arrayType, arrayLength);
+ }
+
+ /**
+ * Makes an <em>array-spreading</em> method handle, which accepts an array argument at a given position and spreads
+ * its elements as positional arguments in place of the array. The new method handle adapts, as its <i>target</i>,
+ * the current method handle. The type of the adapter will be the same as the type of the target, except that the
+ * {@code arrayLength} parameters of the target's type, starting at the zero-based position {@code spreadArgPos},
+ * are replaced by a single array parameter of type {@code arrayType}.
+ * <p>
+ * This method behaves very much like {@link #asSpreader(Class, int)}, but accepts an additional {@code spreadArgPos}
+ * argument to indicate at which position in the parameter list the spreading should take place.
+ *
+ * @apiNote Example:
+ * <blockquote><pre>{@code
+ MethodHandle compare = LOOKUP.findStatic(Objects.class, "compare", methodType(int.class, Object.class, Object.class, Comparator.class));
+ MethodHandle compare2FromArray = compare.asSpreader(0, Object[].class, 2);
+ Object[] ints = new Object[]{3, 9, 7, 7};
+ Comparator<Integer> cmp = (a, b) -> a - b;
+ assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 0, 2), cmp) < 0);
+ assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 1, 3), cmp) > 0);
+ assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 2, 4), cmp) == 0);
+ * }</pre></blockquote>
+ * @param spreadArgPos the position (zero-based index) in the argument list at which spreading should start.
+ * @param arrayType usually {@code Object[]}, the type of the array argument from which to extract the spread arguments
+ * @param arrayLength the number of arguments to spread from an incoming array argument
+ * @return a new method handle which spreads an array argument at a given position,
+ * before calling the original method handle
+ * @throws NullPointerException if {@code arrayType} is a null reference
+ * @throws IllegalArgumentException if {@code arrayType} is not an array type,
+ * or if target does not have at least
+ * {@code arrayLength} parameter types,
+ * or if {@code arrayLength} is negative,
+ * or if {@code spreadArgPos} has an illegal value (negative, or together with arrayLength exceeding the
+ * number of arguments),
+ * or if the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ * @throws WrongMethodTypeException if the implied {@code asType} call fails
+ *
+ * @see #asSpreader(Class, int)
+ * @since 9
+ */
+ public MethodHandle asSpreader(int spreadArgPos, Class<?> arrayType, int arrayLength) {
+ MethodType postSpreadType = asSpreaderChecks(arrayType, spreadArgPos, arrayLength);
// BEGIN Android-changed: Android specific implementation.
/*
- int arity = type().parameterCount();
- int spreadArgPos = arity - arrayLength;
MethodHandle afterSpread = this.asType(postSpreadType);
BoundMethodHandle mh = afterSpread.rebind();
LambdaForm lform = mh.editor().spreadArgumentsForm(1 + spreadArgPos, arrayType, arrayLength);
- MethodType preSpreadType = postSpreadType.replaceParameterTypes(spreadArgPos, arity, arrayType);
+ MethodType preSpreadType = postSpreadType.replaceParameterTypes(spreadArgPos, spreadArgPos + arrayLength, arrayType);
return mh.copyWith(preSpreadType, lform);
*/
- final int targetParamCount = postSpreadType.parameterCount();
- MethodType dropArrayArgs = postSpreadType.dropParameterTypes(
- (targetParamCount - arrayLength), targetParamCount);
- MethodType adapterType = dropArrayArgs.appendParameterTypes(arrayType);
-
- return new Transformers.Spreader(this, adapterType, arrayLength);
+ final int spreadEnd = spreadArgPos + arrayLength;
+ final MethodType adapterType =
+ postSpreadType.replaceParameterTypes(spreadArgPos, spreadEnd, arrayType);
+ return new Transformers.Spreader(
+ asType(postSpreadType), adapterType, spreadArgPos, arrayLength);
// END Android-changed: Android specific implementation.
}
@@ -983,15 +1027,18 @@
* See if {@code asSpreader} can be validly called with the given arguments.
* Return the type of the method handle call after spreading but before conversions.
*/
- private MethodType asSpreaderChecks(Class<?> arrayType, int arrayLength) {
+ private MethodType asSpreaderChecks(Class<?> arrayType, int pos, int arrayLength) {
spreadArrayChecks(arrayType, arrayLength);
int nargs = type().parameterCount();
if (nargs < arrayLength || arrayLength < 0)
throw newIllegalArgumentException("bad spread array length");
+ if (pos < 0 || pos + arrayLength > nargs) {
+ throw newIllegalArgumentException("bad spread position");
+ }
Class<?> arrayElement = arrayType.getComponentType();
MethodType mtype = type();
boolean match = true, fail = false;
- for (int i = nargs - arrayLength; i < nargs; i++) {
+ for (int i = pos; i < pos + arrayLength; i++) {
Class<?> ptype = mtype.parameterType(i);
if (ptype != arrayElement) {
match = false;
@@ -1002,11 +1049,11 @@
}
}
if (match) return mtype;
- MethodType needType = mtype.asSpreaderType(arrayType, arrayLength);
+ MethodType needType = mtype.asSpreaderType(arrayType, pos, arrayLength);
if (!fail) return needType;
// elicit an error:
this.asType(needType);
- throw newInternalError("should not return", null);
+ throw newInternalError("should not return");
}
private void spreadArrayChecks(Class<?> arrayType, int arrayLength) {
diff --git a/ojluni/src/main/java/java/lang/invoke/MethodType.java b/ojluni/src/main/java/java/lang/invoke/MethodType.java
index 0a35f45..cf92c7d 100644
--- a/ojluni/src/main/java/java/lang/invoke/MethodType.java
+++ b/ojluni/src/main/java/java/lang/invoke/MethodType.java
@@ -473,12 +473,13 @@
/** Replace the last arrayLength parameter types with the component type of arrayType.
* @param arrayType any array type
+ * @param pos position at which to spread
* @param arrayLength the number of parameter types to change
* @return the resulting type
*/
- /*non-public*/ MethodType asSpreaderType(Class<?> arrayType, int arrayLength) {
+ /*non-public*/ MethodType asSpreaderType(Class<?> arrayType, int pos, int arrayLength) {
assert(parameterCount() >= arrayLength);
- int spreadPos = ptypes.length - arrayLength;
+ int spreadPos = pos;
if (arrayLength == 0) return this; // nothing to change
if (arrayType == Object[].class) {
if (isGeneric()) return this; // nothing to change
@@ -493,10 +494,10 @@
}
Class<?> elemType = arrayType.getComponentType();
assert(elemType != null);
- for (int i = spreadPos; i < ptypes.length; i++) {
+ for (int i = spreadPos; i < spreadPos + arrayLength; i++) {
if (ptypes[i] != elemType) {
Class<?>[] fixedPtypes = ptypes.clone();
- Arrays.fill(fixedPtypes, i, ptypes.length, elemType);
+ Arrays.fill(fixedPtypes, i, spreadPos + arrayLength, elemType);
return methodType(rtype, fixedPtypes);
}
}
diff --git a/ojluni/src/main/java/java/lang/invoke/Transformers.java b/ojluni/src/main/java/java/lang/invoke/Transformers.java
index 2e3451d..f73afbe1 100644
--- a/ojluni/src/main/java/java/lang/invoke/Transformers.java
+++ b/ojluni/src/main/java/java/lang/invoke/Transformers.java
@@ -1315,8 +1315,8 @@
*/
private final int arrayOffset;
- /** The type char of the component type of the array. */
- private final char arrayTypeChar;
+ /** The component type of the array. */
+ private final Class<?> componentType;
/**
* The number of input arguments that will be present in the array. In other words, this is
@@ -1328,335 +1328,133 @@
* Range of arguments to copy verbatim from the input frame, This will cover all arguments
* that aren't a part of the trailing array.
*/
- private final Range copyRange;
+ private final Range leadingRange;
+ private final Range trailingRange;
- Spreader(MethodHandle target, MethodType spreaderType, int numArrayArgs) {
+ Spreader(MethodHandle target, MethodType spreaderType, int spreadArgPos, int numArrayArgs) {
super(spreaderType);
this.target = target;
- // Copy all arguments except the last argument (which is the trailing array argument
- // that needs to be spread).
- arrayOffset = spreaderType.parameterCount() - 1;
-
- // Get and cache the component type of the input array.
- final Class<?> componentType = spreaderType.ptypes()[arrayOffset].getComponentType();
+ arrayOffset = spreadArgPos;
+ componentType = spreaderType.ptypes()[arrayOffset].getComponentType();
if (componentType == null) {
- throw new AssertionError("Trailing argument must be an array.");
+ throw new AssertionError("Argument " + spreadArgPos + " must be an array.");
}
- arrayTypeChar = Wrapper.basicTypeChar(componentType);
-
this.numArrayArgs = numArrayArgs;
- // Copy all args except for the last argument.
- this.copyRange = EmulatedStackFrame.Range.of(spreaderType, 0, arrayOffset);
+ // Copy all args except the spreader array.
+ leadingRange = EmulatedStackFrame.Range.of(spreaderType, 0, arrayOffset);
+ trailingRange = EmulatedStackFrame.Range.from(spreaderType, arrayOffset + 1);
}
@Override
public void transform(EmulatedStackFrame callerFrame) throws Throwable {
- // Create a new stack frame for the callee.
- EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type());
-
- // Copy all arguments except for the trailing array argument.
- callerFrame.copyRangeTo(targetFrame, copyRange, 0, 0);
-
- // Attach the writer, prepare to spread the trailing array arguments into
- // the callee frame.
- StackFrameWriter writer = new StackFrameWriter();
- writer.attach(targetFrame, arrayOffset, copyRange.numReferences, copyRange.numBytes);
-
// Get the array reference and check that its length is as expected.
- Object arrayObj =
- callerFrame.getReference(
- copyRange.numReferences, this.type().ptypes()[arrayOffset]);
+ final Class<?> arrayType = type().parameterType(arrayOffset);
+ final Object arrayObj = callerFrame.getReference(arrayOffset, arrayType);
final int arrayLength = Array.getLength(arrayObj);
if (arrayLength != numArrayArgs) {
throw new IllegalArgumentException(
"Invalid array length " + arrayLength + " expected " + numArrayArgs);
}
- final MethodType type = target.type();
- switch (arrayTypeChar) {
+ // Create a new stack frame for the callee.
+ EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type());
+
+ // Copy ranges not affected by the spreading.
+ callerFrame.copyRangeTo(targetFrame, leadingRange, 0, 0);
+ if (componentType.isPrimitive()) {
+ final int elementBytes = EmulatedStackFrame.getSize(componentType);
+ final int spreadBytes = elementBytes * arrayLength;
+ callerFrame.copyRangeTo(targetFrame, trailingRange,
+ leadingRange.numReferences, leadingRange.numBytes + spreadBytes);
+ } else {
+ callerFrame.copyRangeTo(targetFrame, trailingRange,
+ leadingRange.numReferences + numArrayArgs, leadingRange.numBytes);
+ }
+
+ // Attach the writer, prepare to spread the trailing array arguments into
+ // the callee frame.
+ StackFrameWriter writer = new StackFrameWriter();
+ writer.attach(targetFrame, arrayOffset, leadingRange.numReferences, leadingRange.numBytes);
+
+ final Class<?> componentType = arrayType.getComponentType();
+ switch (Wrapper.basicTypeChar(componentType)) {
case 'L':
- spreadArray((Object[]) arrayObj, writer, type, numArrayArgs, arrayOffset);
+ {
+ final Object[] array = (Object[]) arrayObj;
+ for (int i = 0; i < array.length; ++i) {
+ writer.putNextReference(array[i], componentType);
+ }
break;
+ }
case 'I':
- spreadArray((int[]) arrayObj, writer, type, numArrayArgs, arrayOffset);
+ {
+ final int[] array = (int[]) arrayObj;
+ for (int i = 0; i < array.length; ++i) {
+ writer.putNextInt(array[i]);
+ }
break;
+ }
case 'J':
- spreadArray((long[]) arrayObj, writer, type, numArrayArgs, arrayOffset);
+ {
+ final long[] array = (long[]) arrayObj;
+ for (int i = 0; i < array.length; ++i) {
+ writer.putNextLong(array[i]);
+ }
break;
+ }
case 'B':
- spreadArray((byte[]) arrayObj, writer, type, numArrayArgs, arrayOffset);
+ {
+ final byte[] array = (byte[]) arrayObj;
+ for (int i = 0; i < array.length; ++i) {
+ writer.putNextByte(array[i]);
+ }
break;
+ }
case 'S':
- spreadArray((short[]) arrayObj, writer, type, numArrayArgs, arrayOffset);
+ {
+ final short[] array = (short[]) arrayObj;
+ for (int i = 0; i < array.length; ++i) {
+ writer.putNextShort(array[i]);
+ }
break;
+ }
case 'C':
- spreadArray((char[]) arrayObj, writer, type, numArrayArgs, arrayOffset);
+ {
+ final char[] array = (char[]) arrayObj;
+ for (int i = 0; i < array.length; ++i) {
+ writer.putNextChar(array[i]);
+ }
break;
+ }
case 'Z':
- spreadArray((boolean[]) arrayObj, writer, type, numArrayArgs, arrayOffset);
+ {
+ final boolean[] array = (boolean[]) arrayObj;
+ for (int i = 0; i < array.length; ++i) {
+ writer.putNextBoolean(array[i]);
+ }
break;
+ }
case 'F':
- spreadArray((float[]) arrayObj, writer, type, numArrayArgs, arrayOffset);
+ {
+ final float[] array = (float[]) arrayObj;
+ for (int i = 0; i < array.length; ++i) {
+ writer.putNextFloat(array[i]);
+ }
break;
+ }
case 'D':
- spreadArray((double[]) arrayObj, writer, type, numArrayArgs, arrayOffset);
+ {
+ final double[] array = (double[]) arrayObj;
+ for (int i = 0; i < array.length; ++i) {
+ writer.putNextDouble(array[i]);
+ }
break;
+ }
}
- invokeFromTransform(target, targetFrame);
+ invokeExactFromTransform(target, targetFrame);
targetFrame.copyReturnValueTo(callerFrame);
}
-
- public static void spreadArray(
- Object[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) {
- final Class<?>[] ptypes = type.ptypes();
- for (int i = 0; i < numArgs; ++i) {
- Class<?> argumentType = ptypes[i + offset];
- Object o = array[i];
- switch (Wrapper.basicTypeChar(argumentType)) {
- case 'L':
- writer.putNextReference(o, argumentType);
- break;
- case 'I':
- writer.putNextInt((int) o);
- break;
- case 'J':
- writer.putNextLong((long) o);
- break;
- case 'B':
- writer.putNextByte((byte) o);
- break;
- case 'S':
- writer.putNextShort((short) o);
- break;
- case 'C':
- writer.putNextChar((char) o);
- break;
- case 'Z':
- writer.putNextBoolean((boolean) o);
- break;
- case 'F':
- writer.putNextFloat((float) o);
- break;
- case 'D':
- writer.putNextDouble((double) o);
- break;
- }
- }
- }
-
- public static void spreadArray(
- int[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) {
- final Class<?>[] ptypes = type.ptypes();
- for (int i = 0; i < numArgs; ++i) {
- Class<?> argumentType = ptypes[i + offset];
- int j = array[i];
- switch (Wrapper.basicTypeChar(argumentType)) {
- case 'L':
- writer.putNextReference(j, argumentType);
- break;
- case 'I':
- writer.putNextInt(j);
- break;
- case 'J':
- writer.putNextLong(j);
- break;
- case 'F':
- writer.putNextFloat(j);
- break;
- case 'D':
- writer.putNextDouble(j);
- break;
- default:
- throw new AssertionError();
- }
- }
- }
-
- public static void spreadArray(
- long[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) {
- final Class<?>[] ptypes = type.ptypes();
- for (int i = 0; i < numArgs; ++i) {
- Class<?> argumentType = ptypes[i + offset];
- long l = array[i];
- switch (Wrapper.basicTypeChar(argumentType)) {
- case 'L':
- writer.putNextReference(l, argumentType);
- break;
- case 'J':
- writer.putNextLong(l);
- break;
- case 'F':
- writer.putNextFloat((float) l);
- break;
- case 'D':
- writer.putNextDouble((double) l);
- break;
- default:
- throw new AssertionError();
- }
- }
- }
-
- public static void spreadArray(
- byte[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) {
- final Class<?>[] ptypes = type.ptypes();
- for (int i = 0; i < numArgs; ++i) {
- Class<?> argumentType = ptypes[i + offset];
- byte b = array[i];
- switch (Wrapper.basicTypeChar(argumentType)) {
- case 'L':
- writer.putNextReference(b, argumentType);
- break;
- case 'I':
- writer.putNextInt(b);
- break;
- case 'J':
- writer.putNextLong(b);
- break;
- case 'B':
- writer.putNextByte(b);
- break;
- case 'S':
- writer.putNextShort(b);
- break;
- case 'F':
- writer.putNextFloat(b);
- break;
- case 'D':
- writer.putNextDouble(b);
- break;
- default:
- throw new AssertionError();
- }
- }
- }
-
- public static void spreadArray(
- short[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) {
- final Class<?>[] ptypes = type.ptypes();
- for (int i = 0; i < numArgs; ++i) {
- Class<?> argumentType = ptypes[i + offset];
- short s = array[i];
- switch (Wrapper.basicTypeChar(argumentType)) {
- case 'L':
- writer.putNextReference(s, argumentType);
- break;
- case 'I':
- writer.putNextInt(s);
- break;
- case 'J':
- writer.putNextLong(s);
- break;
- case 'S':
- writer.putNextShort(s);
- break;
- case 'F':
- writer.putNextFloat(s);
- break;
- case 'D':
- writer.putNextDouble(s);
- break;
- default:
- throw new AssertionError();
- }
- }
- }
-
- public static void spreadArray(
- char[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) {
- final Class<?>[] ptypes = type.ptypes();
- for (int i = 0; i < numArgs; ++i) {
- Class<?> argumentType = ptypes[i + offset];
- char c = array[i];
- switch (Wrapper.basicTypeChar(argumentType)) {
- case 'L':
- writer.putNextReference(c, argumentType);
- break;
- case 'I':
- writer.putNextInt(c);
- break;
- case 'J':
- writer.putNextLong(c);
- break;
- case 'C':
- writer.putNextChar(c);
- break;
- case 'F':
- writer.putNextFloat(c);
- break;
- case 'D':
- writer.putNextDouble(c);
- break;
- default:
- throw new AssertionError();
- }
- }
- }
-
- public static void spreadArray(
- boolean[] array,
- StackFrameWriter writer,
- MethodType type,
- int numArgs,
- int offset) {
- final Class<?>[] ptypes = type.ptypes();
- for (int i = 0; i < numArgs; ++i) {
- Class<?> argumentType = ptypes[i + offset];
- boolean z = array[i];
- switch (Wrapper.basicTypeChar(argumentType)) {
- case 'L':
- writer.putNextReference(z, argumentType);
- break;
- case 'Z':
- writer.putNextBoolean(z);
- break;
- default:
- throw new AssertionError();
- }
- }
- }
-
- public static void spreadArray(
- double[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) {
- final Class<?>[] ptypes = type.ptypes();
- for (int i = 0; i < numArgs; ++i) {
- Class<?> argumentType = ptypes[i + offset];
- double d = array[i];
- switch (Wrapper.basicTypeChar(argumentType)) {
- case 'L':
- writer.putNextReference(d, argumentType);
- break;
- case 'D':
- writer.putNextDouble(d);
- break;
- default:
- throw new AssertionError();
- }
- }
- }
-
- public static void spreadArray(
- float[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) {
- final Class<?>[] ptypes = type.ptypes();
- for (int i = 0; i < numArgs; ++i) {
- Class<?> argumentType = ptypes[i + offset];
- float f = array[i];
- switch (Wrapper.basicTypeChar(argumentType)) {
- case 'L':
- writer.putNextReference(f, argumentType);
- break;
- case 'D':
- writer.putNextDouble((double) f);
- break;
- case 'F':
- writer.putNextFloat(f);
- break;
- default:
- throw new AssertionError();
- }
- }
- }
}
/** Implements MethodHandle.asCollector. */
diff --git a/ojluni/src/test/java/lang/invoke/SpreadCollectTest.java b/ojluni/src/test/java/lang/invoke/SpreadCollectTest.java
new file mode 100644
index 0000000..78332d6
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/SpreadCollectTest.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/* @test
+ * @bug 8139885
+ * @bug 8143798
+ * @run testng/othervm -ea -esa test.java.lang.invoke.SpreadCollectTest
+ */
+
+package test.java.lang.invoke;
+
+import java.io.StringWriter;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.WrongMethodTypeException;
+import java.util.*;
+
+import static java.lang.invoke.MethodType.methodType;
+
+import static org.testng.AssertJUnit.*;
+
+import org.testng.annotations.*;
+
+/**
+ * Tests for the new asSpreader/asCollector API added in JEP 274.
+ */
+public class SpreadCollectTest {
+
+ static final Lookup LOOKUP = MethodHandles.lookup();
+
+ @Test
+ public static void testAsSpreader() throws Throwable {
+ MethodHandle spreader = SpreadCollect.MH_forSpreading.asSpreader(1, int[].class, 3);
+ assertEquals(SpreadCollect.MT_spreader, spreader.type());
+ assertEquals("A456B", (String) spreader.invoke("A", new int[]{4, 5, 6}, "B"));
+ }
+
+ @Test
+ public static void testAsSpreaderExample() throws Throwable {
+ // test the JavaDoc asSpreader-with-pos example
+ MethodHandle compare = LOOKUP.findStatic(Objects.class, "compare", methodType(int.class, Object.class, Object.class, Comparator.class));
+ MethodHandle compare2FromArray = compare.asSpreader(0, Object[].class, 2);
+ Object[] ints = new Object[]{3, 9, 7, 7};
+ Comparator<Integer> cmp = (a, b) -> a - b;
+ assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 0, 2), cmp) < 0);
+ assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 1, 3), cmp) > 0);
+ assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 2, 4), cmp) == 0);
+ }
+
+ @DataProvider
+ static Object[][] asSpreaderIllegalPositions() {
+ return new Object[][]{{-7}, {3}, {19}};
+ }
+
+ @Test(dataProvider = "asSpreaderIllegalPositions")
+ public static void testAsSpreaderIllegalPos(int p) throws Throwable {
+ boolean caught = false;
+ try {
+ SpreadCollect.MH_forSpreading.asSpreader(p, Object[].class, 3);
+ } catch (IllegalArgumentException iae) {
+ assertEquals("bad spread position", iae.getMessage());
+ caught = true;
+ }
+ assertTrue(caught);
+ }
+
+ @Test(expectedExceptions = {WrongMethodTypeException.class})
+ public static void testAsSpreaderIllegalMethodType() {
+ MethodHandle h = MethodHandles.dropArguments(MethodHandles.constant(String.class, ""), 0, int.class, int.class);
+ MethodHandle s = h.asSpreader(String[].class, 1);
+ }
+
+ @Test(expectedExceptions = {NullPointerException.class})
+ public static void testAsSpreaderNullArrayType() {
+ SpreadCollect.MH_forSpreading.asSpreader(null, 0);
+ }
+
+ @Test(expectedExceptions = {NullPointerException.class})
+ public static void testAsSpreaderNullArrayNonZeroLength() {
+ SpreadCollect.MH_forSpreading.asSpreader(null, 1);
+ }
+
+ @Test(expectedExceptions = {IllegalArgumentException.class})
+ public static void testAsSpreaderTooManyParams() throws Throwable {
+ SpreadCollect.MH_forSpreading.asSpreader(1, int[].class, 6);
+ }
+
+ @Test
+ public static void testAsCollector() throws Throwable {
+ MethodHandle collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 1);
+ assertEquals(SpreadCollect.MT_collector1, collector.type());
+ assertEquals("A4B", (String) collector.invoke("A", 4, "B"));
+ collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 2);
+ assertEquals(SpreadCollect.MT_collector2, collector.type());
+ assertEquals("A45B", (String) collector.invoke("A", 4, 5, "B"));
+ collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 3);
+ assertEquals(SpreadCollect.MT_collector3, collector.type());
+ assertEquals("A456B", (String) collector.invoke("A", 4, 5, 6, "B"));
+ }
+
+ @Test
+ public static void testAsCollectorInvokeWithArguments() throws Throwable {
+ MethodHandle collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 1);
+ assertEquals(SpreadCollect.MT_collector1, collector.type());
+ assertEquals("A4B", (String) collector.invokeWithArguments("A", 4, "B"));
+ collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 2);
+ assertEquals(SpreadCollect.MT_collector2, collector.type());
+ assertEquals("A45B", (String) collector.invokeWithArguments("A", 4, 5, "B"));
+ collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 3);
+ assertEquals(SpreadCollect.MT_collector3, collector.type());
+ assertEquals("A456B", (String) collector.invokeWithArguments("A", 4, 5, 6, "B"));
+ }
+
+ @Test
+ public static void testAsCollectorLeading() throws Throwable {
+ MethodHandle collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 1);
+ assertEquals(SpreadCollect.MT_collectorLeading1, collector.type());
+ assertEquals("7Q", (String) collector.invoke(7, "Q"));
+ collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 2);
+ assertEquals(SpreadCollect.MT_collectorLeading2, collector.type());
+ assertEquals("78Q", (String) collector.invoke(7, 8, "Q"));
+ collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 3);
+ assertEquals(SpreadCollect.MT_collectorLeading3, collector.type());
+ assertEquals("789Q", (String) collector.invoke(7, 8, 9, "Q"));
+ }
+
+ @Test
+ public static void testAsCollectorLeadingInvokeWithArguments() throws Throwable {
+ MethodHandle collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 1);
+ assertEquals(SpreadCollect.MT_collectorLeading1, collector.type());
+ assertEquals("7Q", (String) collector.invokeWithArguments(7, "Q"));
+ collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 2);
+ assertEquals(SpreadCollect.MT_collectorLeading2, collector.type());
+ assertEquals("78Q", (String) collector.invokeWithArguments(7, 8, "Q"));
+ collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 3);
+ assertEquals(SpreadCollect.MT_collectorLeading3, collector.type());
+ assertEquals("789Q", (String) collector.invokeWithArguments(7, 8, 9, "Q"));
+ }
+
+ @Test
+ public static void testAsCollectorNone() throws Throwable {
+ MethodHandle collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 0);
+ assertEquals(SpreadCollect.MT_collector0, collector.type());
+ assertEquals("AB", (String) collector.invoke("A", "B"));
+ }
+
+ @DataProvider
+ static Object[][] asCollectorIllegalPositions() {
+ return new Object[][]{{-1}, {17}};
+ }
+
+ @Test(dataProvider = "asCollectorIllegalPositions")
+ public static void testAsCollectorIllegalPos(int p) {
+ boolean caught = false;
+ try {
+ SpreadCollect.MH_forCollecting.asCollector(p, int[].class, 0);
+ } catch (IllegalArgumentException iae) {
+ assertEquals("bad collect position", iae.getMessage());
+ caught = true;
+ }
+ assertTrue(caught);
+ }
+
+ @Test
+ public static void testAsCollectorExample() throws Throwable {
+ // test the JavaDoc asCollector-with-pos example
+ StringWriter swr = new StringWriter();
+ MethodHandle swWrite = LOOKUP.
+ findVirtual(StringWriter.class, "write", methodType(void.class, char[].class, int.class, int.class)).
+ bindTo(swr);
+ MethodHandle swWrite4 = swWrite.asCollector(0, char[].class, 4);
+ swWrite4.invoke('A', 'B', 'C', 'D', 1, 2);
+ assertEquals("BC", swr.toString());
+ swWrite4.invoke('P', 'Q', 'R', 'S', 0, 4);
+ assertEquals("BCPQRS", swr.toString());
+ swWrite4.invoke('W', 'X', 'Y', 'Z', 3, 1);
+ assertEquals("BCPQRSZ", swr.toString());
+ }
+
+ static class SpreadCollect {
+
+ static String forSpreading(String s1, int i1, int i2, int i3, String s2) {
+ return s1 + i1 + i2 + i3 + s2;
+ }
+
+ static String forCollecting(String s1, int[] is, String s2) {
+ StringBuilder sb = new StringBuilder(s1);
+ for (int i : is) {
+ sb.append(i);
+ }
+ return sb.append(s2).toString();
+ }
+
+ static String forCollectingLeading(int[] is, String s) {
+ return forCollecting("", is, s);
+ }
+
+ static final Class<SpreadCollect> SPREAD_COLLECT = SpreadCollect.class;
+
+ static final MethodType MT_forSpreading = methodType(String.class, String.class, int.class, int.class, int.class, String.class);
+ static final MethodType MT_forCollecting = methodType(String.class, String.class, int[].class, String.class);
+ static final MethodType MT_forCollectingLeading = methodType(String.class, int[].class, String.class);
+
+ static final MethodHandle MH_forSpreading;
+ static final MethodHandle MH_forCollecting;
+ static final MethodHandle MH_forCollectingLeading;
+
+ static final MethodType MT_spreader = methodType(String.class, String.class, int[].class, String.class);
+ static final MethodType MT_collector0 = methodType(String.class, String.class, String.class);
+ static final MethodType MT_collector1 = methodType(String.class, String.class, int.class, String.class);
+ static final MethodType MT_collector2 = methodType(String.class, String.class, int.class, int.class, String.class);
+ static final MethodType MT_collector3 = methodType(String.class, String.class, int.class, int.class, int.class, String.class);
+ static final MethodType MT_collectorLeading1 = methodType(String.class, int.class, String.class);
+ static final MethodType MT_collectorLeading2 = methodType(String.class, int.class, int.class, String.class);
+ static final MethodType MT_collectorLeading3 = methodType(String.class, int.class, int.class, int.class, String.class);
+
+ static final String NONE_ERROR = "zero array length in MethodHandle.asCollector";
+
+ static {
+ try {
+ MH_forSpreading = LOOKUP.findStatic(SPREAD_COLLECT, "forSpreading", MT_forSpreading);
+ MH_forCollecting = LOOKUP.findStatic(SPREAD_COLLECT, "forCollecting", MT_forCollecting);
+ MH_forCollectingLeading = LOOKUP.findStatic(SPREAD_COLLECT, "forCollectingLeading", MT_forCollectingLeading);
+ } catch (Exception e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ }
+
+}