Merge "Update README to add the nullability annotations to ojluni classes"
diff --git a/JavaLibrary.bp b/JavaLibrary.bp
index 4838a11..03d84c2 100644
--- a/JavaLibrary.bp
+++ b/JavaLibrary.bp
@@ -484,6 +484,7 @@
         "//frameworks/base/location/tests/locationtests",
         "//frameworks/base/core/tests/coretests",
         "//frameworks/base/wifi/tests",
+        "//libcore/luni/src/test/java9compatibility",
         "//packages/modules/Wifi/framework/tests",
     ],
     hostdex: true,
@@ -655,6 +656,7 @@
 
     static_libs: [
         "core-compat-test-rules",
+        "core-java-9-compatibility-tests",
         "core-java-9-language-tests",
         "core-java-11-language-tests",
         "core-test-rules",
diff --git a/api/current.txt b/api/current.txt
index 0f46df2..7de96a1 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -5610,6 +5610,7 @@
     field public static final int SO_OOBINLINE = 4099; // 0x1003
     field public static final int SO_RCVBUF = 4098; // 0x1002
     field public static final int SO_REUSEADDR = 4; // 0x4
+    field public static final int SO_REUSEPORT = 14; // 0xe
     field public static final int SO_SNDBUF = 4097; // 0x1001
     field public static final int SO_TIMEOUT = 4102; // 0x1006
     field public static final int TCP_NODELAY = 1; // 0x1
@@ -5641,6 +5642,7 @@
     field public static final java.net.SocketOption<java.lang.Integer> SO_LINGER;
     field public static final java.net.SocketOption<java.lang.Integer> SO_RCVBUF;
     field public static final java.net.SocketOption<java.lang.Boolean> SO_REUSEADDR;
+    field public static final java.net.SocketOption<java.lang.Boolean> SO_REUSEPORT;
     field public static final java.net.SocketOption<java.lang.Integer> SO_SNDBUF;
     field public static final java.net.SocketOption<java.lang.Boolean> TCP_NODELAY;
   }
@@ -8784,6 +8786,10 @@
     field public static final long serialVersionUID = -8727434096241101194L; // 0x86e1ecedeceab676L
   }
 
+  public interface XECKey {
+    method public java.security.spec.AlgorithmParameterSpec getParams();
+  }
+
 }
 
 package java.security.spec {
@@ -8975,6 +8981,18 @@
     method public final String getFormat();
   }
 
+  public class XECPrivateKeySpec implements java.security.spec.KeySpec {
+    ctor public XECPrivateKeySpec(java.security.spec.AlgorithmParameterSpec, byte[]);
+    method public java.security.spec.AlgorithmParameterSpec getParams();
+    method public byte[] getScalar();
+  }
+
+  public class XECPublicKeySpec implements java.security.spec.KeySpec {
+    ctor public XECPublicKeySpec(java.security.spec.AlgorithmParameterSpec, java.math.BigInteger);
+    method public java.security.spec.AlgorithmParameterSpec getParams();
+    method public java.math.BigInteger getU();
+  }
+
 }
 
 package java.sql {
diff --git a/dalvik/src/main/java/dalvik/system/EmulatedStackFrame.java b/dalvik/src/main/java/dalvik/system/EmulatedStackFrame.java
index 7a1c20b..4c4f300 100644
--- a/dalvik/src/main/java/dalvik/system/EmulatedStackFrame.java
+++ b/dalvik/src/main/java/dalvik/system/EmulatedStackFrame.java
@@ -16,6 +16,8 @@
 
 package dalvik.system;
 
+import sun.invoke.util.Wrapper;
+
 import java.lang.invoke.MethodType;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -177,6 +179,20 @@
     }
 
     /**
+     * Convert parameter index to index within references array.
+     */
+    int getReferenceIndex(int parameterIndex) {
+        final Class [] ptypes = type.ptypes();
+        int refIndex = 0;
+        for (int i = 0; i < parameterIndex; ++i) {
+            if (!ptypes[i].isPrimitive()) {
+                refIndex += 1;
+            }
+        }
+        return refIndex;
+    }
+
+    /**
      * Sets the {@code idx} to {@code reference}. Type checks are performed.
      */
     public void setReference(int idx, Object reference) {
@@ -184,12 +200,11 @@
         if (idx < 0 || idx >= ptypes.length) {
             throw new IllegalArgumentException("Invalid index: " + idx);
         }
-
         if (reference != null && !ptypes[idx].isInstance(reference)) {
             throw new IllegalStateException("reference is not of type: " + type.ptypes()[idx]);
         }
-
-        references[idx] = reference;
+        int referenceIndex = getReferenceIndex(idx);
+        references[referenceIndex] = reference;
     }
 
     /**
@@ -200,8 +215,8 @@
             throw new IllegalArgumentException("Argument: " + idx +
                     " is of type " + type.ptypes()[idx] + " expected " + referenceType + "");
         }
-
-        return (T) references[idx];
+        int referenceIndex = getReferenceIndex(idx);
+        return (T) references[referenceIndex];
     }
 
     /**
@@ -387,26 +402,36 @@
             }
         }
 
-        public static void copyNext(StackFrameReader reader, StackFrameWriter writer,
-                                    Class<?> type) {
-            if (!type.isPrimitive()) {
-                writer.putNextReference(reader.nextReference(type), type);
-            } else if (type == boolean.class) {
-                writer.putNextBoolean(reader.nextBoolean());
-            } else if (type == byte.class) {
-                writer.putNextByte(reader.nextByte());
-            } else if (type == char.class) {
-                writer.putNextChar(reader.nextChar());
-            } else if (type == short.class) {
-                writer.putNextShort(reader.nextShort());
-            } else if (type == int.class) {
-                writer.putNextInt(reader.nextInt());
-            } else if (type == long.class) {
-                writer.putNextLong(reader.nextLong());
-            } else if (type == float.class) {
-                writer.putNextFloat(reader.nextFloat());
-            } else if (type == double.class) {
-                writer.putNextDouble(reader.nextDouble());
+        public static void copyNext(
+                StackFrameReader reader, StackFrameWriter writer, Class<?> type) {
+            switch (Wrapper.basicTypeChar(type)) {
+                case 'L':
+                    writer.putNextReference(reader.nextReference(type), type);
+                    break;
+                case 'Z':
+                    writer.putNextBoolean(reader.nextBoolean());
+                    break;
+                case 'B':
+                    writer.putNextByte(reader.nextByte());
+                    break;
+                case 'C':
+                    writer.putNextChar(reader.nextChar());
+                    break;
+                case 'S':
+                    writer.putNextShort(reader.nextShort());
+                    break;
+                case 'I':
+                    writer.putNextInt(reader.nextInt());
+                    break;
+                case 'J':
+                    writer.putNextLong(reader.nextLong());
+                    break;
+                case 'F':
+                    writer.putNextFloat(reader.nextFloat());
+                    break;
+                case 'D':
+                    writer.putNextDouble(reader.nextDouble());
+                    break;
             }
         }
     }
diff --git a/luni/src/test/java/libcore/dalvik/system/EmulatedStackFrameTest.java b/luni/src/test/java/libcore/dalvik/system/EmulatedStackFrameTest.java
index c17fad7..8b69016 100644
--- a/luni/src/test/java/libcore/dalvik/system/EmulatedStackFrameTest.java
+++ b/luni/src/test/java/libcore/dalvik/system/EmulatedStackFrameTest.java
@@ -195,7 +195,7 @@
         } catch (IllegalArgumentException expected) {
         }
 
-        // Should succeeed.
+        // Should succeed.
         assertFalse(reader.nextBoolean());
 
         // The next attempt should fail.
@@ -215,7 +215,7 @@
         } catch (IllegalArgumentException expected) {
         }
 
-        // Should succeeed.
+        // Should succeed.
         writer.putNextBoolean(true);
 
         // The next attempt should fail.
@@ -225,4 +225,13 @@
         } catch (IllegalArgumentException expected) {
         }
     }
+
+    public void testGetSetReference() {
+        EmulatedStackFrame stackFrame = EmulatedStackFrame.create(MethodType.methodType(
+            void.class, new Class<?>[] { Integer.class, boolean.class, String.class }));
+        stackFrame.setReference(0, Integer.valueOf(-1));
+        assertEquals(Integer.valueOf(-1), stackFrame.getReference(0, Integer.class));
+        stackFrame.setReference(2, "Hello");
+        assertEquals("Hello", stackFrame.getReference(2, String.class));
+    }
 }
diff --git a/luni/src/test/java/libcore/java/io/ObjectStreamClassSuidTest.java b/luni/src/test/java/libcore/java/io/ObjectStreamClassSuidTest.java
new file mode 100644
index 0000000..28ab598
--- /dev/null
+++ b/luni/src/test/java/libcore/java/io/ObjectStreamClassSuidTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package libcore.java.io;
+
+import java.io.ObjectStreamClass;
+import java.io.ObjectStreamClass.DefaultSUIDCompatibilityListener;
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+import libcore.junit.util.SwitchTargetSdkVersionRule;
+import libcore.junit.util.SwitchTargetSdkVersionRule.TargetSdkVersion;
+import org.junit.FixMethodOrder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+@RunWith(JUnitParamsRunner.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class ObjectStreamClassSuidTest {
+
+  @Rule
+  public TestRule switchTargetSdkVersionRule = SwitchTargetSdkVersionRule.getInstance();
+
+  /**
+   * The default SUID for this should not be affected by the b/29064453 patch.
+   */
+  public static class BaseWithStaticInitializer implements Serializable {
+    static {
+      System.out.println(
+          "Static initializer for " + BaseWithoutStaticInitializer.class.getCanonicalName());
+    }
+  }
+
+  /**
+   * The default SUID for this should not be affected by the b/29064453 patch.
+   */
+  public static class BaseWithoutStaticInitializer implements Serializable {
+  }
+
+  /**
+   * The default SUID for this should not be affected by the b/29064453 patch.
+   */
+  public static class WithStaticInitializer extends BaseWithoutStaticInitializer {
+    static {
+      System.out.println(
+          "Static initializer for " + WithStaticInitializer.class.getCanonicalName());
+    }
+  }
+
+  /**
+   * The default SUID for this should not be affected by the b/29064453 patch.
+   */
+  public static class WithoutStaticInitializer extends BaseWithoutStaticInitializer {
+  }
+
+  /**
+   * The default SUID for this should be affected by the b/29064453 patch and so should differ
+   * between version <= 23 and version > 23.
+   */
+  public static class InheritStaticInitializer extends BaseWithStaticInitializer {
+  }
+
+  public static Object[][] defaultSUIDs() {
+    return new Object[][] {
+        // The default SUID for BaseWithStaticInitializer should not be affected by the b/29064453
+        // patch.
+        { BaseWithStaticInitializer.class, 1857698805282079740L, 1857698805282079740L },
+
+        // The default SUID for BaseWithoutStaticInitializer should not be affected by the
+        // b/29064453 patch.
+        { BaseWithoutStaticInitializer.class, -4805670618654058372L, -4805670618654058372L },
+
+        // The default SUID for WithStaticInitializer should not be affected by the b/29064453
+        // patch.
+        { WithStaticInitializer.class, 8758222524306909802L, 8758222524306909802L },
+
+        // The default SUID for WithStaticInitializer should not be affected by the
+        // b/29064453 patch.
+        { WithoutStaticInitializer.class, -6923417559496792279L, -6923417559496792279L },
+
+        // The default SUID for the InheritStaticInitializer should be affected by the b/29064453
+        // patch and so should differ between version <= 23 and version > 23.
+        { InheritStaticInitializer.class, 509356435664048990L, -6712883765570708525L },
+    };
+  }
+
+  @Parameters(method = "defaultSUIDs")
+  @Test
+  public void computeDefaultSUID_current(Class<?> clazz, long suid,
+      @SuppressWarnings("unused") long suid23) {
+    checkSerialVersionUID(suid, clazz, false);
+  }
+
+  @Parameters(method = "defaultSUIDs")
+  @Test
+  @TargetSdkVersion(23)
+  public void computeDefaultSUID_targetSdkVersion_23(Class<?> clazz, long suid, long suid23) {
+    // If the suid and suid23 hashes are different then a warning is expected to be logged.
+    boolean expectedWarning = suid23 != suid;
+    checkSerialVersionUID(suid23, clazz, expectedWarning);
+  }
+
+  private static void checkSerialVersionUID(
+      long expectedSUID, Class<?> clazz, boolean expectedWarning) {
+    // Use reflection to call the private static computeDefaultSUID method directly to avoid the
+    // caching performed by ObjectStreamClass.lookup(Class).
+    long defaultSUID;
+    DefaultSUIDCompatibilityListener savedListener
+        = ObjectStreamClass.suidCompatibilityListener;
+    try {
+      ObjectStreamClass.suidCompatibilityListener = (c, hash) -> {
+        // Delegate to the existing listener so that the warning is logged.
+        savedListener.warnDefaultSUIDTargetVersionDependent(clazz, hash);
+        if (expectedWarning) {
+          assertEquals(clazz, c);
+          assertEquals(expectedSUID, hash);
+        } else {
+          fail("Unexpected warning for " + c + " with defaultSUID " + hash);
+        }
+      };
+
+      Method computeDefaultSUIDMethod =
+          ObjectStreamClass.class.getDeclaredMethod("computeDefaultSUID", Class.class);
+      computeDefaultSUIDMethod.setAccessible(true);
+
+      defaultSUID = (Long) computeDefaultSUIDMethod.invoke(null, clazz);
+    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+      throw new IllegalStateException(e);
+    } finally {
+      ObjectStreamClass.suidCompatibilityListener = savedListener;
+    }
+    assertEquals(expectedSUID, defaultSUID);
+  }
+}
diff --git a/luni/src/test/java9compatibility/Android.bp b/luni/src/test/java9compatibility/Android.bp
new file mode 100644
index 0000000..1d5dab5
--- /dev/null
+++ b/luni/src/test/java9compatibility/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Android tests related to compatibility with pre-Java 11 language features.
+
+java_library {
+    name: "core-java-9-compatibility-tests",
+    hostdex: true,
+    srcs: [
+        "java/**/*.java",
+    ],
+    java_version: "1.9",
+    sdk_version: "none",
+    system_modules: "core-all-system-modules",
+    static_libs: [
+        "core-test-rules",
+        "junit",
+        "junit-params",
+    ],
+    visibility: ["//libcore"],
+}
diff --git a/luni/src/test/java/libcore/java/io/ObjectStreamClassTest.java b/luni/src/test/java9compatibility/java/libcore/java/io/ObjectStreamClassTest.java
similarity index 100%
rename from luni/src/test/java/libcore/java/io/ObjectStreamClassTest.java
rename to luni/src/test/java9compatibility/java/libcore/java/io/ObjectStreamClassTest.java
diff --git a/ojluni/src/main/java/java/lang/invoke/MethodHandle.java b/ojluni/src/main/java/java/lang/invoke/MethodHandle.java
index 17387a6..cb9eaa7 100644
--- a/ojluni/src/main/java/java/lang/invoke/MethodHandle.java
+++ b/ojluni/src/main/java/java/lang/invoke/MethodHandle.java
@@ -434,35 +434,26 @@
     // @interface PolymorphicSignature { }
     public @interface PolymorphicSignature { }
 
-    // Android-added: Comment to differentiate between type and nominalType.
     /**
      * The type of this method handle, this corresponds to the exact type of the method
      * being invoked.
-     * 
-     * @see #nominalType
      */
     private final MethodType type;
-    // Android-removed: LambdaForm and customizationCount unused on Android.
+
+    // Android-removed: LambdaForm is unused on Android.
     // They will be substituted with appropriate implementation / delegate classes.
     /*
     /*private* final LambdaForm form;
     // form is not private so that invokers can easily fetch it
-    /*private* MethodHandle asTypeCache;
+    */
+    /*private*/ MethodHandle asTypeCache;
     // asTypeCache is not private so that invokers can easily fetch it
+    /*
+    // Android-removed: customizationCount is unused on Android.
     /*non-public* byte customizationCount;
     // customizationCount should be accessible from invokers
     */
 
-    // BEGIN Android-added: Android specific implementation.
-    // The MethodHandle functionality is tightly coupled with internal details of the runtime and
-    // so Android has a completely different implementation compared to the RI.
-    /**
-     * The nominal type of this method handle, will be non-null if a method handle declares
-     * a different type from its "real" type, which is either the type of the method being invoked
-     * or the type of the emulated stackframe expected by an underyling adapter.
-     */
-    private MethodType nominalType;
-
     /**
      * The spread invoker associated with this type with zero trailing arguments.
      * This is used to speed up invokeWithArguments.
@@ -522,11 +513,6 @@
      * @return the method handle type
      */
     public MethodType type() {
-        // Android-added: Added nominalType field.
-        if (nominalType != null) {
-            return nominalType;
-        }
-
         return type;
     }
 
@@ -715,19 +701,15 @@
      * @see MethodHandles#spreadInvoker
      */
     public Object invokeWithArguments(Object... arguments) throws Throwable {
+        MethodType invocationType = MethodType.genericMethodType(arguments == null ? 0 : arguments.length);
         // BEGIN Android-changed: Android specific implementation.
-        // MethodType invocationType = MethodType.genericMethodType(arguments == null ? 0 : arguments.length);
         // return invocationType.invokers().spreadInvoker(0).invokeExact(asType(invocationType), arguments);
-        MethodHandle invoker = null;
-        synchronized (this) {
-            if (cachedSpreadInvoker == null) {
-                cachedSpreadInvoker = MethodHandles.spreadInvoker(this.type(), 0);
-            }
-
-            invoker = cachedSpreadInvoker;
+        MethodHandle invoker = cachedSpreadInvoker;
+        if (invoker == null || !invoker.type().equals(invocationType)) {
+            invoker = MethodHandles.spreadInvoker(invocationType, 0);
+            cachedSpreadInvoker = invoker;
         }
-
-        return invoker.invoke(this, arguments);
+        return invoker.invoke(asType(invocationType), arguments);
         // END Android-changed: Android specific implementation.
     }
 
@@ -854,31 +836,26 @@
     public MethodHandle asType(MethodType newType) {
         // Fast path alternative to a heavyweight {@code asType} call.
         // Return 'this' if the conversion will be a no-op.
-        // Android-changed: Use `type()` rather than `type` due to nominal type.
-        if (newType == type()) {
+        // Android-changed: use equals() rather than = since MethodTypes are not interned.
+        if (newType.equals(type)) {
             return this;
         }
-        // Android-removed: Type conversion memoizing is unsupported on Android.
-        /*
         // Return 'this.asTypeCache' if the conversion is already memoized.
         MethodHandle atc = asTypeCached(newType);
         if (atc != null) {
             return atc;
         }
-        */
         return asTypeUncached(newType);
     }
 
-    // Android-removed: Type conversion memoizing is unsupported on Android.
-    /*
     private MethodHandle asTypeCached(MethodType newType) {
         MethodHandle atc = asTypeCache;
-        if (atc != null && newType == atc.type) {
+        // Android-changed: use equals() rather than = since MethodTypes are not interned.
+        if (atc != null && newType.equals(atc.type)) {
             return atc;
         }
         return null;
     }
-    */
 
     /** Override this to change asType behavior. */
     /*non-public*/ MethodHandle asTypeUncached(MethodType newType) {
@@ -886,9 +863,7 @@
             throw new WrongMethodTypeException("cannot convert "+this+" to "+newType);
         // BEGIN Android-changed: Android specific implementation.
         // return asTypeCache = MethodHandleImpl.makePairwiseConvert(this, newType, true);
-        MethodHandle mh = duplicate();
-        mh.nominalType = newType;
-        return mh;
+        return asTypeCache = new Transformers.AsTypeAdapter(this, newType);
         // END Android-changed: Android specific implementation.
     }
 
@@ -1415,8 +1390,7 @@
      * @see MethodHandles#insertArguments
      */
     public MethodHandle bindTo(Object x) {
-        // Android-changed: use `type()` instead of `type` due to nominal type.
-        x = type().leadingReferenceParameter().cast(x);  // throw CCE if needed
+        x = type.leadingReferenceParameter().cast(x);  // throw CCE if needed
         // Android-changed: Android specific implementation.
         // return bindArgumentL(0, x);
         return new Transformers.BindTo(this, x);
@@ -1444,8 +1418,7 @@
         return standardString();
     }
     String standardString() {
-        // Android-changed: use `type()` rather than `type` due to Android's nominal type.
-        return "MethodHandle"+type();
+        return "MethodHandle"+type;
     }
 
     // BEGIN Android-removed: Debugging support unused on Android.
diff --git a/ojluni/src/main/java/java/lang/invoke/MethodType.java b/ojluni/src/main/java/java/lang/invoke/MethodType.java
index 4652c93..8bf0839 100644
--- a/ojluni/src/main/java/java/lang/invoke/MethodType.java
+++ b/ojluni/src/main/java/java/lang/invoke/MethodType.java
@@ -842,11 +842,12 @@
 
     /*non-public*/
     boolean isConvertibleTo(MethodType newType) {
-        MethodTypeForm oldForm = this.form();
-        MethodTypeForm newForm = newType.form();
-        if (oldForm == newForm)
-            // same parameter count, same primitive/object mix
-            return true;
+        // Android-removed: use of MethodTypeForm does not apply to Android implementation.
+        // MethodTypeForm oldForm = this.form();
+        // MethodTypeForm newForm = newType.form();
+        // if (oldForm == newForm)
+        //     // same parameter count, same primitive/object mix
+        //     return true;
         if (!canConvert(returnType(), newType.returnType()))
             return false;
         Class<?>[] srcTypes = newType.ptypes;
@@ -861,13 +862,14 @@
                 return false;
             return true;
         }
-        if ((oldForm.primitiveParameterCount() == 0 && oldForm.erasedType == this) ||
-            (newForm.primitiveParameterCount() == 0 && newForm.erasedType == newType)) {
-            // Somewhat complicated test to avoid a loop of 2 or more trips.
-            // If either type has only Object parameters, we know we can convert.
-            assert(canConvertParameters(srcTypes, dstTypes));
-            return true;
-        }
+        // Android-removed: use of MethodTypeForm does not apply to Android implementation.
+        // if ((oldForm.primitiveParameterCount() == 0 && oldForm.erasedType == this) ||
+        //     (newForm.primitiveParameterCount() == 0 && newForm.erasedType == newType)) {
+        //     // Somewhat complicated test to avoid a loop of 2 or more trips.
+        //     // If either type has only Object parameters, we know we can convert.
+        //     assert(canConvertParameters(srcTypes, dstTypes));
+        //     return true;
+        // }
         return canConvertParameters(srcTypes, dstTypes);
     }
 
diff --git a/ojluni/src/main/java/java/lang/invoke/Transformers.java b/ojluni/src/main/java/java/lang/invoke/Transformers.java
index 59bb159..be0c3e1 100644
--- a/ojluni/src/main/java/java/lang/invoke/Transformers.java
+++ b/ojluni/src/main/java/java/lang/invoke/Transformers.java
@@ -21,41 +21,43 @@
 
 package java.lang.invoke;
 
+import static dalvik.system.EmulatedStackFrame.StackFrameAccessor.copyNext;
+
 import dalvik.system.EmulatedStackFrame;
 import dalvik.system.EmulatedStackFrame.Range;
 import dalvik.system.EmulatedStackFrame.StackFrameAccessor;
 import dalvik.system.EmulatedStackFrame.StackFrameReader;
 import dalvik.system.EmulatedStackFrame.StackFrameWriter;
+
+import sun.invoke.util.Wrapper;
+import sun.misc.Unsafe;
+
 import java.lang.reflect.Array;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
-import sun.invoke.util.Wrapper;
-import sun.misc.Unsafe;
-import static dalvik.system.EmulatedStackFrame.StackFrameAccessor.copyNext;
 
-/**
- * @hide Public for testing only.
- */
+/** @hide Public for testing only. */
 public class Transformers {
     private Transformers() {}
 
     static {
         try {
-            TRANSFORM_INTERNAL = MethodHandle.class.getDeclaredMethod("transformInternal",
-                    EmulatedStackFrame.class);
+            TRANSFORM_INTERNAL =
+                    MethodHandle.class.getDeclaredMethod(
+                            "transformInternal", EmulatedStackFrame.class);
         } catch (NoSuchMethodException nsme) {
             throw new AssertionError();
         }
     }
 
     /**
-     * Method reference to the private {@code MethodHandle.transformInternal} method. This is
-     * cached here because it's the point of entry for all transformers.
+     * Method reference to the private {@code MethodHandle.transformInternal} method. This is cached
+     * here because it's the point of entry for all transformers.
      */
     private static final Method TRANSFORM_INTERNAL;
 
     /** @hide */
-    public static abstract class Transformer extends MethodHandle implements Cloneable {
+    public abstract static class Transformer extends MethodHandle implements Cloneable {
         protected Transformer(MethodType type) {
             super(TRANSFORM_INTERNAL.getArtMethod(), MethodHandle.INVOKE_TRANSFORM, type);
         }
@@ -68,20 +70,38 @@
         public Object clone() throws CloneNotSupportedException {
             return super.clone();
         }
+
+        protected void invokeFromTransform(MethodHandle target, EmulatedStackFrame stackFrame)
+                throws Throwable {
+            if (target instanceof Transformer) {
+                ((Transformer) target).transform(stackFrame);
+            } else {
+                target.invoke(stackFrame);
+            }
+        }
+
+        protected void invokeExactFromTransform(MethodHandle target, EmulatedStackFrame stackFrame)
+                throws Throwable {
+            if (target instanceof Transformer) {
+                ((Transformer) target).transform(stackFrame);
+            } else {
+                target.invokeExact(stackFrame);
+            }
+        }
     }
 
     /**
      * A method handle that always throws an exception of a specified type.
      *
-     * The handle declares a nominal return type, which is immaterial to the execution
-     * of the handle because it never returns.
+     * <p>The handle declares a nominal return type, which is immaterial to the execution of the
+     * handle because it never returns.
      *
      * @hide
      */
     public static class AlwaysThrow extends Transformer {
         private final Class<? extends Throwable> exceptionType;
 
-        public AlwaysThrow(Class<?> nominalReturnType, Class<? extends  Throwable> exType) {
+        public AlwaysThrow(Class<?> nominalReturnType, Class<? extends Throwable> exType) {
             super(MethodType.methodType(nominalReturnType, exType));
             this.exceptionType = exType;
         }
@@ -92,22 +112,19 @@
         }
     }
 
-    /**
-     * Implements {@code MethodHandles.dropArguments}.
-     */
+    /** Implements {@code MethodHandles.dropArguments}. */
     public static class DropArguments extends Transformer {
         private final MethodHandle delegate;
 
         private final EmulatedStackFrame.Range range1;
 
         /**
-         * Note that {@code range2} will be null if the arguments that are being dropped
-         * are the last {@code n}.
+         * Note that {@code range2} will be null if the arguments that are being dropped are the
+         * last {@code n}.
          */
         /* @Nullable */ private final EmulatedStackFrame.Range range2;
 
-        public DropArguments(MethodType type, MethodHandle delegate,
-                             int startPos, int numDropped) {
+        public DropArguments(MethodType type, MethodHandle delegate, int startPos, int numDropped) {
             super(type);
 
             this.delegate = delegate;
@@ -127,25 +144,23 @@
         public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable {
             EmulatedStackFrame calleeFrame = EmulatedStackFrame.create(delegate.type());
 
-            emulatedStackFrame.copyRangeTo(calleeFrame, range1,
-                    0 /* referencesStart */, 0 /* stackFrameStart */);
+            emulatedStackFrame.copyRangeTo(
+                    calleeFrame, range1, 0 /* referencesStart */, 0 /* stackFrameStart */);
 
             if (range2 != null) {
                 final int referencesStart = range1.numReferences;
                 final int stackFrameStart = range1.numBytes;
 
-                emulatedStackFrame.copyRangeTo(calleeFrame, range2,
-                        referencesStart, stackFrameStart);
+                emulatedStackFrame.copyRangeTo(
+                        calleeFrame, range2, referencesStart, stackFrameStart);
             }
 
-            delegate.invoke(calleeFrame);
+            invokeFromTransform(delegate, calleeFrame);
             calleeFrame.copyReturnValueTo(emulatedStackFrame);
         }
     }
 
-    /**
-     * Implements {@code MethodHandles.catchException}.
-     */
+    /** Implements {@code MethodHandles.catchException}. */
     public static class CatchException extends Transformer {
         private final MethodHandle target;
         private final MethodHandle handler;
@@ -163,14 +178,15 @@
             // We only copy the first "count" args, dropping others if required. Note that
             // we subtract one because the first handler arg is the exception thrown by the
             // target.
-            handlerArgsRange = EmulatedStackFrame.Range.of(target.type(), 0,
-                    (handler.type().parameterCount() - 1));
+            handlerArgsRange =
+                    EmulatedStackFrame.Range.of(
+                            target.type(), 0, (handler.type().parameterCount() - 1));
         }
 
         @Override
         public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable {
             try {
-                target.invoke(emulatedStackFrame);
+                invokeFromTransform(target, emulatedStackFrame);
             } catch (Throwable th) {
                 if (th.getClass() == exType) {
                     // We've gotten an exception of the appropriate type, so we need to call
@@ -183,11 +199,14 @@
                     // We then copy other arguments that need to be passed through to the handler.
                     // Note that we might drop arguments at the end, if needed. Note that
                     // referencesStart == 1 because the first argument is the exception type.
-                    emulatedStackFrame.copyRangeTo(fallback, handlerArgsRange,
-                            1 /* referencesStart */, 0 /* stackFrameStart */);
+                    emulatedStackFrame.copyRangeTo(
+                            fallback,
+                            handlerArgsRange,
+                            1 /* referencesStart */,
+                            0 /* stackFrameStart */);
 
                     // Perform the invoke and return the appropriate value.
-                    handler.invoke(fallback);
+                    invokeFromTransform(handler, fallback);
                     fallback.copyReturnValueTo(emulatedStackFrame);
                 } else {
                     // The exception is not of the expected type, we throw it.
@@ -197,9 +216,7 @@
         }
     }
 
-    /**
-     * Implements {@code MethodHandles.GuardWithTest}.
-     */
+    /** Implements {@code MethodHandles.GuardWithTest}. */
     public static class GuardWithTest extends Transformer {
         private final MethodHandle test;
         private final MethodHandle target;
@@ -215,7 +232,8 @@
             this.fallback = fallback;
 
             // The test method might have a subset of the arguments of the handle / target.
-            testArgsRange = EmulatedStackFrame.Range.of(target.type(), 0, test.type().parameterCount());
+            testArgsRange =
+                    EmulatedStackFrame.Range.of(target.type(), 0, test.type().parameterCount());
         }
 
         @Override
@@ -223,26 +241,28 @@
             EmulatedStackFrame testFrame = EmulatedStackFrame.create(test.type());
             emulatedStackFrame.copyRangeTo(testFrame, testArgsRange, 0, 0);
 
-            // We know that the return value for test is going to be boolean.class, so we don't have
-            // to do the copyReturnValue dance.
-            final boolean value = (boolean) test.invoke(testFrame);
-            if (value) {
-                target.invoke(emulatedStackFrame);
+            // We know that the return value for test is going to be boolean.class.
+            StackFrameReader reader = new StackFrameReader();
+            reader.attach(testFrame);
+            reader.makeReturnValueAccessor();
+            invokeFromTransform(test, testFrame);
+            final boolean testResult = (boolean) reader.nextBoolean();
+            if (testResult) {
+                invokeFromTransform(target, emulatedStackFrame);
             } else {
-                fallback.invoke(emulatedStackFrame);
+                invokeFromTransform(fallback, emulatedStackFrame);
             }
         }
     }
 
-    /**
-     * Implementation of MethodHandles.arrayElementGetter for reference types.
-     */
+    /** Implementation of MethodHandles.arrayElementGetter for reference types. */
     public static class ReferenceArrayElementGetter extends Transformer {
         private final Class<?> arrayClass;
 
         public ReferenceArrayElementGetter(Class<?> arrayClass) {
-            super(MethodType.methodType(arrayClass.getComponentType(),
-                    new Class<?>[]{arrayClass, int.class}));
+            super(
+                    MethodType.methodType(
+                            arrayClass.getComponentType(), new Class<?>[] {arrayClass, int.class}));
             this.arrayClass = arrayClass;
         }
 
@@ -263,15 +283,15 @@
         }
     }
 
-    /**
-     * Implementation of MethodHandles.arrayElementSetter for reference types.
-     */
+    /** Implementation of MethodHandles.arrayElementSetter for reference types. */
     public static class ReferenceArrayElementSetter extends Transformer {
         private final Class<?> arrayClass;
 
         public ReferenceArrayElementSetter(Class<?> arrayClass) {
-            super(MethodType.methodType(void.class,
-                    new Class<?>[] { arrayClass, int.class, arrayClass.getComponentType() }));
+            super(
+                    MethodType.methodType(
+                            void.class,
+                            new Class<?>[] {arrayClass, int.class, arrayClass.getComponentType()}));
             this.arrayClass = arrayClass;
         }
 
@@ -289,9 +309,7 @@
         }
     }
 
-    /**
-     * Implementation of MethodHandles.identity() for reference types.
-     */
+    /** Implementation of MethodHandles.identity() for reference types. */
     public static class ReferenceIdentity extends Transformer {
         private final Class<?> type;
 
@@ -312,9 +330,7 @@
         }
     }
 
-    /**
-     * Implementation of MethodHandles.constant.
-     */
+    /** Implementation of MethodHandles.constant. */
     public static class Constant extends Transformer {
         private final Class<?> type;
 
@@ -335,36 +351,38 @@
         public Constant(Class<?> type, Object value) {
             super(MethodType.methodType(type));
             this.type = type;
+            typeChar = Wrapper.basicTypeChar(type);
 
-            if (!type.isPrimitive()) {
-                asReference = value;
-                typeChar = 'L';
-            } else if (type == int.class) {
-                asInt = (int) value;
-                typeChar = 'I';
-            } else if (type == char.class) {
-                asInt = (int) (char) value;
-                typeChar = 'C';
-            } else if (type == short.class) {
-                asInt = (int) (short) value;
-                typeChar = 'S';
-            } else if (type == byte.class) {
-                asInt = (int) (byte) value;
-                typeChar = 'B';
-            } else if (type == boolean.class) {
-                asInt = ((boolean) value) ? 1 : 0;
-                typeChar = 'Z';
-            } else if (type == long.class) {
-                asLong = (long) value;
-                typeChar = 'J';
-            } else if (type == float.class) {
-                asFloat = (float) value;
-                typeChar = 'F';
-            } else if (type == double.class) {
-                asDouble = (double) value;
-                typeChar = 'D';
-            } else {
-                throw new AssertionError("unknown type: " + typeChar);
+            switch (typeChar) {
+                case 'L':
+                    asReference = value;
+                    break;
+                case 'I':
+                    asInt = (int) value;
+                    break;
+                case 'C':
+                    asInt = (int) (char) value;
+                    break;
+                case 'S':
+                    asInt = (int) (short) value;
+                    break;
+                case 'B':
+                    asInt = (int) (byte) value;
+                    break;
+                case 'Z':
+                    asInt = ((boolean) value) ? 1 : 0;
+                    break;
+                case 'J':
+                    asLong = (long) value;
+                    break;
+                case 'F':
+                    asFloat = (float) value;
+                    break;
+                case 'D':
+                    asDouble = (double) value;
+                    break;
+                default:
+                    throw new AssertionError("unknown type: " + typeChar);
             }
         }
 
@@ -375,15 +393,33 @@
             writer.makeReturnValueAccessor();
 
             switch (typeChar) {
-                case 'L' : { writer.putNextReference(asReference, type); break; }
-                case 'I' : { writer.putNextInt(asInt); break; }
-                case 'C' : { writer.putNextChar((char) asInt); break; }
-                case 'S' : { writer.putNextShort((short) asInt); break; }
-                case 'B' : { writer.putNextByte((byte) asInt); break; }
-                case 'Z' : { writer.putNextBoolean(asInt == 1); break; }
-                case 'J' : { writer.putNextLong(asLong); break; }
-                case 'F' : { writer.putNextFloat(asFloat); break; }
-                case 'D' : { writer.putNextDouble(asDouble); break; }
+                case 'L':
+                    writer.putNextReference(asReference, type);
+                    break;
+                case 'I':
+                    writer.putNextInt(asInt);
+                    break;
+                case 'C':
+                    writer.putNextChar((char) asInt);
+                    break;
+                case 'S':
+                    writer.putNextShort((short) asInt);
+                    break;
+                case 'B':
+                    writer.putNextByte((byte) asInt);
+                    break;
+                case 'Z':
+                    writer.putNextBoolean(asInt == 1);
+                    break;
+                case 'J':
+                    writer.putNextLong(asLong);
+                    break;
+                case 'F':
+                    writer.putNextFloat(asFloat);
+                    break;
+                case 'D':
+                    writer.putNextDouble(asDouble);
+                    break;
                 default:
                     throw new AssertionError("Unexpected typeChar: " + typeChar);
             }
@@ -432,7 +468,7 @@
                     EmulatedStackFrame.create(constructorHandle.type());
             constructorFrame.setReference(0, receiver);
             emulatedStackFrame.copyRangeTo(constructorFrame, callerRange, 1, 0);
-            constructorHandle.invoke(constructorFrame);
+            invokeExactFromTransform(constructorHandle, constructorFrame);
 
             // Set return result for caller.
             emulatedStackFrame.setReturnValueTo(receiver);
@@ -468,18 +504,16 @@
             // The first reference argument must be the receiver.
             stackFrame.setReference(0, receiver);
             // Copy all other arguments.
-            emulatedStackFrame.copyRangeTo(stackFrame, range,
-                    1 /* referencesStart */, 0 /* stackFrameStart */);
+            emulatedStackFrame.copyRangeTo(
+                    stackFrame, range, 1 /* referencesStart */, 0 /* stackFrameStart */);
 
             // Perform the invoke.
-            delegate.invoke(stackFrame);
+            invokeFromTransform(delegate, stackFrame);
             stackFrame.copyReturnValueTo(emulatedStackFrame);
         }
     }
 
-    /**
-     * Implements MethodHandle.filterReturnValue.
-     */
+    /** Implements MethodHandle.filterReturnValue. */
     public static class FilterReturnValue extends Transformer {
         private final MethodHandle target;
         private final MethodHandle filter;
@@ -502,7 +536,7 @@
             // the same parameter shapes.
             EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type());
             emulatedStackFrame.copyRangeTo(targetFrame, allArgs, 0, 0);
-            target.invoke(targetFrame);
+            invokeFromTransform(target, targetFrame);
 
             // Perform the invoke.
             final StackFrameReader returnValueReader = new StackFrameReader();
@@ -515,29 +549,43 @@
             filterWriter.attach(filterFrame);
 
             final Class<?> returnType = target.type().rtype();
-            if (!returnType.isPrimitive()) {
-                filterWriter.putNextReference(returnValueReader.nextReference(returnType),
-                        returnType);
-            } else if (returnType == boolean.class) {
-                filterWriter.putNextBoolean(returnValueReader.nextBoolean());
-            } else if (returnType == byte.class) {
-                filterWriter.putNextByte(returnValueReader.nextByte());
-            } else if (returnType == char.class) {
-                filterWriter.putNextChar(returnValueReader.nextChar());
-            } else if (returnType == short.class) {
-                filterWriter.putNextShort(returnValueReader.nextShort());
-            } else if (returnType == int.class) {
-                filterWriter.putNextInt(returnValueReader.nextInt());
-            } else if (returnType == long.class) {
-                filterWriter.putNextLong(returnValueReader.nextLong());
-            } else if (returnType == float.class) {
-                filterWriter.putNextFloat(returnValueReader.nextFloat());
-            } else if (returnType == double.class) {
-                filterWriter.putNextDouble(returnValueReader.nextDouble());
+            switch (Wrapper.basicTypeChar(returnType)) {
+                case 'L':
+                    filterWriter.putNextReference(
+                            returnValueReader.nextReference(returnType), returnType);
+                    break;
+                case 'Z':
+                    filterWriter.putNextBoolean(returnValueReader.nextBoolean());
+                    break;
+                case 'B':
+                    filterWriter.putNextByte(returnValueReader.nextByte());
+                    break;
+                case 'C':
+                    filterWriter.putNextChar(returnValueReader.nextChar());
+                    break;
+                case 'S':
+                    filterWriter.putNextShort(returnValueReader.nextShort());
+                    break;
+                case 'I':
+                    filterWriter.putNextInt(returnValueReader.nextInt());
+                    break;
+                case 'J':
+                    filterWriter.putNextLong(returnValueReader.nextLong());
+                    break;
+                case 'F':
+                    filterWriter.putNextFloat(returnValueReader.nextFloat());
+                    break;
+                case 'D':
+                    filterWriter.putNextDouble(returnValueReader.nextDouble());
+                    break;
+                case 'V':
+                    break;
+                default:
+                    throw new IllegalStateException("Unsupported type: " + returnType);
             }
 
             // Invoke the filter and copy its return value back to the original frame.
-            filter.invoke(filterFrame);
+            invokeFromTransform(filter, filterFrame);
             filterFrame.copyReturnValueTo(emulatedStackFrame);
         }
     }
@@ -571,26 +619,36 @@
             final Class<?>[] ptypes = type().ptypes();
             for (int i = 0; i < ptypes.length; ++i) {
                 final Class<?> ptype = ptypes[i];
-                if (!ptype.isPrimitive()) {
-                    arguments[i] = reader.nextReference(ptype);
-                } else if (ptype == boolean.class) {
-                    arguments[i] = reader.nextBoolean();
-                } else if (ptype == byte.class) {
-                    arguments[i] = reader.nextByte();
-                } else if (ptype == char.class) {
-                    arguments[i] = reader.nextChar();
-                } else if (ptype == short.class) {
-                    arguments[i] = reader.nextShort();
-                } else if (ptype == int.class) {
-                    arguments[i] = reader.nextInt();
-                } else if (ptype == long.class) {
-                    arguments[i] = reader.nextLong();
-                } else if (ptype == float.class) {
-                    arguments[i] = reader.nextFloat();
-                } else if (ptype == double.class) {
-                    arguments[i] = reader.nextDouble();
-                } else {
-                    throw new AssertionError("Unexpected type: " + ptype);
+                switch (Wrapper.basicTypeChar(ptype)) {
+                    case 'L':
+                        arguments[i] = reader.nextReference(ptype);
+                        break;
+                    case 'Z':
+                        arguments[i] = reader.nextBoolean();
+                        break;
+                    case 'B':
+                        arguments[i] = reader.nextByte();
+                        break;
+                    case 'C':
+                        arguments[i] = reader.nextChar();
+                        break;
+                    case 'S':
+                        arguments[i] = reader.nextShort();
+                        break;
+                    case 'I':
+                        arguments[i] = reader.nextInt();
+                        break;
+                    case 'J':
+                        arguments[i] = reader.nextLong();
+                        break;
+                    case 'F':
+                        arguments[i] = reader.nextFloat();
+                        break;
+                    case 'D':
+                        arguments[i] = reader.nextDouble();
+                        break;
+                    default:
+                        throw new AssertionError("Unexpected type: " + ptype);
                 }
             }
 
@@ -602,51 +660,62 @@
                 int idx = reorder[i];
                 final Class<?> ptype = ptypes[idx];
                 final Object argument = arguments[idx];
-
-                if (!ptype.isPrimitive()) {
-                    writer.putNextReference(argument, ptype);
-                } else if (ptype == boolean.class) {
-                    writer.putNextBoolean((boolean) argument);
-                } else if (ptype == byte.class) {
-                    writer.putNextByte((byte) argument);
-                } else if (ptype == char.class) {
-                    writer.putNextChar((char) argument);
-                } else if (ptype == short.class) {
-                    writer.putNextShort((short) argument);
-                } else if (ptype == int.class) {
-                    writer.putNextInt((int) argument);
-                } else if (ptype == long.class) {
-                    writer.putNextLong((long) argument);
-                } else if (ptype == float.class) {
-                    writer.putNextFloat((float) argument);
-                } else if (ptype == double.class) {
-                    writer.putNextDouble((double) argument);
-                } else {
-                    throw new AssertionError("Unexpected type: " + ptype);
+                switch (Wrapper.basicTypeChar(ptype)) {
+                    case 'L':
+                        writer.putNextReference(argument, ptype);
+                        break;
+                    case 'Z':
+                        writer.putNextBoolean((boolean) argument);
+                        break;
+                    case 'B':
+                        writer.putNextByte((byte) argument);
+                        break;
+                    case 'C':
+                        writer.putNextChar((char) argument);
+                        break;
+                    case 'S':
+                        writer.putNextShort((short) argument);
+                        break;
+                    case 'I':
+                        writer.putNextInt((int) argument);
+                        break;
+                    case 'J':
+                        writer.putNextLong((long) argument);
+                        break;
+                    case 'F':
+                        writer.putNextFloat((float) argument);
+                        break;
+                    case 'D':
+                        writer.putNextDouble((double) argument);
+                        break;
+                    default:
+                        throw new AssertionError("Unexpected type: " + ptype);
                 }
             }
 
-            target.invoke(calleeFrame);
+            invokeFromTransform(target, calleeFrame);
             calleeFrame.copyReturnValueTo(emulatedStackFrame);
         }
     }
 
     /**
-     * Converts methods with a trailing array argument to variable arity
-     * methods. So (A,B,C[])R can be invoked with any number of convertible
-     * arguments after B, e.g. (A,B)R or (A, B, C0)R or (A, B, C0...Cn)R.
+     * Makes a variable-arity adapter that groups trailing varargs arguments into an array.
      *
      * @hide
      */
     /*package*/ static class VarargsCollector extends Transformer {
         final MethodHandle target;
+        private final Class<?> arrayType;
 
         /*package*/ VarargsCollector(MethodHandle target) {
             super(target.type(), MethodHandle.INVOKE_CALLSITE_TRANSFORM);
-            if (!lastParameterTypeIsAnArray(target.type().ptypes())) {
+
+            Class<?>[] parameterTypes = target.type().ptypes();
+            if (!lastParameterTypeIsAnArray(parameterTypes)) {
                 throw new IllegalArgumentException("target does not have array as last parameter");
             }
             this.target = target;
+            this.arrayType = parameterTypes[parameterTypes.length - 1];
         }
 
         private static boolean lastParameterTypeIsAnArray(Class<?>[] parameterTypes) {
@@ -655,10 +724,52 @@
         }
 
         @Override
-        public boolean isVarargsCollector() { return true; }
+        public boolean isVarargsCollector() {
+            return true;
+        }
 
         @Override
-        public MethodHandle asFixedArity() { return target; }
+        public MethodHandle asFixedArity() {
+            return target;
+        }
+
+        @Override
+        MethodHandle asTypeUncached(MethodType newType) {
+            // asType() behavior is specialized per:
+            //
+            // https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/invoke/MethodHandle.html#asVarargsCollector(java.lang.Class)
+            //
+            // "The behavior of asType is also specialized for variable arity adapters, to maintain
+            //  the invariant that plain, inexact invoke is always equivalent to an asType call to
+            //  adjust the target type, followed by invokeExact. Therefore, a variable arity
+            //  adapter responds to an asType request by building a fixed arity collector, if and
+            //  only if the adapter and requested type differ either in arity or trailing argument
+            //  type. The resulting fixed arity collector has its type further adjusted
+            //  (if necessary) to the requested type by pairwise conversion, as if by another
+            //  application of asType."
+            final MethodType currentType = type();
+            final MethodHandle currentFixedArity = asFixedArity();
+            if (currentType.parameterCount() == newType.parameterCount()
+                    && currentType
+                            .lastParameterType()
+                            .isAssignableFrom(newType.lastParameterType())) {
+                return asTypeCache = currentFixedArity.asType(newType);
+            }
+
+            final int arrayLength = newType.parameterCount() - currentType.parameterCount() + 1;
+            if (arrayLength < 0) {
+                // arrayType is definitely array per VarargsCollector constructor.
+                throwWrongMethodTypeException(currentType, newType);
+            }
+
+            MethodHandle collector = null;
+            try {
+                collector = currentFixedArity.asCollector(arrayType, arrayLength).asType(newType);
+            } catch (IllegalArgumentException ex) {
+                throwWrongMethodTypeException(currentType, newType);
+            }
+            return asTypeCache = collector;
+        }
 
         @Override
         public void transform(EmulatedStackFrame callerFrame) throws Throwable {
@@ -667,12 +778,13 @@
             Class<?>[] targetPTypes = type().ptypes();
 
             int lastTargetIndex = targetPTypes.length - 1;
-            if (callerPTypes.length == targetPTypes.length &&
-                targetPTypes[lastTargetIndex].isAssignableFrom(callerPTypes[lastTargetIndex])) {
+            if (callerPTypes.length == targetPTypes.length
+                    && targetPTypes[lastTargetIndex].isAssignableFrom(
+                            callerPTypes[lastTargetIndex])) {
                 // Caller frame matches target frame in the arity array parameter. Invoke
                 // immediately, and let the invoke() dispatch perform any necessary conversions
                 // on the other parameters present.
-                target.invoke(callerFrame);
+                invokeFromTransform(target, callerFrame);
                 return;
             }
 
@@ -698,7 +810,7 @@
             prepareFrame(callerFrame, targetFrame);
 
             // Invoke target.
-            target.invoke(targetFrame);
+            invokeExactFromTransform(target, targetFrame);
 
             // Copy return value to the caller's frame.
             targetFrame.copyReturnValueTo(callerFrame);
@@ -708,11 +820,11 @@
             throw new WrongMethodTypeException("Cannot convert " + from + " to " + to);
         }
 
-        private static boolean arityArgumentsConvertible(Class<?>[] ptypes, int arityStart,
-                                                         Class<?> elementType) {
+        private static boolean arityArgumentsConvertible(
+                Class<?>[] ptypes, int arityStart, Class<?> elementType) {
             if (ptypes.length - 1 == arityStart) {
-                if (ptypes[arityStart].isArray() &&
-                    ptypes[arityStart].getComponentType() == elementType) {
+                if (ptypes[arityStart].isArray()
+                        && ptypes[arityStart].getComponentType() == elementType) {
                     // The last ptype is in the same position as the arity
                     // array and has the same type.
                     return true;
@@ -727,112 +839,162 @@
             return true;
         }
 
-        private static Object referenceArray(StackFrameReader reader, Class<?>[] ptypes,
-                                             Class<?> elementType, int offset, int length) {
+        private static Object referenceArray(
+                StackFrameReader reader,
+                Class<?>[] ptypes,
+                Class<?> elementType,
+                int offset,
+                int length) {
             Object arityArray = Array.newInstance(elementType, length);
             for (int i = 0; i < length; ++i) {
                 Class<?> argumentType = ptypes[i + offset];
                 Object o = null;
                 switch (Wrapper.basicTypeChar(argumentType)) {
-                    case 'L': { o = reader.nextReference(argumentType); break; }
-                    case 'I': { o = reader.nextInt(); break; }
-                    case 'J': { o = reader.nextLong(); break; }
-                    case 'B': { o = reader.nextByte(); break; }
-                    case 'S': { o = reader.nextShort(); break; }
-                    case 'C': { o = reader.nextChar(); break; }
-                    case 'Z': { o = reader.nextBoolean(); break; }
-                    case 'F': { o = reader.nextFloat(); break; }
-                    case 'D': { o = reader.nextDouble(); break; }
+                    case 'L':
+                        o = reader.nextReference(argumentType);
+                        break;
+                    case 'I':
+                        o = reader.nextInt();
+                        break;
+                    case 'J':
+                        o = reader.nextLong();
+                        break;
+                    case 'B':
+                        o = reader.nextByte();
+                        break;
+                    case 'S':
+                        o = reader.nextShort();
+                        break;
+                    case 'C':
+                        o = reader.nextChar();
+                        break;
+                    case 'Z':
+                        o = reader.nextBoolean();
+                        break;
+                    case 'F':
+                        o = reader.nextFloat();
+                        break;
+                    case 'D':
+                        o = reader.nextDouble();
+                        break;
                 }
                 Array.set(arityArray, i, elementType.cast(o));
             }
             return arityArray;
         }
 
-        private static Object intArray(StackFrameReader reader, Class<?> ptypes[],
-                                       int offset, int length) {
+        private static Object intArray(
+                StackFrameReader reader, Class<?> ptypes[], int offset, int length) {
             int[] arityArray = new int[length];
             for (int i = 0; i < length; ++i) {
                 Class<?> argumentType = ptypes[i + offset];
                 switch (Wrapper.basicTypeChar(argumentType)) {
-                    case 'I': { arityArray[i] = reader.nextInt(); break; }
-                    case 'S': { arityArray[i] = reader.nextShort(); break; }
-                    case 'B': { arityArray[i] = reader.nextByte(); break; }
-                    default: {
+                    case 'I':
+                        arityArray[i] = reader.nextInt();
+                        break;
+                    case 'S':
+                        arityArray[i] = reader.nextShort();
+                        break;
+                    case 'B':
+                        arityArray[i] = reader.nextByte();
+                        break;
+                    default:
                         arityArray[i] = (Integer) reader.nextReference(argumentType);
                         break;
-                    }
                 }
             }
             return arityArray;
         }
 
-        private static Object longArray(StackFrameReader reader, Class<?> ptypes[],
-                                        int offset, int length) {
+        private static Object longArray(
+                StackFrameReader reader, Class<?> ptypes[], int offset, int length) {
             long[] arityArray = new long[length];
             for (int i = 0; i < length; ++i) {
                 Class<?> argumentType = ptypes[i + offset];
                 switch (Wrapper.basicTypeChar(argumentType)) {
-                    case 'J': { arityArray[i] = reader.nextLong(); break; }
-                    case 'I': { arityArray[i] = reader.nextInt(); break; }
-                    case 'S': { arityArray[i] = reader.nextShort(); break; }
-                    case 'B': { arityArray[i] = reader.nextByte(); break; }
-                    default: { arityArray[i] = (Long) reader.nextReference(argumentType); break; }
+                    case 'J':
+                        arityArray[i] = reader.nextLong();
+                        break;
+                    case 'I':
+                        arityArray[i] = reader.nextInt();
+                        break;
+                    case 'S':
+                        arityArray[i] = reader.nextShort();
+                        break;
+                    case 'B':
+                        arityArray[i] = reader.nextByte();
+                        break;
+                    default:
+                        arityArray[i] = (Long) reader.nextReference(argumentType);
+                        break;
                 }
             }
             return arityArray;
         }
 
-        private static Object byteArray(StackFrameReader reader, Class<?> ptypes[],
-                                        int offset, int length) {
+        private static Object byteArray(
+                StackFrameReader reader, Class<?> ptypes[], int offset, int length) {
             byte[] arityArray = new byte[length];
             for (int i = 0; i < length; ++i) {
                 Class<?> argumentType = ptypes[i + offset];
                 switch (Wrapper.basicTypeChar(argumentType)) {
-                    case 'B': { arityArray[i] = reader.nextByte(); break; }
-                    default: { arityArray[i] = (Byte) reader.nextReference(argumentType); break; }
+                    case 'B':
+                        arityArray[i] = reader.nextByte();
+                        break;
+                    default:
+                        arityArray[i] = (Byte) reader.nextReference(argumentType);
+                        break;
                 }
             }
             return arityArray;
         }
 
-        private static Object shortArray(StackFrameReader reader, Class<?> ptypes[],
-                                        int offset, int length) {
+        private static Object shortArray(
+                StackFrameReader reader, Class<?> ptypes[], int offset, int length) {
             short[] arityArray = new short[length];
             for (int i = 0; i < length; ++i) {
                 Class<?> argumentType = ptypes[i + offset];
                 switch (Wrapper.basicTypeChar(argumentType)) {
-                    case 'S': { arityArray[i] = reader.nextShort(); break; }
-                    case 'B': { arityArray[i] = reader.nextByte(); break; }
-                    default: { arityArray[i] = (Short) reader.nextReference(argumentType); break; }
+                    case 'S':
+                        arityArray[i] = reader.nextShort();
+                        break;
+                    case 'B':
+                        arityArray[i] = reader.nextByte();
+                        break;
+                    default:
+                        arityArray[i] = (Short) reader.nextReference(argumentType);
+                        break;
                 }
             }
             return arityArray;
         }
 
-        private static Object charArray(StackFrameReader reader, Class<?> ptypes[],
-                                        int offset, int length) {
+        private static Object charArray(
+                StackFrameReader reader, Class<?> ptypes[], int offset, int length) {
             char[] arityArray = new char[length];
             for (int i = 0; i < length; ++i) {
                 Class<?> argumentType = ptypes[i + offset];
                 switch (Wrapper.basicTypeChar(argumentType)) {
-                    case 'C': { arityArray[i] = reader.nextChar(); break; }
-                    default: {
+                    case 'C':
+                        arityArray[i] = reader.nextChar();
+                        break;
+                    default:
                         arityArray[i] = (Character) reader.nextReference(argumentType);
                         break;
-                    }
                 }
             }
             return arityArray;
         }
 
-        private static Object booleanArray(StackFrameReader reader, Class<?> ptypes[],
-                                        int offset, int length) {
+        private static Object booleanArray(
+                StackFrameReader reader, Class<?> ptypes[], int offset, int length) {
             boolean[] arityArray = new boolean[length];
             for (int i = 0; i < length; ++i) {
                 Class<?> argumentType = ptypes[i + offset];
                 switch (Wrapper.basicTypeChar(argumentType)) {
-                    case 'Z': { arityArray[i] = reader.nextBoolean(); break; }
+                    case 'Z':
+                        arityArray[i] = reader.nextBoolean();
+                        break;
                     default:
                         arityArray[i] = (Boolean) reader.nextReference(argumentType);
                         break;
@@ -841,114 +1003,188 @@
             return arityArray;
         }
 
-        private static Object floatArray(StackFrameReader reader, Class<?> ptypes[],
-                                        int offset, int length) {
+        private static Object floatArray(
+                StackFrameReader reader, Class<?> ptypes[], int offset, int length) {
             float[] arityArray = new float[length];
             for (int i = 0; i < length; ++i) {
                 Class<?> argumentType = ptypes[i + offset];
                 switch (Wrapper.basicTypeChar(argumentType)) {
-                    case 'F': { arityArray[i] = reader.nextFloat(); break; }
-                    case 'J': { arityArray[i] = reader.nextLong(); break; }
-                    case 'I': { arityArray[i] = reader.nextInt(); break; }
-                    case 'S': { arityArray[i] = reader.nextShort(); break; }
-                    case 'B': { arityArray[i] = reader.nextByte(); break; }
-                    default: {
+                    case 'F':
+                        arityArray[i] = reader.nextFloat();
+                        break;
+                    case 'J':
+                        arityArray[i] = reader.nextLong();
+                        break;
+                    case 'I':
+                        arityArray[i] = reader.nextInt();
+                        break;
+                    case 'S':
+                        arityArray[i] = reader.nextShort();
+                        break;
+                    case 'B':
+                        arityArray[i] = reader.nextByte();
+                        break;
+                    default:
                         arityArray[i] = (Float) reader.nextReference(argumentType);
                         break;
-                    }
                 }
             }
             return arityArray;
         }
 
-        private static Object doubleArray(StackFrameReader reader, Class<?> ptypes[],
-                                        int offset, int length) {
+        private static Object doubleArray(
+                StackFrameReader reader, Class<?> ptypes[], int offset, int length) {
             double[] arityArray = new double[length];
             for (int i = 0; i < length; ++i) {
                 Class<?> argumentType = ptypes[i + offset];
                 switch (Wrapper.basicTypeChar(argumentType)) {
-                    case 'D': { arityArray[i] = reader.nextDouble(); break; }
-                    case 'F': { arityArray[i] = reader.nextFloat(); break; }
-                    case 'J': { arityArray[i] = reader.nextLong(); break; }
-                    case 'I': { arityArray[i] = reader.nextInt(); break; }
-                    case 'S': { arityArray[i] = reader.nextShort(); break; }
-                    case 'B': { arityArray[i] = reader.nextByte(); break; }
-                    default: {
+                    case 'D':
+                        arityArray[i] = reader.nextDouble();
+                        break;
+                    case 'F':
+                        arityArray[i] = reader.nextFloat();
+                        break;
+                    case 'J':
+                        arityArray[i] = reader.nextLong();
+                        break;
+                    case 'I':
+                        arityArray[i] = reader.nextInt();
+                        break;
+                    case 'S':
+                        arityArray[i] = reader.nextShort();
+                        break;
+                    case 'B':
+                        arityArray[i] = reader.nextByte();
+                        break;
+                    default:
                         arityArray[i] = (Double) reader.nextReference(argumentType);
                         break;
-                    }
                 }
             }
             return arityArray;
         }
 
-        private static Object makeArityArray(MethodType callerFrameType,
-                                             StackFrameReader callerFrameReader,
-                                             int indexOfArityArray,
-                                             Class<?> arityArrayType) {
+        private static Object makeArityArray(
+                MethodType callerFrameType,
+                StackFrameReader callerFrameReader,
+                int indexOfArityArray,
+                Class<?> arityArrayType) {
             int arityArrayLength = callerFrameType.ptypes().length - indexOfArityArray;
             Class<?> elementType = arityArrayType.getComponentType();
             Class<?>[] callerPTypes = callerFrameType.ptypes();
 
             char elementBasicType = Wrapper.basicTypeChar(elementType);
             switch (elementBasicType) {
-                case 'L': return referenceArray(callerFrameReader, callerPTypes, elementType,
-                                                indexOfArityArray, arityArrayLength);
-                case 'I': return intArray(callerFrameReader, callerPTypes,
-                                          indexOfArityArray, arityArrayLength);
-                case 'J': return longArray(callerFrameReader, callerPTypes,
-                                           indexOfArityArray, arityArrayLength);
-                case 'B': return byteArray(callerFrameReader, callerPTypes,
-                                           indexOfArityArray, arityArrayLength);
-                case 'S': return shortArray(callerFrameReader, callerPTypes,
-                                            indexOfArityArray, arityArrayLength);
-                case 'C': return charArray(callerFrameReader, callerPTypes,
-                                           indexOfArityArray, arityArrayLength);
-                case 'Z': return booleanArray(callerFrameReader, callerPTypes,
-                                              indexOfArityArray, arityArrayLength);
-                case 'F': return floatArray(callerFrameReader, callerPTypes,
-                                            indexOfArityArray, arityArrayLength);
-                case 'D': return doubleArray(callerFrameReader, callerPTypes,
-                                             indexOfArityArray, arityArrayLength);
+                case 'L':
+                    return referenceArray(
+                            callerFrameReader,
+                            callerPTypes,
+                            elementType,
+                            indexOfArityArray,
+                            arityArrayLength);
+                case 'I':
+                    return intArray(
+                            callerFrameReader, callerPTypes,
+                            indexOfArityArray, arityArrayLength);
+                case 'J':
+                    return longArray(
+                            callerFrameReader, callerPTypes,
+                            indexOfArityArray, arityArrayLength);
+                case 'B':
+                    return byteArray(
+                            callerFrameReader, callerPTypes,
+                            indexOfArityArray, arityArrayLength);
+                case 'S':
+                    return shortArray(
+                            callerFrameReader, callerPTypes,
+                            indexOfArityArray, arityArrayLength);
+                case 'C':
+                    return charArray(
+                            callerFrameReader, callerPTypes,
+                            indexOfArityArray, arityArrayLength);
+                case 'Z':
+                    return booleanArray(
+                            callerFrameReader, callerPTypes,
+                            indexOfArityArray, arityArrayLength);
+                case 'F':
+                    return floatArray(
+                            callerFrameReader, callerPTypes,
+                            indexOfArityArray, arityArrayLength);
+                case 'D':
+                    return doubleArray(
+                            callerFrameReader, callerPTypes,
+                            indexOfArityArray, arityArrayLength);
             }
             throw new InternalError("Unexpected type: " + elementType);
         }
 
-        public static Object collectArguments(char basicComponentType, Class<?> componentType,
-                                              StackFrameReader reader, Class<?>[] types,
-                                              int startIdx, int length) {
+        public static Object collectArguments(
+                char basicComponentType,
+                Class<?> componentType,
+                StackFrameReader reader,
+                Class<?>[] types,
+                int startIdx,
+                int length) {
             switch (basicComponentType) {
-                case 'L': return referenceArray(reader, types, componentType, startIdx, length);
-                case 'I': return intArray(reader, types, startIdx, length);
-                case 'J': return longArray(reader, types, startIdx, length);
-                case 'B': return byteArray(reader, types, startIdx, length);
-                case 'S': return shortArray(reader, types, startIdx, length);
-                case 'C': return charArray(reader, types, startIdx, length);
-                case 'Z': return booleanArray(reader, types, startIdx, length);
-                case 'F': return floatArray(reader, types, startIdx, length);
-                case 'D': return doubleArray(reader, types, startIdx, length);
+                case 'L':
+                    return referenceArray(reader, types, componentType, startIdx, length);
+                case 'I':
+                    return intArray(reader, types, startIdx, length);
+                case 'J':
+                    return longArray(reader, types, startIdx, length);
+                case 'B':
+                    return byteArray(reader, types, startIdx, length);
+                case 'S':
+                    return shortArray(reader, types, startIdx, length);
+                case 'C':
+                    return charArray(reader, types, startIdx, length);
+                case 'Z':
+                    return booleanArray(reader, types, startIdx, length);
+                case 'F':
+                    return floatArray(reader, types, startIdx, length);
+                case 'D':
+                    return doubleArray(reader, types, startIdx, length);
             }
             throw new InternalError("Unexpected type: " + basicComponentType);
         }
 
-        private static void copyParameter(StackFrameReader reader, StackFrameWriter writer,
-                                          Class<?> ptype) {
+        private static void copyParameter(
+                StackFrameReader reader, StackFrameWriter writer, Class<?> ptype) {
             switch (Wrapper.basicTypeChar(ptype)) {
-                case 'L': { writer.putNextReference(reader.nextReference(ptype), ptype); break; }
-                case 'I': { writer.putNextInt(reader.nextInt()); break; }
-                case 'J': { writer.putNextLong(reader.nextLong()); break; }
-                case 'B': { writer.putNextByte(reader.nextByte()); break; }
-                case 'S': { writer.putNextShort(reader.nextShort()); break; }
-                case 'C': { writer.putNextChar(reader.nextChar()); break; }
-                case 'Z': { writer.putNextBoolean(reader.nextBoolean()); break; }
-                case 'F': { writer.putNextFloat(reader.nextFloat()); break; }
-                case 'D': { writer.putNextDouble(reader.nextDouble()); break; }
-                default: throw new InternalError("Unexpected type: " + ptype);
+                case 'L':
+                    writer.putNextReference(reader.nextReference(ptype), ptype);
+                    break;
+                case 'I':
+                    writer.putNextInt(reader.nextInt());
+                    break;
+                case 'J':
+                    writer.putNextLong(reader.nextLong());
+                    break;
+                case 'B':
+                    writer.putNextByte(reader.nextByte());
+                    break;
+                case 'S':
+                    writer.putNextShort(reader.nextShort());
+                    break;
+                case 'C':
+                    writer.putNextChar(reader.nextChar());
+                    break;
+                case 'Z':
+                    writer.putNextBoolean(reader.nextBoolean());
+                    break;
+                case 'F':
+                    writer.putNextFloat(reader.nextFloat());
+                    break;
+                case 'D':
+                    writer.putNextDouble(reader.nextDouble());
+                    break;
+                default:
+                    throw new InternalError("Unexpected type: " + ptype);
             }
         }
 
-        private static void prepareFrame(EmulatedStackFrame callerFrame,
-                                         EmulatedStackFrame targetFrame) {
+        private static void prepareFrame(
+                EmulatedStackFrame callerFrame, EmulatedStackFrame targetFrame) {
             StackFrameWriter targetWriter = new StackFrameWriter();
             targetWriter.attach(targetFrame);
             StackFrameReader callerReader = new StackFrameReader();
@@ -964,22 +1200,25 @@
 
             // Add arity array as last parameter in |targetFrame|.
             Class<?> arityArrayType = targetMethodType.ptypes()[indexOfArityArray];
-            Object arityArray = makeArityArray(callerFrame.getMethodType(), callerReader,
-                                               indexOfArityArray, arityArrayType);
+            Object arityArray =
+                    makeArityArray(
+                            callerFrame.getMethodType(),
+                            callerReader,
+                            indexOfArityArray,
+                            arityArrayType);
             targetWriter.putNextReference(arityArray, arityArrayType);
         }
 
         /**
-         * Computes the frame type to invoke the target method handle with. This
-         * is the same as the caller frame type, but with the trailing argument
-         * being the array type that is the trailing argument in the target method
-         * handle.
+         * Computes the frame type to invoke the target method handle with. This is the same as the
+         * caller frame type, but with the trailing argument being the array type that is the
+         * trailing argument in the target method handle.
          *
-         * Suppose the targetType is (T0, T1, T2[])RT and the callerType is (C0, C1, C2, C3)RC
+         * <p>Suppose the targetType is (T0, T1, T2[])RT and the callerType is (C0, C1, C2, C3)RC
          * then the constructed type is (C0, C1, T2[])RC.
          */
-        private static MethodType makeTargetFrameType(MethodType callerType,
-                                                      MethodType targetType) {
+        private static MethodType makeTargetFrameType(
+                MethodType callerType, MethodType targetType) {
             final int ptypesLength = targetType.ptypes().length;
             final Class<?>[] ptypes = new Class<?>[ptypesLength];
             // Copy types from caller types to new methodType.
@@ -991,9 +1230,7 @@
         }
     }
 
-    /**
-     * Implements MethodHandles.invoker & MethodHandles.exactInvoker.
-     */
+    /** Implements MethodHandles.invoker & MethodHandles.exactInvoker. */
     static class Invoker extends Transformer {
         private final MethodType targetType;
         private final boolean isExactInvoker;
@@ -1012,10 +1249,10 @@
             // can't call invokeExact on the target inside the transformer.
             if (isExactInvoker) {
                 MethodType callsiteType =
-                    emulatedStackFrame.getCallsiteType().dropParameterTypes(0, 1);
+                        emulatedStackFrame.getCallsiteType().dropParameterTypes(0, 1);
                 if (!exactMatch(callsiteType, targetType)) {
-                    throw new WrongMethodTypeException("Wrong type, Expected: " + targetType
-                            + " was: " + callsiteType);
+                    throw new WrongMethodTypeException(
+                            "Wrong type, Expected: " + targetType + " was: " + callsiteType);
                 }
             }
 
@@ -1027,43 +1264,45 @@
             emulatedStackFrame.copyRangeTo(targetFrame, copyRange, 0, 0);
 
             // Finally, invoke the handle and copy the return value.
-            target.invoke(targetFrame);
+            invokeFromTransform(target, targetFrame);
             targetFrame.copyReturnValueTo(emulatedStackFrame);
         }
 
-         /**
-          * Checks whether two method types are compatible as an exact match. The exact match
-          * is based on the erased form (all reference types treated as Object).
-          * @param callsiteType the MethodType associated with the invocation.
-          * @param targetType the MethodType of the MethodHandle being invoked.
-          * @return true if {@code callsiteType} and {@code targetType} are an exact match.
-          */
-        static private boolean exactMatch(MethodType callsiteType, MethodType targetType) {
+        /**
+         * Checks whether two method types are compatible as an exact match. The exact match is
+         * based on the erased form (all reference types treated as Object).
+         *
+         * @param callsiteType the MethodType associated with the invocation.
+         * @param targetType the MethodType of the MethodHandle being invoked.
+         * @return true if {@code callsiteType} and {@code targetType} are an exact match.
+         */
+        private static boolean exactMatch(MethodType callsiteType, MethodType targetType) {
             final int parameterCount = callsiteType.parameterCount();
             if (callsiteType.parameterCount() != targetType.parameterCount()) {
                 return false;
             }
 
             for (int i = 0; i < parameterCount; ++i) {
-                Class argumentType  = callsiteType.parameterType(i);
+                Class argumentType = callsiteType.parameterType(i);
                 Class parameterType = targetType.parameterType(i);
                 if (!exactMatch(argumentType, parameterType)) {
                     return false;
                 }
             }
             // Check return type, noting it's always okay to discard the return value.
-            return callsiteType.returnType() == Void.TYPE ||
-                exactMatch(callsiteType.returnType(), targetType.returnType());
+            return callsiteType.returnType() == Void.TYPE
+                    || exactMatch(callsiteType.returnType(), targetType.returnType());
         }
 
         /**
-         * Checks whether two types are an exact match. The exact match is based on the erased
-         * types so any two reference types match, but primitive types must be the same.
+         * Checks whether two types are an exact match. The exact match is based on the erased types
+         * so any two reference types match, but primitive types must be the same.
+         *
          * @param lhs first class to compare.
          * @param rhs second class to compare.
          * @return true if both classes satisfy the exact match criteria.
          */
-        static private boolean exactMatch(Class lhs, Class rhs) {
+        private static boolean exactMatch(Class lhs, Class rhs) {
             if (lhs.isPrimitive() || rhs.isPrimitive()) {
                 return lhs == rhs;
             }
@@ -1071,33 +1310,29 @@
         }
     }
 
-    /**
-     * Implements MethodHandle.asSpreader / MethodHandles.spreadInvoker.
-     */
+    /** Implements MethodHandle.asSpreader / MethodHandles.spreadInvoker. */
     static class Spreader extends Transformer {
         /** The method handle we're delegating to. */
         private final MethodHandle target;
 
         /**
-         * The offset of the trailing array argument in the list of arguments to
-         * this transformer. The array argument is always the last argument.
+         * The offset of the trailing array argument in the list of arguments to this transformer.
+         * The array argument is always the last argument.
          */
         private final int arrayOffset;
 
-        /**
-         * The type char of the component type of the array.
-         */
+        /** The type char of the component type of the array. */
         private final char arrayTypeChar;
 
         /**
-         * The number of input arguments that will be present in the array. In other words,
-         * this is the expected array length.
+         * The number of input arguments that will be present in the array. In other words, this is
+         * the expected array length.
          */
         private final int numArrayArgs;
 
         /**
-         * Range of arguments to copy verbatim from the input frame, This will cover all
-         * arguments that aren't a part of the trailing array.
+         * 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;
 
@@ -1131,17 +1366,16 @@
             // 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);
+            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]);
+            Object arrayObj =
+                    callerFrame.getReference(
+                            copyRange.numReferences, this.type().ptypes()[arrayOffset]);
             final int arrayLength = Array.getLength(arrayObj);
             if (arrayLength != numArrayArgs) {
-                throw new IllegalArgumentException("Invalid array length: " + arrayLength);
+                throw new IllegalArgumentException(
+                        "Invalid array length " + arrayLength + " expected " + numArrayArgs);
             }
 
             final MethodType type = target.type();
@@ -1173,207 +1407,306 @@
                 case 'D':
                     spreadArray((double[]) arrayObj, writer, type, numArrayArgs, arrayOffset);
                     break;
-
             }
 
-            target.invoke(targetFrame);
+            invokeFromTransform(target, targetFrame);
             targetFrame.copyReturnValueTo(callerFrame);
         }
 
-        public static void spreadArray(Object[] array, StackFrameWriter writer, MethodType type,
-                                       int numArgs, int offset) {
+        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; }
+                    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) {
+        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(); }
+                    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) {
+        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(); }
+                    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) {
+        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(); }
+                    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) {
+        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(); }
+                    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) {
+        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(); }
+                    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) {
+        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(); }
+                    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) {
+        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(); }
+                    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) {
+        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(); }
+                    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.
-     */
+    /** Implements MethodHandle.asCollector. */
     static class Collector extends Transformer {
         private final MethodHandle target;
 
         /**
-         * The offset of the trailing array argument in the list of arguments to
-         * this transformer. The array argument is always the last argument.
+         * The offset of the trailing array argument in the list of arguments to this transformer.
+         * The array argument is always the last argument.
          */
         private final int arrayOffset;
 
         /**
-         * The number of input arguments that will be present in the array. In other words,
-         * this is the expected array length.
+         * The number of input arguments that will be present in the array. In other words, this is
+         * the expected array length.
          */
         private final int numArrayArgs;
 
-        /**
-         * The type char of the component type of the array.
-         */
+        /** The component type of the array. */
+        private final Class arrayType;
+
+        /** The type char of the component type of the array. */
         private final char arrayTypeChar;
 
         /**
-         * Range of arguments to copy verbatim from the input frame, This will cover all
-         * arguments that aren't a part of the trailing array.
+         * 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;
 
-        Collector(MethodHandle delegate, Class<?> arrayType, int length) {
+        Collector(MethodHandle delegate, final Class<?> arrayType, final int length) {
             super(delegate.type().asCollectorType(arrayType, length));
 
+            final Class<?> componentType = arrayType.getComponentType();
+            if (componentType == null) {
+                throw new IllegalArgumentException("arrayType is not an array type");
+            }
+
             target = delegate;
             // Copy all arguments except the last argument (which is the trailing array argument
             // that needs to be spread).
             arrayOffset = delegate.type().parameterCount() - 1;
-            arrayTypeChar = Wrapper.basicTypeChar(arrayType.getComponentType());
+            this.arrayType = arrayType;
+            arrayTypeChar = Wrapper.basicTypeChar(componentType);
             numArrayArgs = length;
 
             // Copy all args except for the last argument.
@@ -1396,89 +1729,98 @@
             reader.attach(callerFrame, arrayOffset, copyRange.numReferences, copyRange.numBytes);
 
             switch (arrayTypeChar) {
-                case 'L': {
-                    // Reference arrays are the only case where the component type of the
-                    // array we construct might differ from the type of the reference we read
-                    // from the stack frame.
-                    final Class<?> targetType = target.type().ptypes()[arrayOffset];
-                    final Class<?> targetComponentType = targetType.getComponentType();
-                    final Class<?> adapterComponentType = type().lastParameterType();
+                case 'L':
+                    {
+                        // Reference arrays are the only case where the component type of the
+                        // array we construct might differ from the type of the reference we read
+                        // from the stack frame.
+                        final Class<?> targetType = target.type().ptypes()[arrayOffset];
+                        final Class<?> adapterComponentType = arrayType.getComponentType();
 
-                    Object[] arr = (Object[]) Array.newInstance(targetComponentType, numArrayArgs);
-                    for (int i = 0; i < numArrayArgs; ++i) {
-                        arr[i] = reader.nextReference(adapterComponentType);
-                    }
+                        Object[] arr =
+                                (Object[]) Array.newInstance(adapterComponentType, numArrayArgs);
+                        for (int i = 0; i < numArrayArgs; ++i) {
+                            arr[i] = reader.nextReference(adapterComponentType);
+                        }
 
-                    writer.putNextReference(arr, targetType);
-                    break;
-                }
-                case 'I': {
-                    int[] array = new int[numArrayArgs];
-                    for (int i = 0; i < numArrayArgs; ++i) {
-                        array[i] = reader.nextInt();
+                        writer.putNextReference(arr, targetType);
+                        break;
                     }
-                    writer.putNextReference(array, int[].class);
-                    break;
-                }
-                case 'J': {
-                    long[] array = new long[numArrayArgs];
-                    for (int i = 0; i < numArrayArgs; ++i) {
-                        array[i] = reader.nextLong();
+                case 'I':
+                    {
+                        int[] array = new int[numArrayArgs];
+                        for (int i = 0; i < numArrayArgs; ++i) {
+                            array[i] = reader.nextInt();
+                        }
+                        writer.putNextReference(array, int[].class);
+                        break;
                     }
-                    writer.putNextReference(array, long[].class);
-                    break;
-                }
-                case 'B': {
-                    byte[] array = new byte[numArrayArgs];
-                    for (int i = 0; i < numArrayArgs; ++i) {
-                        array[i] = reader.nextByte();
+                case 'J':
+                    {
+                        long[] array = new long[numArrayArgs];
+                        for (int i = 0; i < numArrayArgs; ++i) {
+                            array[i] = reader.nextLong();
+                        }
+                        writer.putNextReference(array, long[].class);
+                        break;
                     }
-                    writer.putNextReference(array, byte[].class);
-                    break;
-                }
-                case 'S': {
-                    short[] array = new short[numArrayArgs];
-                    for (int i = 0; i < numArrayArgs; ++i) {
-                        array[i] = reader.nextShort();
+                case 'B':
+                    {
+                        byte[] array = new byte[numArrayArgs];
+                        for (int i = 0; i < numArrayArgs; ++i) {
+                            array[i] = reader.nextByte();
+                        }
+                        writer.putNextReference(array, byte[].class);
+                        break;
                     }
-                    writer.putNextReference(array, short[].class);
-                    break;
-                }
-                case 'C': {
-                    char[] array = new char[numArrayArgs];
-                    for (int i = 0; i < numArrayArgs; ++i) {
-                        array[i] = reader.nextChar();
+                case 'S':
+                    {
+                        short[] array = new short[numArrayArgs];
+                        for (int i = 0; i < numArrayArgs; ++i) {
+                            array[i] = reader.nextShort();
+                        }
+                        writer.putNextReference(array, short[].class);
+                        break;
                     }
-                    writer.putNextReference(array, char[].class);
-                    break;
-                }
-                case 'Z': {
-                    boolean[] array = new boolean[numArrayArgs];
-                    for (int i = 0; i < numArrayArgs; ++i) {
-                        array[i] = reader.nextBoolean();
+                case 'C':
+                    {
+                        char[] array = new char[numArrayArgs];
+                        for (int i = 0; i < numArrayArgs; ++i) {
+                            array[i] = reader.nextChar();
+                        }
+                        writer.putNextReference(array, char[].class);
+                        break;
                     }
-                    writer.putNextReference(array, boolean[].class);
-                    break;
-                }
-                case 'F': {
-                    float[] array = new float[numArrayArgs];
-                    for (int i = 0; i < numArrayArgs; ++i) {
-                        array[i] = reader.nextFloat();
+                case 'Z':
+                    {
+                        boolean[] array = new boolean[numArrayArgs];
+                        for (int i = 0; i < numArrayArgs; ++i) {
+                            array[i] = reader.nextBoolean();
+                        }
+                        writer.putNextReference(array, boolean[].class);
+                        break;
                     }
-                    writer.putNextReference(array, float[].class);
-                    break;
-                }
-                case 'D': {
-                    double[] array = new double[numArrayArgs];
-                    for (int i = 0; i < numArrayArgs; ++i) {
-                        array[i] = reader.nextDouble();
+                case 'F':
+                    {
+                        float[] array = new float[numArrayArgs];
+                        for (int i = 0; i < numArrayArgs; ++i) {
+                            array[i] = reader.nextFloat();
+                        }
+                        writer.putNextReference(array, float[].class);
+                        break;
                     }
-                    writer.putNextReference(array, double[].class);
-                    break;
-                }
+                case 'D':
+                    {
+                        double[] array = new double[numArrayArgs];
+                        for (int i = 0; i < numArrayArgs; ++i) {
+                            array[i] = reader.nextDouble();
+                        }
+                        writer.putNextReference(array, double[].class);
+                        break;
+                    }
             }
 
-            target.invoke(targetFrame);
+            invokeFromTransform(target, targetFrame);
             targetFrame.copyReturnValueTo(callerFrame);
         }
     }
@@ -1500,7 +1842,6 @@
             this.target = target;
             this.pos = pos;
             this.filters = filters;
-
         }
 
         private static MethodType deriveType(MethodHandle target, int pos, MethodHandle[] filters) {
@@ -1545,7 +1886,7 @@
                     filterWriter.attach(filterFrame);
                     copyNext(reader, filterWriter, filter.type().ptypes()[0]);
 
-                    filter.invoke(filterFrame);
+                    invokeFromTransform(filter, filterFrame);
 
                     // Copy the argument back from the filter frame to the stack frame.
                     final StackFrameReader filterReader = new StackFrameReader();
@@ -1559,14 +1900,12 @@
                 }
             }
 
-            target.invoke(transformedFrame);
+            invokeFromTransform(target, transformedFrame);
             transformedFrame.copyReturnValueTo(stackFrame);
         }
     }
 
-    /**
-     * Implements MethodHandles.collectArguments.
-     */
+    /** Implements MethodHandles.collectArguments. */
     static class CollectArguments extends Transformer {
         private final MethodHandle target;
         private final MethodHandle collector;
@@ -1576,8 +1915,8 @@
         private final Range collectorRange;
 
         /**
-         * The first range of arguments we copy to the target. These are arguments
-         * in the range [0, pos). Note that arg[pos] is the return value of the filter.
+         * The first range of arguments we copy to the target. These are arguments in the range [0,
+         * pos). Note that arg[pos] is the return value of the filter.
          */
         private final Range range1;
 
@@ -1590,8 +1929,8 @@
         private final int referencesOffset;
         private final int stackFrameOffset;
 
-        CollectArguments(MethodHandle target, MethodHandle collector, int pos,
-                         MethodType adapterType) {
+        CollectArguments(
+                MethodHandle target, MethodHandle collector, int pos, MethodType adapterType) {
             super(adapterType);
 
             this.target = target;
@@ -1629,7 +1968,7 @@
             // First invoke the collector.
             EmulatedStackFrame filterFrame = EmulatedStackFrame.create(collector.type());
             stackFrame.copyRangeTo(filterFrame, collectorRange, 0, 0);
-            collector.invoke(filterFrame);
+            invokeFromTransform(collector, filterFrame);
 
             // Start constructing the target frame.
             EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type());
@@ -1645,19 +1984,19 @@
             }
 
             if (range2 != null) {
-                stackFrame.copyRangeTo(targetFrame, range2,
+                stackFrame.copyRangeTo(
+                        targetFrame,
+                        range2,
                         range1.numReferences + referencesOffset,
                         range2.numBytes + stackFrameOffset);
             }
 
-            target.invoke(targetFrame);
+            invokeFromTransform(target, targetFrame);
             targetFrame.copyReturnValueTo(stackFrame);
         }
     }
 
-    /**
-     * Implements MethodHandles.foldArguments.
-     */
+    /** Implements MethodHandles.foldArguments. */
     static class FoldArguments extends Transformer {
         private final MethodHandle target;
         private final MethodHandle combiner;
@@ -1695,7 +2034,7 @@
             // First construct the combiner frame and invoke it.
             EmulatedStackFrame combinerFrame = EmulatedStackFrame.create(combiner.type());
             stackFrame.copyRangeTo(combinerFrame, combinerArgs, 0, 0);
-            combiner.invoke(combinerFrame);
+            invokeFromTransform(combiner, combinerFrame);
 
             // Create the stack frame for the target.
             EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type());
@@ -1710,7 +2049,7 @@
             }
 
             stackFrame.copyRangeTo(targetFrame, targetArgs, referencesOffset, stackFrameOffset);
-            target.invoke(targetFrame);
+            invokeFromTransform(target, targetFrame);
 
             targetFrame.copyReturnValueTo(stackFrame);
         }
@@ -1724,9 +2063,7 @@
         }
     }
 
-    /**
-     * Implements MethodHandles.insertArguments.
-     */
+    /** Implements MethodHandles.insertArguments. */
     static class InsertArguments extends Transformer {
         private final MethodHandle target;
         private final int pos;
@@ -1764,48 +2101,445 @@
             final Class<?>[] ptypes = target.type().ptypes();
             for (int i = 0; i < values.length; ++i) {
                 final Class<?> ptype = ptypes[i + pos];
-                if (ptype.isPrimitive()) {
-                    if (ptype == boolean.class) {
-                        writer.putNextBoolean((boolean) values[i]);
-                    } else if (ptype == byte.class) {
-                        writer.putNextByte((byte) values[i]);
-                    } else if (ptype == char.class) {
-                        writer.putNextChar((char) values[i]);
-                    } else if (ptype == short.class) {
-                        writer.putNextShort((short) values[i]);
-                    } else if (ptype == int.class) {
-                        writer.putNextInt((int) values[i]);
-                    } else if (ptype == long.class) {
-                        writer.putNextLong((long) values[i]);
-                    } else if (ptype == float.class) {
-                        writer.putNextFloat((float) values[i]);
-                    } else if (ptype == double.class) {
-                        writer.putNextDouble((double) values[i]);
-                    }
-
-                    bytesCopied += EmulatedStackFrame.getSize(ptype);
-                } else {
+                final char typeChar = Wrapper.basicTypeChar(ptype);
+                if (typeChar == 'L') {
                     writer.putNextReference(values[i], ptype);
                     referencesCopied++;
+                } else {
+                    switch (typeChar) {
+                        case 'Z':
+                            writer.putNextBoolean((boolean) values[i]);
+                            break;
+                        case 'B':
+                            writer.putNextByte((byte) values[i]);
+                            break;
+                        case 'C':
+                            writer.putNextChar((char) values[i]);
+                            break;
+                        case 'S':
+                            writer.putNextShort((short) values[i]);
+                            break;
+                        case 'I':
+                            writer.putNextInt((int) values[i]);
+                            break;
+                        case 'J':
+                            writer.putNextLong((long) values[i]);
+                            break;
+                        case 'F':
+                            writer.putNextFloat((float) values[i]);
+                            break;
+                        case 'D':
+                            writer.putNextDouble((double) values[i]);
+                            break;
+                    }
+                    bytesCopied += EmulatedStackFrame.getSize(ptype);
                 }
             }
 
             // Copy all remaining arguments.
             if (range2 != null) {
-                stackFrame.copyRangeTo(calleeFrame, range2,
+                stackFrame.copyRangeTo(
+                        calleeFrame,
+                        range2,
                         range1.numReferences + referencesCopied,
                         range1.numBytes + bytesCopied);
             }
 
-            target.invoke(calleeFrame);
+            invokeFromTransform(target, calleeFrame);
             calleeFrame.copyReturnValueTo(stackFrame);
         }
     }
 
+    public static class AsTypeAdapter extends Transformer {
+        private final MethodHandle target;
 
-    /**
-     * Implements {@link java.lang.invokeMethodHandles#explicitCastArguments()}.
-     */
+        public AsTypeAdapter(MethodHandle target, MethodType type) {
+            super(type);
+            this.target = target;
+        }
+
+        @Override
+        public void transform(EmulatedStackFrame callerFrame) throws Throwable {
+            final EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type());
+            final StackFrameReader reader = new StackFrameReader();
+            final StackFrameWriter writer = new StackFrameWriter();
+
+            // Adapt arguments
+            reader.attach(callerFrame);
+            writer.attach(targetFrame);
+            adaptArguments(reader, writer);
+
+            // Invoke target
+            invokeFromTransform(target, targetFrame);
+
+            if (callerFrame.getMethodType().rtype() != void.class) {
+                // Adapt return value
+                reader.attach(targetFrame).makeReturnValueAccessor();
+                writer.attach(callerFrame).makeReturnValueAccessor();
+                adaptReturnValue(reader, writer);
+            }
+        }
+
+        private void adaptArguments(final StackFrameReader reader, final StackFrameWriter writer) {
+            final Class<?>[] fromTypes = type().ptypes();
+            final Class<?>[] toTypes = target.type().ptypes();
+            for (int i = 0; i < fromTypes.length; ++i) {
+                adaptArgument(reader, fromTypes[i], writer, toTypes[i]);
+            }
+        }
+
+        private void adaptReturnValue(
+                final StackFrameReader reader, final StackFrameWriter writer) {
+            final Class<?> fromType = target.type().rtype();
+            final Class<?> toType = type().rtype();
+            adaptArgument(reader, fromType, writer, toType);
+        }
+
+        private void throwWrongMethodTypeException() throws WrongMethodTypeException {
+            throw new WrongMethodTypeException(
+                    "Cannot convert from " + type() + " to " + target.type());
+        }
+
+        private static void throwClassCastException(Class from, Class to)
+                throws ClassCastException {
+            throw new ClassCastException("Cannot cast from " + from + " to " + to);
+        }
+
+        private void writePrimitiveByteAs(final StackFrameWriter writer, char baseType, byte value)
+                throws WrongMethodTypeException {
+            switch (baseType) {
+                case 'B':
+                    writer.putNextByte(value);
+                    return;
+                case 'S':
+                    writer.putNextShort((short) value);
+                    return;
+                case 'I':
+                    writer.putNextInt((int) value);
+                    return;
+                case 'J':
+                    writer.putNextLong((long) value);
+                    return;
+                case 'F':
+                    writer.putNextFloat((float) value);
+                    return;
+                case 'D':
+                    writer.putNextDouble((double) value);
+                    return;
+                default:
+                    throwWrongMethodTypeException();
+            }
+        }
+
+        private void writePrimitiveShortAs(
+                final StackFrameWriter writer, char baseType, short value)
+                throws WrongMethodTypeException {
+            switch (baseType) {
+                case 'S':
+                    writer.putNextShort(value);
+                    return;
+                case 'I':
+                    writer.putNextInt((int) value);
+                    return;
+                case 'J':
+                    writer.putNextLong((long) value);
+                    return;
+                case 'F':
+                    writer.putNextFloat((float) value);
+                    return;
+                case 'D':
+                    writer.putNextDouble((double) value);
+                    return;
+                default:
+                    throwWrongMethodTypeException();
+            }
+        }
+
+        private void writePrimitiveCharAs(final StackFrameWriter writer, char baseType, char value)
+                throws WrongMethodTypeException {
+            switch (baseType) {
+                case 'C':
+                    writer.putNextChar(value);
+                    return;
+                case 'I':
+                    writer.putNextInt((int) value);
+                    return;
+                case 'J':
+                    writer.putNextLong((long) value);
+                    return;
+                case 'F':
+                    writer.putNextFloat((float) value);
+                    return;
+                case 'D':
+                    writer.putNextDouble((double) value);
+                    return;
+                default:
+                    throwWrongMethodTypeException();
+            }
+        }
+
+        private void writePrimitiveIntAs(final StackFrameWriter writer, char baseType, int value)
+                throws WrongMethodTypeException {
+            switch (baseType) {
+                case 'I':
+                    writer.putNextInt(value);
+                    return;
+                case 'J':
+                    writer.putNextLong((long) value);
+                    return;
+                case 'F':
+                    writer.putNextFloat((float) value);
+                    return;
+                case 'D':
+                    writer.putNextDouble((double) value);
+                    return;
+                default:
+                    throwWrongMethodTypeException();
+            }
+            throwWrongMethodTypeException();
+        }
+
+        private void writePrimitiveLongAs(final StackFrameWriter writer, char baseType, long value)
+                throws WrongMethodTypeException {
+            switch (baseType) {
+                case 'J':
+                    writer.putNextLong(value);
+                    return;
+                case 'F':
+                    writer.putNextFloat((float) value);
+                    return;
+                case 'D':
+                    writer.putNextDouble((double) value);
+                    return;
+                default:
+                    throwWrongMethodTypeException();
+            }
+        }
+
+        private void writePrimitiveFloatAs(
+                final StackFrameWriter writer, char baseType, float value)
+                throws WrongMethodTypeException {
+            switch (baseType) {
+                case 'F':
+                    writer.putNextFloat(value);
+                    return;
+                case 'D':
+                    writer.putNextDouble((double) value);
+                    return;
+                default:
+                    throwWrongMethodTypeException();
+            }
+        }
+
+        private void writePrimitiveDoubleAs(
+                final StackFrameWriter writer, char baseType, double value)
+                throws WrongMethodTypeException {
+            switch (baseType) {
+                case 'D':
+                    writer.putNextDouble(value);
+                    return;
+                default:
+                    throwWrongMethodTypeException();
+            }
+        }
+
+        private void writePrimitiveVoidAs(final StackFrameWriter writer, char baseType) {
+            switch (baseType) {
+                case 'Z':
+                    writer.putNextBoolean(false);
+                    return;
+                case 'B':
+                    writer.putNextByte((byte) 0);
+                    return;
+                case 'S':
+                    writer.putNextShort((short) 0);
+                    return;
+                case 'C':
+                    writer.putNextChar((char) 0);
+                    return;
+                case 'I':
+                    writer.putNextInt(0);
+                    return;
+                case 'J':
+                    writer.putNextLong(0L);
+                    return;
+                case 'F':
+                    writer.putNextFloat(0.0f);
+                    return;
+                case 'D':
+                    writer.putNextDouble(0.0);
+                    return;
+                default:
+                    throwWrongMethodTypeException();
+            }
+        }
+
+        private static Class getBoxedPrimitiveClass(char baseType) {
+            switch (baseType) {
+                case 'Z':
+                    return Boolean.class;
+                case 'B':
+                    return Byte.class;
+                case 'S':
+                    return Short.class;
+                case 'C':
+                    return Character.class;
+                case 'I':
+                    return Integer.class;
+                case 'J':
+                    return Long.class;
+                case 'F':
+                    return Float.class;
+                case 'D':
+                    return Double.class;
+                default:
+                    return null;
+            }
+        }
+
+        private void adaptArgument(
+                final StackFrameReader reader,
+                final Class<?> from,
+                final StackFrameWriter writer,
+                final Class<?> to) {
+            if (from.equals(to)) {
+                StackFrameAccessor.copyNext(reader, writer, from);
+                return;
+            }
+
+            if (to.isPrimitive()) {
+                if (from.isPrimitive()) {
+                    final char fromBaseType = Wrapper.basicTypeChar(from);
+                    final char toBaseType = Wrapper.basicTypeChar(to);
+                    switch (fromBaseType) {
+                        case 'B':
+                            writePrimitiveByteAs(writer, toBaseType, reader.nextByte());
+                            return;
+                        case 'S':
+                            writePrimitiveShortAs(writer, toBaseType, reader.nextShort());
+                            return;
+                        case 'C':
+                            writePrimitiveCharAs(writer, toBaseType, reader.nextChar());
+                            return;
+                        case 'I':
+                            writePrimitiveIntAs(writer, toBaseType, reader.nextInt());
+                            return;
+                        case 'J':
+                            writePrimitiveLongAs(writer, toBaseType, reader.nextLong());
+                            return;
+                        case 'F':
+                            writePrimitiveFloatAs(writer, toBaseType, reader.nextFloat());
+                            return;
+                        case 'V':
+                            writePrimitiveVoidAs(writer, toBaseType);
+                            return;
+                        default:
+                            throwWrongMethodTypeException();
+                    }
+                } else {
+                    final Object value = reader.nextReference(Object.class);
+                    if (to == void.class) {
+                        return;
+                    }
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    if (!Wrapper.isWrapperType(value.getClass())) {
+                        throwClassCastException(value.getClass(), to);
+                    }
+                    final Wrapper fromWrapper = Wrapper.forWrapperType(value.getClass());
+                    final Wrapper toWrapper = Wrapper.forPrimitiveType(to);
+                    if (!toWrapper.isConvertibleFrom(fromWrapper)) {
+                        throwClassCastException(from, to);
+                    }
+
+                    final char toChar = toWrapper.basicTypeChar();
+                    switch (fromWrapper.basicTypeChar()) {
+                        case 'Z':
+                            writer.putNextBoolean(((Boolean) value).booleanValue());
+                            return;
+                        case 'B':
+                            writePrimitiveByteAs(writer, toChar, ((Byte) value).byteValue());
+                            return;
+                        case 'S':
+                            writePrimitiveShortAs(writer, toChar, ((Short) value).shortValue());
+                            return;
+                        case 'C':
+                            writePrimitiveCharAs(writer, toChar, ((Character) value).charValue());
+                            return;
+                        case 'I':
+                            writePrimitiveIntAs(writer, toChar, ((Integer) value).intValue());
+                            return;
+                        case 'J':
+                            writePrimitiveLongAs(writer, toChar, ((Long) value).longValue());
+                            return;
+                        case 'F':
+                            writePrimitiveFloatAs(writer, toChar, ((Float) value).floatValue());
+                            return;
+                        case 'D':
+                            writePrimitiveDoubleAs(writer, toChar, ((Double) value).doubleValue());
+                            return;
+                        default:
+                            throw new IllegalStateException();
+                    }
+                }
+            } else {
+                if (from.isPrimitive()) {
+                    // Boxing conversion
+                    final char fromBaseType = Wrapper.basicTypeChar(from);
+                    final Class fromBoxed = getBoxedPrimitiveClass(fromBaseType);
+                    // 'to' maybe a super class of the boxed `from` type, e.g. Number.
+                    if (fromBoxed != null && !to.isAssignableFrom(fromBoxed)) {
+                        throwWrongMethodTypeException();
+                    }
+
+                    Object boxed;
+                    switch (fromBaseType) {
+                        case 'Z':
+                            boxed = Boolean.valueOf(reader.nextBoolean());
+                            break;
+                        case 'B':
+                            boxed = Byte.valueOf(reader.nextByte());
+                            break;
+                        case 'S':
+                            boxed = Short.valueOf(reader.nextShort());
+                            break;
+                        case 'C':
+                            boxed = Character.valueOf(reader.nextChar());
+                            break;
+                        case 'I':
+                            boxed = Integer.valueOf(reader.nextInt());
+                            break;
+                        case 'J':
+                            boxed = Long.valueOf(reader.nextLong());
+                            break;
+                        case 'F':
+                            boxed = Float.valueOf(reader.nextFloat());
+                            break;
+                        case 'D':
+                            boxed = Double.valueOf(reader.nextDouble());
+                            break;
+                        case 'V':
+                            boxed = null;
+                            break;
+                        default:
+                            throw new IllegalStateException();
+                    }
+                    writer.putNextReference(boxed, to);
+                    return;
+                } else {
+                    // Cast
+                    Object value = reader.nextReference(Object.class);
+                    if (value != null && !to.isAssignableFrom(value.getClass())) {
+                        throwClassCastException(value.getClass(), to);
+                    }
+                    writer.putNextReference(value, to);
+                }
+            }
+        }
+    }
+
+    /** Implements {@link java.lang.invokeMethodHandles#explicitCastArguments()}. */
     public static class ExplicitCastArguments extends Transformer {
         private final MethodHandle target;
 
@@ -1820,12 +2554,12 @@
             EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type());
 
             explicitCastArguments(callerFrame, targetFrame);
-            target.invoke(targetFrame);
+            invokeFromTransform(target, targetFrame);
             explicitCastReturnValue(callerFrame, targetFrame);
         }
 
-        private void explicitCastArguments(final EmulatedStackFrame callerFrame,
-                                           final EmulatedStackFrame targetFrame) {
+        private void explicitCastArguments(
+                final EmulatedStackFrame callerFrame, final EmulatedStackFrame targetFrame) {
             final StackFrameReader reader = new StackFrameReader();
             reader.attach(callerFrame);
             final StackFrameWriter writer = new StackFrameWriter();
@@ -1838,8 +2572,8 @@
             }
         }
 
-        private void explicitCastReturnValue(final EmulatedStackFrame callerFrame,
-                                             final EmulatedStackFrame targetFrame) {
+        private void explicitCastReturnValue(
+                final EmulatedStackFrame callerFrame, final EmulatedStackFrame targetFrame) {
             Class<?> from = target.type().rtype();
             Class<?> to = type().rtype();
             if (to != void.class) {
@@ -1871,413 +2605,532 @@
         }
 
         /**
-         * Converts byte value to boolean according to
-         * {@link java.lang.invoke.MethodHandles#explicitCast()}
+         * Converts byte value to boolean according to {@link
+         * java.lang.invoke.MethodHandles#explicitCast()}
          */
         private static boolean toBoolean(byte value) {
             return (value & 1) == 1;
         }
 
-        private static byte readPrimitiveAsByte(final StackFrameReader reader,
-                                                final Class<?> from) {
-            if (from == byte.class) {
-                return (byte) reader.nextByte();
-            } else if (from == char.class) {
-                return (byte) reader.nextChar();
-            } else if (from == short.class) {
-                return (byte) reader.nextShort();
-            } else if (from == int.class) {
-                return (byte) reader.nextInt();
-            } else if (from == long.class) {
-                return (byte) reader.nextLong();
-            } else if (from == float.class) {
-                return (byte) reader.nextFloat();
-            } else if (from == double.class) {
-                return (byte) reader.nextDouble();
-            } else if (from == boolean.class) {
-                return reader.nextBoolean() ? (byte) 1 : (byte) 0;
-            } else {
-                throwUnexpectedType(from);
-                return 0;
+        private static byte readPrimitiveAsByte(
+                final StackFrameReader reader, final Class<?> from) {
+            switch (Wrapper.basicTypeChar(from)) {
+                case 'B':
+                    return (byte) reader.nextByte();
+                case 'C':
+                    return (byte) reader.nextChar();
+                case 'S':
+                    return (byte) reader.nextShort();
+                case 'I':
+                    return (byte) reader.nextInt();
+                case 'J':
+                    return (byte) reader.nextLong();
+                case 'F':
+                    return (byte) reader.nextFloat();
+                case 'D':
+                    return (byte) reader.nextDouble();
+                case 'Z':
+                    return reader.nextBoolean() ? (byte) 1 : (byte) 0;
+                default:
+                    throwUnexpectedType(from);
+                    return 0;
             }
         }
 
-        private static char readPrimitiveAsChar(final StackFrameReader reader,
-                                                final Class<?> from) {
-            if (from == byte.class) {
-                return (char) reader.nextByte();
-            } else if (from == char.class) {
-                return (char) reader.nextChar();
-            } else if (from == short.class) {
-                return (char) reader.nextShort();
-            } else if (from == int.class) {
-                return (char) reader.nextInt();
-            } else if (from == long.class) {
-                return (char) reader.nextLong();
-            } else if (from == float.class) {
-                return (char) reader.nextFloat();
-            } else if (from == double.class) {
-                return (char) reader.nextDouble();
-            } else if (from == boolean.class) {
-                return reader.nextBoolean() ? (char) 1 : (char) 0;
-            } else {
-                throwUnexpectedType(from);
-                return 0;
+        private static char readPrimitiveAsChar(
+                final StackFrameReader reader, final Class<?> from) {
+            switch (Wrapper.basicTypeChar(from)) {
+                case 'B':
+                    return (char) reader.nextByte();
+                case 'C':
+                    return (char) reader.nextChar();
+                case 'S':
+                    return (char) reader.nextShort();
+                case 'I':
+                    return (char) reader.nextInt();
+                case 'J':
+                    return (char) reader.nextLong();
+                case 'F':
+                    return (char) reader.nextFloat();
+                case 'D':
+                    return (char) reader.nextDouble();
+                case 'Z':
+                    return reader.nextBoolean() ? (char) 1 : (char) 0;
+                default:
+                    throwUnexpectedType(from);
+                    return 0;
             }
         }
 
-        private static short readPrimitiveAsShort(final StackFrameReader reader,
-                                                  final Class<?> from) {
-            if (from == byte.class) {
-                return (short) reader.nextByte();
-            } else if (from == char.class) {
-                return (short) reader.nextChar();
-            } else if (from == short.class) {
-                return (short) reader.nextShort();
-            } else if (from == int.class) {
-                return (short) reader.nextInt();
-            } else if (from == long.class) {
-                return (short) reader.nextLong();
-            } else if (from == float.class) {
-                return (short) reader.nextFloat();
-            } else if (from == double.class) {
-                return (short) reader.nextDouble();
-            } else if (from == boolean.class) {
-                return reader.nextBoolean() ? (short) 1 : (short) 0;
-            } else {
-                throwUnexpectedType(from);
-                return 0;
+        private static short readPrimitiveAsShort(
+                final StackFrameReader reader, final Class<?> from) {
+            switch (Wrapper.basicTypeChar(from)) {
+                case 'B':
+                    return (short) reader.nextByte();
+                case 'C':
+                    return (short) reader.nextChar();
+                case 'S':
+                    return (short) reader.nextShort();
+                case 'I':
+                    return (short) reader.nextInt();
+                case 'J':
+                    return (short) reader.nextLong();
+                case 'F':
+                    return (short) reader.nextFloat();
+                case 'D':
+                    return (short) reader.nextDouble();
+                case 'Z':
+                    return reader.nextBoolean() ? (short) 1 : (short) 0;
+                default:
+                    throwUnexpectedType(from);
+                    return 0;
             }
         }
 
-        private static int readPrimitiveAsInt(final StackFrameReader reader,
-                                              final Class<?> from) {
-            if (from == byte.class) {
-                return (int) reader.nextByte();
-            } else if (from == char.class) {
-                return (int) reader.nextChar();
-            } else if (from == short.class) {
-                return (int) reader.nextShort();
-            } else if (from == int.class) {
-                return (int) reader.nextInt();
-            } else if (from == long.class) {
-                return (int) reader.nextLong();
-            } else if (from == float.class) {
-                return (int) reader.nextFloat();
-            } else if (from == double.class) {
-                return (int) reader.nextDouble();
-            } else if (from == boolean.class) {
-                return reader.nextBoolean() ? 1 : 0;
-            } else {
-                throwUnexpectedType(from);
-                return 0;
+        private static int readPrimitiveAsInt(final StackFrameReader reader, final Class<?> from) {
+            switch (Wrapper.basicTypeChar(from)) {
+                case 'B':
+                    return (int) reader.nextByte();
+                case 'C':
+                    return (int) reader.nextChar();
+                case 'S':
+                    return (int) reader.nextShort();
+                case 'I':
+                    return (int) reader.nextInt();
+                case 'J':
+                    return (int) reader.nextLong();
+                case 'F':
+                    return (int) reader.nextFloat();
+                case 'D':
+                    return (int) reader.nextDouble();
+                case 'Z':
+                    return reader.nextBoolean() ? 1 : 0;
+                default:
+                    throwUnexpectedType(from);
+                    return 0;
             }
         }
 
-        private static long readPrimitiveAsLong(final StackFrameReader reader,
-                                                final Class<?> from) {
-            if (from == byte.class) {
-                return (long) reader.nextByte();
-            } else if (from == char.class) {
-                return (long) reader.nextChar();
-            } else if (from == short.class) {
-                return (long) reader.nextShort();
-            } else if (from == int.class) {
-                return (long) reader.nextInt();
-            } else if (from == long.class) {
-                return (long) reader.nextLong();
-            } else if (from == float.class) {
-                return (long) reader.nextFloat();
-            } else if (from == double.class) {
-                return (long) reader.nextDouble();
-            } else if (from == boolean.class) {
-                return reader.nextBoolean() ? 1L : 0L;
-            } else {
-                throwUnexpectedType(from);
-                return 0;
+        private static long readPrimitiveAsLong(
+                final StackFrameReader reader, final Class<?> from) {
+            switch (Wrapper.basicTypeChar(from)) {
+                case 'B':
+                    return (long) reader.nextByte();
+                case 'C':
+                    return (long) reader.nextChar();
+                case 'S':
+                    return (long) reader.nextShort();
+                case 'I':
+                    return (long) reader.nextInt();
+                case 'J':
+                    return (long) reader.nextLong();
+                case 'F':
+                    return (long) reader.nextFloat();
+                case 'D':
+                    return (long) reader.nextDouble();
+                case 'Z':
+                    return reader.nextBoolean() ? 1L : 0L;
+                default:
+                    throwUnexpectedType(from);
+                    return 0;
             }
         }
 
-        private static float readPrimitiveAsFloat(final StackFrameReader reader,
-                                                  final Class<?> from) {
-            if (from == byte.class) {
-                return (float) reader.nextByte();
-            } else if (from == char.class) {
-                return (float) reader.nextChar();
-            } else if (from == short.class) {
-                return (float) reader.nextShort();
-            } else if (from == int.class) {
-                return (float) reader.nextInt();
-            } else if (from == long.class) {
-                return (float) reader.nextLong();
-            } else if (from == float.class) {
-                return (float) reader.nextFloat();
-            } else if (from == double.class) {
-                return (float) reader.nextDouble();
-            } else if (from == boolean.class) {
-                return reader.nextBoolean() ? 1.0f : 0.0f;
-            } else {
-                throwUnexpectedType(from);
-                return 0;
+        private static float readPrimitiveAsFloat(
+                final StackFrameReader reader, final Class<?> from) {
+            switch (Wrapper.basicTypeChar(from)) {
+                case 'B':
+                    return (float) reader.nextByte();
+                case 'C':
+                    return (float) reader.nextChar();
+                case 'S':
+                    return (float) reader.nextShort();
+                case 'I':
+                    return (float) reader.nextInt();
+                case 'J':
+                    return (float) reader.nextLong();
+                case 'F':
+                    return (float) reader.nextFloat();
+                case 'D':
+                    return (float) reader.nextDouble();
+                case 'Z':
+                    return reader.nextBoolean() ? 1.0f : 0.0f;
+                default:
+                    throwUnexpectedType(from);
+                    return 0;
             }
         }
 
-        private static double readPrimitiveAsDouble(final StackFrameReader reader,
-                                                    final Class<?> from) {
-            if (from == byte.class) {
-                return (double) reader.nextByte();
-            } else if (from == char.class) {
-                return (double) reader.nextChar();
-            } else if (from == short.class) {
-                return (double) reader.nextShort();
-            } else if (from == int.class) {
-                return (double) reader.nextInt();
-            } else if (from == long.class) {
-                return (double) reader.nextLong();
-            } else if (from == float.class) {
-                return (double) reader.nextFloat();
-            } else if (from == double.class) {
-                return (double) reader.nextDouble();
-            } else if (from == boolean.class) {
-                return reader.nextBoolean() ? 1.0 : 0.0;
-            } else {
-                throwUnexpectedType(from);
-                return 0;
+        private static double readPrimitiveAsDouble(
+                final StackFrameReader reader, final Class<?> from) {
+            switch (Wrapper.basicTypeChar(from)) {
+                case 'B':
+                    return (double) reader.nextByte();
+                case 'C':
+                    return (double) reader.nextChar();
+                case 'S':
+                    return (double) reader.nextShort();
+                case 'I':
+                    return (double) reader.nextInt();
+                case 'J':
+                    return (double) reader.nextLong();
+                case 'F':
+                    return (double) reader.nextFloat();
+                case 'D':
+                    return (double) reader.nextDouble();
+                case 'Z':
+                    return reader.nextBoolean() ? 1.0 : 0.0;
+                default:
+                    throwUnexpectedType(from);
+                    return 0;
             }
         }
 
-        private static void explicitCastPrimitives(final StackFrameReader reader,
-                                                   final Class<?> from,
-                                                   final StackFrameWriter writer,
-                                                   final Class<?> to) {
-            if (to == byte.class) {
-                byte value = readPrimitiveAsByte(reader, from);
-                writer.putNextByte(value);
-            } else if (to == char.class) {
-                char value = readPrimitiveAsChar(reader, from);
-                writer.putNextChar(value);
-            } else if (to == short.class) {
-                short value = readPrimitiveAsShort(reader, from);
-                writer.putNextShort(value);
-            } else if (to == int.class) {
-                int value = readPrimitiveAsInt(reader, from);
-                writer.putNextInt(value);
-            } else if (to == long.class) {
-                long value = readPrimitiveAsLong(reader, from);
-                writer.putNextLong(value);
-            } else if (to == float.class) {
-                float value = readPrimitiveAsFloat(reader, from);
-                writer.putNextFloat(value);
-            } else if (to == double.class) {
-                double value = readPrimitiveAsDouble(reader, from);
-                writer.putNextDouble(value);
-            } else if (to == boolean.class) {
-                byte byteValue = readPrimitiveAsByte(reader, from);
-                writer.putNextBoolean(toBoolean(byteValue));
-            } else {
-                throwUnexpectedType(to);
+        private static void explicitCastPrimitives(
+                final StackFrameReader reader,
+                final Class<?> from,
+                final StackFrameWriter writer,
+                final Class<?> to) {
+            switch (Wrapper.basicTypeChar(to)) {
+                case 'B':
+                    writer.putNextByte(readPrimitiveAsByte(reader, from));
+                    break;
+                case 'C':
+                    writer.putNextChar(readPrimitiveAsChar(reader, from));
+                    break;
+                case 'S':
+                    writer.putNextShort(readPrimitiveAsShort(reader, from));
+                    break;
+                case 'I':
+                    writer.putNextInt(readPrimitiveAsInt(reader, from));
+                    break;
+                case 'J':
+                    writer.putNextLong(readPrimitiveAsLong(reader, from));
+                    break;
+                case 'F':
+                    writer.putNextFloat(readPrimitiveAsFloat(reader, from));
+                    break;
+                case 'D':
+                    writer.putNextDouble(readPrimitiveAsDouble(reader, from));
+                    break;
+                case 'Z':
+                    writer.putNextBoolean(toBoolean(readPrimitiveAsByte(reader, from)));
+                    break;
+                default:
+                    throwUnexpectedType(to);
+                    break;
             }
         }
 
         private static void unboxNull(final StackFrameWriter writer, final Class<?> to) {
-            if (to == boolean.class) {
-                writer.putNextBoolean(false);
-            } else if (to == byte.class) {
-                writer.putNextByte((byte) 0);
-            } else if (to == char.class) {
-                writer.putNextChar((char) 0);
-            } else if (to == short.class) {
-                writer.putNextShort((short) 0);
-            } else if (to == int.class) {
-                writer.putNextInt((int) 0);
-            } else if (to == long.class) {
-                writer.putNextLong((long) 0);
-            } else if (to == float.class) {
-                writer.putNextFloat((float) 0);
-            } else if (to == double.class) {
-                writer.putNextDouble((double) 0);
-            } else {
-                throwUnexpectedType(to);
+            switch (Wrapper.basicTypeChar(to)) {
+                case 'Z':
+                    writer.putNextBoolean(false);
+                    break;
+                case 'B':
+                    writer.putNextByte((byte) 0);
+                    break;
+                case 'C':
+                    writer.putNextChar((char) 0);
+                    break;
+                case 'S':
+                    writer.putNextShort((short) 0);
+                    break;
+                case 'I':
+                    writer.putNextInt((int) 0);
+                    break;
+                case 'J':
+                    writer.putNextLong((long) 0);
+                    break;
+                case 'F':
+                    writer.putNextFloat((float) 0);
+                    break;
+                case 'D':
+                    writer.putNextDouble((double) 0);
+                    break;
+                default:
+                    throwUnexpectedType(to);
+                    break;
             }
         }
 
-        private static void unboxNonNull(final Object ref, final Class<?> from,
-                                         final StackFrameWriter writer, final Class<?> to) {
-            if (from == Boolean.class) {
-                boolean z = (boolean) ref;
-                if (to == boolean.class) {
-                    writer.putNextBoolean(z);
-                } else if (to == byte.class) {
-                    writer.putNextByte(z ? (byte) 1 : (byte) 0);
-                } else if (to == short.class) {
-                    writer.putNextShort(z ? (short) 1 : (short) 0);
-                } else if (to == char.class) {
-                    writer.putNextChar(z ? (char) 1 : (char) 0);
-                } else if (to == int.class) {
-                    writer.putNextInt(z ? 1 : 0);
-                } else if (to == long.class) {
-                    writer.putNextLong(z ? 1l : 0l);
-                } else if (to == float.class) {
-                    writer.putNextFloat(z ? 1.0f : 0.0f);
-                } else if (to == double.class) {
-                    writer.putNextDouble(z ? 1.0 : 0.0);
-                } else {
-                    badCast(from, to);
-                }
-            } else if (from == Byte.class) {
-                byte b = (byte) ref;
-                if (to == byte.class) {
-                    writer.putNextByte(b);
-                } else if (to == boolean.class) {
-                    writer.putNextBoolean(toBoolean(b));
-                } else if (to == short.class) {
-                    writer.putNextShort((short) b);
-                } else if (to == char.class) {
-                    writer.putNextChar((char) b);
-                } else if (to == int.class) {
-                    writer.putNextInt((int) b);
-                } else if (to == long.class) {
-                    writer.putNextLong((long) b);
-                } else if (to == float.class) {
-                    writer.putNextFloat((float) b);
-                } else if (to == double.class) {
-                    writer.putNextDouble((double) b);
-                } else {
-                    badCast(from, to);
-                }
-            } else if (from == Short.class) {
-                short s = (short) ref;
-                if (to == boolean.class) {
-                    writer.putNextBoolean((s & 1) == 1);
-                } else if (to == byte.class) {
-                    writer.putNextByte((byte) s);
-                } else if (to == short.class) {
-                    writer.putNextShort(s);
-                } else if (to == char.class) {
-                    writer.putNextChar((char) s);
-                } else if (to == int.class) {
-                    writer.putNextInt((int) s);
-                } else if (to == long.class) {
-                    writer.putNextLong((long) s);
-                } else if (to == float.class) {
-                    writer.putNextFloat((float) s);
-                } else if (to == double.class) {
-                    writer.putNextDouble((double) s);
-                } else {
-                    badCast(from, to);
-                }
-            } else if (from == Character.class) {
-                char c = (char) ref;
-                if (to == boolean.class) {
-                    writer.putNextBoolean((c & (char) 1) == (char) 1);
-                } else if (to == byte.class) {
-                    writer.putNextByte((byte) c);
-                } else if (to == short.class) {
-                    writer.putNextShort((short) c);
-                } else if (to == char.class) {
-                    writer.putNextChar(c);
-                } else if (to == int.class) {
-                    writer.putNextInt((int) c);
-                } else if (to == long.class) {
-                    writer.putNextLong((long) c);
-                } else if (to == float.class) {
-                    writer.putNextFloat((float) c);
-                } else if (to == double.class) {
-                    writer.putNextDouble((double) c);
-                } else {
-                    badCast(from, to);
-                }
-            } else if (from == Integer.class) {
-                int i = (int) ref;
-                if (to == boolean.class) {
-                    writer.putNextBoolean((i & 1) == 1);
-                } else if (to == byte.class) {
-                    writer.putNextByte((byte) i);
-                } else if (to == short.class) {
-                    writer.putNextShort((short) i);
-                } else if (to == char.class) {
-                    writer.putNextChar((char) i);
-                } else if (to == int.class) {
-                    writer.putNextInt(i);
-                } else if (to == long.class) {
-                    writer.putNextLong((long) i);
-                } else if (to == float.class) {
-                    writer.putNextFloat((float) i);
-                } else if (to == double.class) {
-                    writer.putNextDouble((double) i);
-                } else {
-                    badCast(from, to);
-                }
-            } else if (from == Long.class) {
-                long j = (long) ref;
-                if (to == boolean.class) {
-                    writer.putNextBoolean((j & 1l) == 1l);
-                } else if (to == byte.class) {
-                    writer.putNextByte((byte) j);
-                } else if (to == short.class) {
-                    writer.putNextShort((short) j);
-                } else if (to == char.class) {
-                    writer.putNextChar((char) j);
-                } else if (to == int.class) {
-                    writer.putNextInt((int) j);
-                } else if (to == long.class) {
-                    writer.putNextLong(j);
-                } else if (to == float.class) {
-                    writer.putNextFloat((float) j);
-                } else if (to == double.class) {
-                    writer.putNextDouble((double) j);
-                } else {
-                    badCast(from, to);
-                }
-            } else if (from == Float.class) {
-                float f = (float) ref;
-                if (to == boolean.class) {
-                    writer.putNextBoolean(((byte) f & 1) != 0);
-                } else if (to == byte.class) {
-                    writer.putNextByte((byte) f);
-                } else if (to == short.class) {
-                    writer.putNextShort((short) f);
-                } else if (to == char.class) {
-                    writer.putNextChar((char) f);
-                } else if (to == int.class) {
-                    writer.putNextInt((int) f);
-                } else if (to == long.class) {
-                    writer.putNextLong((long) f);
-                } else if (to == float.class) {
-                    writer.putNextFloat(f);
-                } else if (to == double.class) {
-                    writer.putNextDouble((double) f);
-                } else {
-                    badCast(from, to);
-                }
-            } else if (from == Double.class) {
-                double d = (double) ref;
-                if (to == boolean.class) {
-                    writer.putNextBoolean(((byte) d & 1) != 0);
-                } else if (to == byte.class) {
-                    writer.putNextByte((byte) d);
-                } else if (to == short.class) {
-                    writer.putNextShort((short) d);
-                } else if (to == char.class) {
-                    writer.putNextChar((char) d);
-                } else if (to == int.class) {
-                    writer.putNextInt((int) d);
-                } else if (to == long.class) {
-                    writer.putNextLong((long) d);
-                } else if (to == float.class) {
-                    writer.putNextFloat((float) d);
-                } else if (to == double.class) {
-                    writer.putNextDouble(d);
-                } else {
-                    badCast(from, to);
-                }
-            } else {
+        private static void unboxNonNull(
+                final Object ref,
+                final Class<?> from,
+                final StackFrameWriter writer,
+                final Class<?> to) {
+            final Class<?> unboxedFromType = Wrapper.asPrimitiveType(from);
+            if (unboxedFromType == from) {
                 badCast(from, to);
+                return;
+            }
+            switch (Wrapper.basicTypeChar(unboxedFromType)) {
+                case 'Z':
+                    boolean z = (boolean) ref;
+                    switch (Wrapper.basicTypeChar(to)) {
+                        case 'Z':
+                            writer.putNextBoolean(z);
+                            break;
+                        case 'B':
+                            writer.putNextByte(z ? (byte) 1 : (byte) 0);
+                            break;
+                        case 'S':
+                            writer.putNextShort(z ? (short) 1 : (short) 0);
+                            break;
+                        case 'C':
+                            writer.putNextChar(z ? (char) 1 : (char) 0);
+                            break;
+                        case 'I':
+                            writer.putNextInt(z ? 1 : 0);
+                            break;
+                        case 'J':
+                            writer.putNextLong(z ? 1l : 0l);
+                            break;
+                        case 'F':
+                            writer.putNextFloat(z ? 1.0f : 0.0f);
+                            break;
+                        case 'D':
+                            writer.putNextDouble(z ? 1.0 : 0.0);
+                            break;
+                        default:
+                            badCast(from, to);
+                            break;
+                    }
+                    break;
+                case 'B':
+                    byte b = (byte) ref;
+                    switch (Wrapper.basicTypeChar(to)) {
+                        case 'B':
+                            writer.putNextByte(b);
+                            break;
+                        case 'Z':
+                            writer.putNextBoolean(toBoolean(b));
+                            break;
+                        case 'S':
+                            writer.putNextShort((short) b);
+                            break;
+                        case 'C':
+                            writer.putNextChar((char) b);
+                            break;
+                        case 'I':
+                            writer.putNextInt((int) b);
+                            break;
+                        case 'J':
+                            writer.putNextLong((long) b);
+                            break;
+                        case 'F':
+                            writer.putNextFloat((float) b);
+                            break;
+                        case 'D':
+                            writer.putNextDouble((double) b);
+                            break;
+                        default:
+                            badCast(from, to);
+                            break;
+                    }
+                    break;
+                case 'S':
+                    short s = (short) ref;
+                    switch (Wrapper.basicTypeChar(to)) {
+                        case 'Z':
+                            writer.putNextBoolean((s & 1) == 1);
+                            break;
+                        case 'B':
+                            writer.putNextByte((byte) s);
+                            break;
+                        case 'S':
+                            writer.putNextShort(s);
+                            break;
+                        case 'C':
+                            writer.putNextChar((char) s);
+                            break;
+                        case 'I':
+                            writer.putNextInt((int) s);
+                            break;
+                        case 'J':
+                            writer.putNextLong((long) s);
+                            break;
+                        case 'F':
+                            writer.putNextFloat((float) s);
+                            break;
+                        case 'D':
+                            writer.putNextDouble((double) s);
+                            break;
+                        default:
+                            badCast(from, to);
+                            break;
+                    }
+                    break;
+                case 'C':
+                    char c = (char) ref;
+                    switch (Wrapper.basicTypeChar(to)) {
+                        case 'Z':
+                            writer.putNextBoolean((c & (char) 1) == (char) 1);
+                            break;
+                        case 'B':
+                            writer.putNextByte((byte) c);
+                            break;
+                        case 'S':
+                            writer.putNextShort((short) c);
+                            break;
+                        case 'C':
+                            writer.putNextChar(c);
+                            break;
+                        case 'I':
+                            writer.putNextInt((int) c);
+                            break;
+                        case 'J':
+                            writer.putNextLong((long) c);
+                            break;
+                        case 'F':
+                            writer.putNextFloat((float) c);
+                            break;
+                        case 'D':
+                            writer.putNextDouble((double) c);
+                            break;
+                        default:
+                            badCast(from, to);
+                            break;
+                    }
+                    break;
+                case 'I':
+                    int i = (int) ref;
+                    switch (Wrapper.basicTypeChar(to)) {
+                        case 'Z':
+                            writer.putNextBoolean((i & 1) == 1);
+                            break;
+                        case 'B':
+                            writer.putNextByte((byte) i);
+                            break;
+                        case 'S':
+                            writer.putNextShort((short) i);
+                            break;
+                        case 'C':
+                            writer.putNextChar((char) i);
+                            break;
+                        case 'I':
+                            writer.putNextInt(i);
+                            break;
+                        case 'J':
+                            writer.putNextLong((long) i);
+                            break;
+                        case 'F':
+                            writer.putNextFloat((float) i);
+                            break;
+                        case 'D':
+                            writer.putNextDouble((double) i);
+                            break;
+                        default:
+                            badCast(from, to);
+                    }
+                    break;
+                case 'J':
+                    long j = (long) ref;
+                    switch (Wrapper.basicTypeChar(to)) {
+                        case 'Z':
+                            writer.putNextBoolean((j & 1l) == 1l);
+                            break;
+                        case 'B':
+                            writer.putNextByte((byte) j);
+                            break;
+                        case 'S':
+                            writer.putNextShort((short) j);
+                            break;
+                        case 'C':
+                            writer.putNextChar((char) j);
+                            break;
+                        case 'I':
+                            writer.putNextInt((int) j);
+                            break;
+                        case 'J':
+                            writer.putNextLong(j);
+                            break;
+                        case 'F':
+                            writer.putNextFloat((float) j);
+                            break;
+                        case 'D':
+                            writer.putNextDouble((double) j);
+                            break;
+                        default:
+                            badCast(from, to);
+                            break;
+                    }
+                    break;
+                case 'F':
+                    float f = (float) ref;
+                    switch (Wrapper.basicTypeChar(to)) {
+                        case 'Z':
+                            writer.putNextBoolean(((byte) f & 1) != 0);
+                            break;
+                        case 'B':
+                            writer.putNextByte((byte) f);
+                            break;
+                        case 'S':
+                            writer.putNextShort((short) f);
+                            break;
+                        case 'C':
+                            writer.putNextChar((char) f);
+                            break;
+                        case 'I':
+                            writer.putNextInt((int) f);
+                            break;
+                        case 'J':
+                            writer.putNextLong((long) f);
+                            break;
+                        case 'F':
+                            writer.putNextFloat(f);
+                            break;
+                        case 'D':
+                            writer.putNextDouble((double) f);
+                            break;
+                        default:
+                            badCast(from, to);
+                            break;
+                    }
+                    break;
+                case 'D':
+                    double d = (double) ref;
+                    switch (Wrapper.basicTypeChar(to)) {
+                        case 'Z':
+                            writer.putNextBoolean(((byte) d & 1) != 0);
+                            break;
+                        case 'B':
+                            writer.putNextByte((byte) d);
+                            break;
+                        case 'S':
+                            writer.putNextShort((short) d);
+                            break;
+                        case 'C':
+                            writer.putNextChar((char) d);
+                            break;
+                        case 'I':
+                            writer.putNextInt((int) d);
+                            break;
+                        case 'J':
+                            writer.putNextLong((long) d);
+                            break;
+                        case 'F':
+                            writer.putNextFloat((float) d);
+                            break;
+                        case 'D':
+                            writer.putNextDouble(d);
+                            break;
+                        default:
+                            badCast(from, to);
+                            break;
+                    }
+                    break;
+                default:
+                    badCast(from, to);
+                    break;
             }
         }
 
-        private static void unbox(final Object ref, final Class<?> from,
-                                  final StackFrameWriter writer, final Class<?> to) {
+        private static void unbox(
+                final Object ref,
+                final Class<?> from,
+                final StackFrameWriter writer,
+                final Class<?> to) {
             if (ref == null) {
                 unboxNull(writer, to);
             } else {
@@ -2285,33 +3138,49 @@
             }
         }
 
-        private static void box(final StackFrameReader reader, final Class<?> from,
-                                final StackFrameWriter writer, final Class<?> to) {
+        private static void box(
+                final StackFrameReader reader,
+                final Class<?> from,
+                final StackFrameWriter writer,
+                final Class<?> to) {
             Object boxed = null;
-            if (from == boolean.class) {
-                boxed = Boolean.valueOf(reader.nextBoolean());
-            } else if (from == byte.class) {
-                boxed = Byte.valueOf(reader.nextByte());
-            } else if (from == char.class) {
-                boxed = Character.valueOf(reader.nextChar());
-            } else if (from == short.class) {
-                boxed = Short.valueOf(reader.nextShort());
-            } else if (from == int.class) {
-                boxed = Integer.valueOf(reader.nextInt());
-            } else if (from == long.class) {
-                boxed = Long.valueOf(reader.nextLong());
-            } else if (from == float.class) {
-                boxed = Float.valueOf(reader.nextFloat());
-            } else if (from == double.class) {
-                boxed = Double.valueOf(reader.nextDouble());
-            } else {
-                throwUnexpectedType(from);
+            switch (Wrapper.basicTypeChar(from)) {
+                case 'Z':
+                    boxed = Boolean.valueOf(reader.nextBoolean());
+                    break;
+                case 'B':
+                    boxed = Byte.valueOf(reader.nextByte());
+                    break;
+                case 'C':
+                    boxed = Character.valueOf(reader.nextChar());
+                    break;
+                case 'S':
+                    boxed = Short.valueOf(reader.nextShort());
+                    break;
+                case 'I':
+                    boxed = Integer.valueOf(reader.nextInt());
+                    break;
+                case 'J':
+                    boxed = Long.valueOf(reader.nextLong());
+                    break;
+                case 'F':
+                    boxed = Float.valueOf(reader.nextFloat());
+                    break;
+                case 'D':
+                    boxed = Double.valueOf(reader.nextDouble());
+                    break;
+                default:
+                    throwUnexpectedType(from);
+                    break;
             }
             writer.putNextReference(to.cast(boxed), to);
         }
 
-        private static void explicitCast(final StackFrameReader reader, final Class<?> from,
-                                         final StackFrameWriter writer, final Class<?> to) {
+        private static void explicitCast(
+                final StackFrameReader reader,
+                final Class<?> from,
+                final StackFrameWriter writer,
+                final Class<?> to) {
             if (from.equals(to)) {
                 StackFrameAccessor.copyNext(reader, writer, from);
                 return;
diff --git a/ojluni/src/main/java/java/net/SocketOptions.java b/ojluni/src/main/java/java/net/SocketOptions.java
index 7e1b0fc..b9eac7b 100644
--- a/ojluni/src/main/java/java/net/SocketOptions.java
+++ b/ojluni/src/main/java/java/net/SocketOptions.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 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
@@ -38,8 +38,9 @@
  * DatagramSocketImpl, <B>you won't use these directly.</B> There are
  * type-safe methods to get/set each of these options in Socket, ServerSocket,
  * DatagramSocket and MulticastSocket.
- * <P>
+ *
  * @author David Brown
+ * @since 1.1
  */
 
 
@@ -61,21 +62,21 @@
      * If the requested option is binary, it can be set using this method by
      * a java.lang.Boolean:
      * <BR><PRE>
-     * s.setOption(TCP_NODELAY, new Boolean(true));
+     * s.setOption(TCP_NODELAY, Boolean.TRUE);
      *    // OK - enables TCP_NODELAY, a binary option
      * </PRE>
      * <BR>
-     * Any option can be disabled using this method with a Boolean(false):
+     * Any option can be disabled using this method with a Boolean.FALSE:
      * <BR><PRE>
-     * s.setOption(TCP_NODELAY, new Boolean(false));
+     * s.setOption(TCP_NODELAY, Boolean.FALSE);
      *    // OK - disables TCP_NODELAY
-     * s.setOption(SO_LINGER, new Boolean(false));
+     * s.setOption(SO_LINGER, Boolean.FALSE);
      *    // OK - disables SO_LINGER
      * </PRE>
      * <BR>
      * For an option that has a notion of on and off, and requires
      * a non-boolean parameter, setting its value to anything other than
-     * <I>Boolean(false)</I> implicitly enables it.
+     * <I>Boolean.FALSE</I> implicitly enables it.
      * <BR>
      * Throws SocketException if the option is unrecognized,
      * the socket is closed, or some low-level error occurred
@@ -91,8 +92,8 @@
 
     /**
      * Fetch the value of an option.
-     * Binary options will return java.lang.Boolean(true)
-     * if enabled, java.lang.Boolean(false) if disabled, e.g.:
+     * Binary options will return java.lang.Boolean.TRUE
+     * if enabled, java.lang.Boolean.FALSE if disabled, e.g.:
      * <BR><PRE>
      * SocketImpl s;
      * ...
@@ -105,13 +106,13 @@
      * <P>
      * For options that take a particular type as a parameter,
      * getOption(int) will return the parameter's value, else
-     * it will return java.lang.Boolean(false):
+     * it will return java.lang.Boolean.FALSE:
      * <PRE>
      * Object o = s.getOption(SO_LINGER);
      * if (o instanceof Integer) {
      *     System.out.print("Linger time is " + ((Integer)o).intValue());
      * } else {
-     *   // the true type of o is java.lang.Boolean(false);
+     *   // the true type of o is java.lang.Boolean.FALSE;
      * }
      * </PRE>
      *
@@ -139,7 +140,7 @@
      * @see Socket#getTcpNoDelay
      */
 
-    @Native public final static int TCP_NODELAY = 0x0001;
+    @Native public static final int TCP_NODELAY = 0x0001;
 
     /**
      * Fetch the local address binding of a socket (this option cannot
@@ -160,7 +161,7 @@
      * @see DatagramSocket#getLocalAddress
      */
 
-    @Native public final static int SO_BINDADDR = 0x000F;
+    @Native public static final int SO_BINDADDR = 0x000F;
 
     /** Sets SO_REUSEADDR for a socket.  This is used only for MulticastSockets
      * in java, and it is set by default for MulticastSockets.
@@ -168,7 +169,18 @@
      * Valid for: DatagramSocketImpl
      */
 
-    @Native public final static int SO_REUSEADDR = 0x04;
+    @Native public static final int SO_REUSEADDR = 0x04;
+
+    /** Sets SO_REUSEPORT for a socket. This option enables and disables
+     *  the ability to have multiple sockets listen to the same address
+     *  and port.
+     * <P>
+     * Valid for: SocketImpl, DatagramSocketImpl
+     *
+     * @since 9
+     * @see StandardSocketOptions#SO_REUSEPORT
+     */
+    @Native public static final int SO_REUSEPORT = 0x0E;
 
     /**
      * Sets SO_BROADCAST for a socket. This option enables and disables
@@ -179,7 +191,7 @@
      * @since 1.4
      */
 
-    @Native public final static int SO_BROADCAST = 0x0020;
+    @Native public static final int SO_BROADCAST = 0x0020;
 
     /** Set which outgoing interface on which to send multicast packets.
      * Useful on hosts with multiple network interfaces, where applications
@@ -191,7 +203,7 @@
      * @see MulticastSocket#getInterface()
      */
 
-    @Native public final static int IP_MULTICAST_IF = 0x10;
+    @Native public static final int IP_MULTICAST_IF = 0x10;
 
     /** Same as above. This option is introduced so that the behaviour
      *  with IP_MULTICAST_IF will be kept the same as before, while
@@ -203,7 +215,7 @@
      * @see MulticastSocket#getNetworkInterface()
      * @since 1.4
      */
-    @Native public final static int IP_MULTICAST_IF2 = 0x1f;
+    @Native public static final int IP_MULTICAST_IF2 = 0x1f;
 
     /**
      * This option enables or disables local loopback of multicast datagrams.
@@ -211,7 +223,7 @@
      * @since 1.4
      */
 
-    @Native public final static int IP_MULTICAST_LOOP = 0x12;
+    @Native public static final int IP_MULTICAST_LOOP = 0x12;
 
     /**
      * This option sets the type-of-service or traffic class field
@@ -219,7 +231,7 @@
      * @since 1.4
      */
 
-    @Native public final static int IP_TOS = 0x3;
+    @Native public static final int IP_TOS = 0x3;
 
     /**
      * Specify a linger-on-close timeout.  This option disables/enables
@@ -237,7 +249,7 @@
      * @see Socket#setSoLinger
      * @see Socket#getSoLinger
      */
-    @Native public final static int SO_LINGER = 0x0080;
+    @Native public static final int SO_LINGER = 0x0080;
 
     /** Set a timeout on blocking Socket operations:
      * <PRE>
@@ -293,7 +305,7 @@
      * @see DatagramSocket#setReceiveBufferSize
      * @see DatagramSocket#getReceiveBufferSize
      */
-    @Native public final static int SO_RCVBUF = 0x1002;
+    @Native public static final int SO_RCVBUF = 0x1002;
 
     /**
      * When the keepalive option is set for a TCP socket and no data
@@ -316,7 +328,7 @@
      * @see Socket#setKeepAlive
      * @see Socket#getKeepAlive
      */
-    @Native public final static int SO_KEEPALIVE = 0x0008;
+    @Native public static final int SO_KEEPALIVE = 0x0008;
 
     /**
      * When the OOBINLINE option is set, any TCP urgent data received on
@@ -327,5 +339,5 @@
      * @see Socket#setOOBInline
      * @see Socket#getOOBInline
      */
-    @Native public final static int SO_OOBINLINE = 0x1003;
+    @Native public static final int SO_OOBINLINE = 0x1003;
 }
diff --git a/ojluni/src/main/java/java/net/StandardSocketOptions.java b/ojluni/src/main/java/java/net/StandardSocketOptions.java
index 7fdd5f0..ae47845 100644
--- a/ojluni/src/main/java/java/net/StandardSocketOptions.java
+++ b/ojluni/src/main/java/java/net/StandardSocketOptions.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 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
@@ -187,6 +187,29 @@
         new StdSocketOption<Boolean>("SO_REUSEADDR", Boolean.class);
 
     /**
+     * Re-use port.
+     *
+     * <p> The value of this socket option is a {@code Boolean} that represents
+     * whether the option is enabled or disabled. The exact semantics of this
+     * socket option are socket type and system dependent.
+     *
+     * <p> In the case of stream-oriented sockets, this socket option usually allows
+     * multiple listening sockets to be bound to both same address
+     * and same port.
+     *
+     * <p> For datagram-oriented sockets the socket option usually allows
+     * multiple UDP sockets to be bound to the same address and port.
+     *
+     * <p> An implementation allows this socket option to be set before the
+     * socket is bound or connected. Changing the value of this socket option
+     * after the socket is bound has no effect.
+     *
+     * @since 9
+     */
+    public static final SocketOption<Boolean> SO_REUSEPORT =
+        new StdSocketOption<Boolean>("SO_REUSEPORT", Boolean.class);
+
+    /**
      * Linger on close if data is present.
      *
      * <p> The value of this socket option is an {@code Integer} that controls
diff --git a/ojluni/src/main/java/java/security/AccessControlException.java b/ojluni/src/main/java/java/security/AccessControlException.java
index a4f2a78..3d654ee 100644
--- a/ojluni/src/main/java/java/security/AccessControlException.java
+++ b/ojluni/src/main/java/java/security/AccessControlException.java
@@ -38,6 +38,7 @@
  *
  * @author Li Gong
  * @author Roland Schemers
+ * @since 1.2
  */
 
 public class AccessControlException extends SecurityException {
diff --git a/ojluni/src/main/java/java/security/Certificate.java b/ojluni/src/main/java/java/security/Certificate.java
index 489c6d6..28690e2 100644
--- a/ojluni/src/main/java/java/security/Certificate.java
+++ b/ojluni/src/main/java/java/security/Certificate.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 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
@@ -56,13 +56,13 @@
  * the certificate and satisfy itself of its validity.
  *
  * @author Benjamin Renaud
- * @deprecated A new certificate handling package is created in the Java platform.
- *             This Certificate interface is entirely deprecated and
- *             is here to allow for a smooth transition to the new
- *             package.
+ * @since 1.1
+ * @deprecated This class is deprecated and subject to removal in a future
+ *     version of Java SE. It has been replaced by
+ *     {@code java.security.cert.Certificate} and related classes.
  * @see java.security.cert.Certificate
  */
-@Deprecated
+@Deprecated(since="1.2", forRemoval=true)
 public interface Certificate {
 
     /**
diff --git a/ojluni/src/main/java/java/security/DigestInputStream.java b/ojluni/src/main/java/java/security/DigestInputStream.java
index a1bf55a..a76ebda 100644
--- a/ojluni/src/main/java/java/security/DigestInputStream.java
+++ b/ojluni/src/main/java/java/security/DigestInputStream.java
@@ -52,13 +52,14 @@
  * {@link MessageDigest}),
  * so that in order to compute intermediate digests, a caller should
  * retain a handle onto the digest object, and clone it for each
- * digest to be computed, leaving the orginal digest untouched.
+ * digest to be computed, leaving the original digest untouched.
  *
  * @see MessageDigest
  *
  * @see DigestOutputStream
  *
  * @author Benjamin Renaud
+ * @since 1.2
  */
 
 public class DigestInputStream extends FilterInputStream {
diff --git a/ojluni/src/main/java/java/security/DigestOutputStream.java b/ojluni/src/main/java/java/security/DigestOutputStream.java
index 619faec..e941673 100644
--- a/ojluni/src/main/java/java/security/DigestOutputStream.java
+++ b/ojluni/src/main/java/java/security/DigestOutputStream.java
@@ -51,6 +51,7 @@
  * @see DigestInputStream
  *
  * @author Benjamin Renaud
+ * @since 1.2
  */
 public class DigestOutputStream extends FilterOutputStream {
 
diff --git a/ojluni/src/main/java/java/security/DomainCombiner.java b/ojluni/src/main/java/java/security/DomainCombiner.java
index e9c010f..d844939 100644
--- a/ojluni/src/main/java/java/security/DomainCombiner.java
+++ b/ojluni/src/main/java/java/security/DomainCombiner.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2015, 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
diff --git a/ojluni/src/main/java/java/security/GeneralSecurityException.java b/ojluni/src/main/java/java/security/GeneralSecurityException.java
index dc9ea06..69ee8ea 100644
--- a/ojluni/src/main/java/java/security/GeneralSecurityException.java
+++ b/ojluni/src/main/java/java/security/GeneralSecurityException.java
@@ -31,6 +31,7 @@
  * security-related exception classes that extend from it.
  *
  * @author Jan Luehe
+ * @since 1.2
  */
 
 public class GeneralSecurityException extends Exception {
diff --git a/ojluni/src/main/java/java/security/Guard.java b/ojluni/src/main/java/java/security/Guard.java
index abafb58..f64a0d9 100644
--- a/ojluni/src/main/java/java/security/Guard.java
+++ b/ojluni/src/main/java/java/security/Guard.java
@@ -38,6 +38,7 @@
  *
  * @author Roland Schemers
  * @author Li Gong
+ * @since 1.2
  */
 
 public interface Guard {
diff --git a/ojluni/src/main/java/java/security/GuardedObject.java b/ojluni/src/main/java/java/security/GuardedObject.java
index a275ddf..a2bf3f7 100644
--- a/ojluni/src/main/java/java/security/GuardedObject.java
+++ b/ojluni/src/main/java/java/security/GuardedObject.java
@@ -44,6 +44,7 @@
  *
  * @author Roland Schemers
  * @author Li Gong
+ * @since 1.2
  */
 
 public class GuardedObject implements java.io.Serializable {
diff --git a/ojluni/src/main/java/java/security/Identity.java b/ojluni/src/main/java/java/security/Identity.java
index e63131e..e6b2e80 100644
--- a/ojluni/src/main/java/java/security/Identity.java
+++ b/ojluni/src/main/java/java/security/Identity.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 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
@@ -52,12 +52,14 @@
  * @see Principal
  *
  * @author Benjamin Renaud
- * @deprecated This class is no longer used. Its functionality has been
- * replaced by {@code java.security.KeyStore}, the
- * {@code java.security.cert} package, and
- * {@code java.security.Principal}.
+ * @since 1.1
+ * @deprecated This class is deprecated and subject to removal in a future
+ *     version of Java SE. It has been replaced by
+ *     {@code java.security.KeyStore}, the {@code java.security.cert} package,
+ *     and {@code java.security.Principal}.
  */
-@Deprecated
+@Deprecated(since="1.2", forRemoval=true)
+@SuppressWarnings("removal")
 public abstract class Identity implements Principal, Serializable {
 
     /** use serialVersionUID from JDK 1.1.x for interoperability */
@@ -186,7 +188,7 @@
 
         check("setIdentityPublicKey");
         this.publicKey = key;
-        certificates = new Vector<Certificate>();
+        certificates = new Vector<>();
     }
 
     /**
@@ -249,7 +251,7 @@
         check("addIdentityCertificate");
 
         if (certificates == null) {
-            certificates = new Vector<Certificate>();
+            certificates = new Vector<>();
         }
         if (publicKey != null) {
             if (!keyEquals(publicKey, certificate.getPublicKey())) {
diff --git a/ojluni/src/main/java/java/security/InvalidAlgorithmParameterException.java b/ojluni/src/main/java/java/security/InvalidAlgorithmParameterException.java
index 559a8be..9636fcb 100644
--- a/ojluni/src/main/java/java/security/InvalidAlgorithmParameterException.java
+++ b/ojluni/src/main/java/java/security/InvalidAlgorithmParameterException.java
@@ -65,7 +65,7 @@
     }
 
     /**
-     * Creates a {@code InvalidAlgorithmParameterException} with the
+     * Creates an {@code InvalidAlgorithmParameterException} with the
      * specified detail message and cause.
      *
      * @param message the detail message (which is saved for later retrieval
@@ -80,7 +80,7 @@
     }
 
     /**
-     * Creates a {@code InvalidAlgorithmParameterException} with the
+     * Creates an {@code InvalidAlgorithmParameterException} with the
      * specified cause and a detail message of
      * {@code (cause==null ? null : cause.toString())}
      * (which typically contains the class and detail message of
diff --git a/ojluni/src/main/java/java/security/InvalidKeyException.java b/ojluni/src/main/java/java/security/InvalidKeyException.java
index 35fc64c..5349796 100644
--- a/ojluni/src/main/java/java/security/InvalidKeyException.java
+++ b/ojluni/src/main/java/java/security/InvalidKeyException.java
@@ -31,6 +31,7 @@
  * length, uninitialized, etc).
  *
  * @author Benjamin Renaud
+ * @since 1.1
  */
 
 public class InvalidKeyException extends KeyException {
@@ -58,7 +59,7 @@
     }
 
     /**
-     * Creates a {@code InvalidKeyException} with the specified
+     * Creates an {@code InvalidKeyException} with the specified
      * detail message and cause.
      *
      * @param message the detail message (which is saved for later retrieval
@@ -73,7 +74,7 @@
     }
 
     /**
-     * Creates a {@code InvalidKeyException} with the specified cause
+     * Creates an {@code InvalidKeyException} with the specified cause
      * and a detail message of {@code (cause==null ? null : cause.toString())}
      * (which typically contains the class and detail message of
      * {@code cause}).
diff --git a/ojluni/src/main/java/java/security/InvalidParameterException.java b/ojluni/src/main/java/java/security/InvalidParameterException.java
index a095f90..18d413e 100644
--- a/ojluni/src/main/java/java/security/InvalidParameterException.java
+++ b/ojluni/src/main/java/java/security/InvalidParameterException.java
@@ -31,6 +31,7 @@
  * to a method.
  *
  * @author Benjamin Renaud
+ * @since 1.1
  */
 
 public class InvalidParameterException extends IllegalArgumentException {
diff --git a/ojluni/src/main/java/java/security/Key.java b/ojluni/src/main/java/java/security/Key.java
index c0c63d7..ff5611e 100644
--- a/ojluni/src/main/java/java/security/Key.java
+++ b/ojluni/src/main/java/java/security/Key.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 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
@@ -63,7 +63,7 @@
  * </pre>
  *
  * For more information, see
- * <a href="http://www.ietf.org/rfc/rfc3280.txt">RFC 3280:
+ * <a href="http://tools.ietf.org/html/rfc5280">RFC 5280:
  * Internet X.509 Public Key Infrastructure Certificate and CRL Profile</a>.
  *
  * <LI>A Format
@@ -82,7 +82,7 @@
  * <p> A Key should use KeyRep as its serialized representation.
  * Note that a serialized Key may contain sensitive information
  * which should not be exposed in untrusted environments.  See the
- * <a href="../../../platform/serialization/spec/security.html">
+ * <a href="{@docRoot}/../specs/serialization/security.html">
  * Security Appendix</a>
  * of the Serialization Specification for more information.
  *
@@ -97,6 +97,7 @@
  * @see Signer
  *
  * @author Benjamin Renaud
+ * @since 1.1
  */
 
 public interface Key extends java.io.Serializable {
@@ -113,10 +114,10 @@
     /**
      * Returns the standard algorithm name for this key. For
      * example, "DSA" would indicate that this key is a DSA key.
-     * See Appendix A in the <a href=
-     * "../../../technotes/guides/security/crypto/CryptoSpec.html#AppA">
-     * Java Cryptography Architecture API Specification &amp; Reference </a>
-     * for information about standard algorithm names.
+     * See the <a href=
+     * "{@docRoot}/../specs/security/standard-names.html">
+     * Java Security Standard Algorithm Names</a> document
+     * for more information.
      *
      * @return the name of the algorithm associated with this key.
      */
diff --git a/ojluni/src/main/java/java/security/KeyException.java b/ojluni/src/main/java/java/security/KeyException.java
index 59cdd6f..b8b87d9 100644
--- a/ojluni/src/main/java/java/security/KeyException.java
+++ b/ojluni/src/main/java/java/security/KeyException.java
@@ -33,6 +33,7 @@
  * @see KeyManagementException
  *
  * @author Benjamin Renaud
+ * @since 1.1
  */
 
 public class KeyException extends GeneralSecurityException {
diff --git a/ojluni/src/main/java/java/security/KeyManagementException.java b/ojluni/src/main/java/java/security/KeyManagementException.java
index be212b9..fe1ab3e 100644
--- a/ojluni/src/main/java/java/security/KeyManagementException.java
+++ b/ojluni/src/main/java/java/security/KeyManagementException.java
@@ -38,6 +38,7 @@
  * </ul>
  *
  * @author Benjamin Renaud
+ * @since 1.1
  *
  * @see Key
  * @see KeyException
diff --git a/ojluni/src/main/java/java/security/KeyPair.java b/ojluni/src/main/java/java/security/KeyPair.java
index 6147a16..1d9e164 100644
--- a/ojluni/src/main/java/java/security/KeyPair.java
+++ b/ojluni/src/main/java/java/security/KeyPair.java
@@ -36,6 +36,7 @@
  * @see PrivateKey
  *
  * @author Benjamin Renaud
+ * @since 1.1
  */
 
 public final class KeyPair implements java.io.Serializable {
diff --git a/ojluni/src/main/java/java/security/KeyRep.java b/ojluni/src/main/java/java/security/KeyRep.java
index 0b1412c..9d53635 100644
--- a/ojluni/src/main/java/java/security/KeyRep.java
+++ b/ojluni/src/main/java/java/security/KeyRep.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 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
@@ -42,7 +42,7 @@
  *
  * Note that a serialized Key may contain sensitive information
  * which should not be exposed in untrusted environments.  See the
- * <a href="../../../platform/serialization/spec/security.html">
+ * <a href="{@docRoot}/../specs/serialization/security.html">
  * Security Appendix</a>
  * of the Serialization Specification for more information.
  *
@@ -112,8 +112,6 @@
     /**
      * Construct the alternate Key class.
      *
-     * <p>
-     *
      * @param type either one of Type.SECRET, Type.PUBLIC, or Type.PRIVATE
      * @param algorithm the algorithm returned from
      *          {@code Key.getAlgorithm()}
@@ -157,8 +155,6 @@
      * encoded key bytes, and generates a private key from the spec
      * </ul>
      *
-     * <p>
-     *
      * @return the resolved Key object
      *
      * @exception ObjectStreamException if the Type/format
diff --git a/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java b/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java
index 951e44e..24455d3 100644
--- a/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java
+++ b/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java
@@ -30,6 +30,7 @@
  * requested but is not available in the environment.
  *
  * @author Benjamin Renaud
+ * @since 1.1
  */
 
 public class NoSuchAlgorithmException extends GeneralSecurityException {
diff --git a/ojluni/src/main/java/java/security/PKCS12Attribute.java b/ojluni/src/main/java/java/security/PKCS12Attribute.java
index e389862..4a8ebfb 100644
--- a/ojluni/src/main/java/java/security/PKCS12Attribute.java
+++ b/ojluni/src/main/java/java/security/PKCS12Attribute.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2020, 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
@@ -44,7 +44,7 @@
         Pattern.compile("^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2})+$");
     private String name;
     private String value;
-    private byte[] encoded;
+    private final byte[] encoded;
     private int hashValue = -1;
 
     /**
@@ -85,7 +85,8 @@
         // Validate value
         int length = value.length();
         String[] values;
-        if (value.charAt(0) == '[' && value.charAt(length - 1) == ']') {
+        if (length > 1 &&
+                value.charAt(0) == '[' && value.charAt(length - 1) == ']') {
             values = value.substring(1, length - 1).split(", ");
         } else {
             values = new String[]{ value };
@@ -198,7 +199,7 @@
         if (!(obj instanceof PKCS12Attribute)) {
             return false;
         }
-        return Arrays.equals(encoded, ((PKCS12Attribute) obj).getEncoded());
+        return Arrays.equals(encoded, ((PKCS12Attribute) obj).encoded);
     }
 
     /**
@@ -209,10 +210,11 @@
      */
     @Override
     public int hashCode() {
-        if (hashValue == -1) {
-            Arrays.hashCode(encoded);
+        int h = hashValue;
+        if (h == -1) {
+            hashValue = h = Arrays.hashCode(encoded);
         }
-        return hashValue;
+        return h;
     }
 
     /**
@@ -252,6 +254,9 @@
     private void parse(byte[] encoded) throws IOException {
         DerInputStream attributeValue = new DerInputStream(encoded);
         DerValue[] attrSeq = attributeValue.getSequence(2);
+        if (attrSeq.length != 2) {
+            throw new IOException("Invalid length for PKCS12Attribute");
+        }
         ObjectIdentifier type = attrSeq[0].getOID();
         DerInputStream attrContent =
             new DerInputStream(attrSeq[1].toByteArray());
diff --git a/ojluni/src/main/java/java/security/Principal.java b/ojluni/src/main/java/java/security/Principal.java
index a538e70..40ee260 100644
--- a/ojluni/src/main/java/java/security/Principal.java
+++ b/ojluni/src/main/java/java/security/Principal.java
@@ -35,6 +35,7 @@
  * @see java.security.cert.X509Certificate
  *
  * @author Li Gong
+ * @since 1.1
  */
 public interface Principal {
 
@@ -74,7 +75,8 @@
     /**
      * Returns true if the specified subject is implied by this principal.
      *
-     * <p>The default implementation of this method returns true if
+     * @implSpec
+     * The default implementation of this method returns true if
      * {@code subject} is non-null and contains at least one principal that
      * is equal to this principal.
      *
diff --git a/ojluni/src/main/java/java/security/PrivateKey.java b/ojluni/src/main/java/java/security/PrivateKey.java
index 7d8a7ea..0bc933b 100644
--- a/ojluni/src/main/java/java/security/PrivateKey.java
+++ b/ojluni/src/main/java/java/security/PrivateKey.java
@@ -54,6 +54,7 @@
  *
  * @author Benjamin Renaud
  * @author Josh Bloch
+ * @since 1.1
  */
 
 public interface PrivateKey extends Key, javax.security.auth.Destroyable {
diff --git a/ojluni/src/main/java/java/security/PrivilegedActionException.java b/ojluni/src/main/java/java/security/PrivilegedActionException.java
index b1eb28f..ddfb89d 100644
--- a/ojluni/src/main/java/java/security/PrivilegedActionException.java
+++ b/ojluni/src/main/java/java/security/PrivilegedActionException.java
@@ -47,6 +47,7 @@
  * <i>cause</i>, and may be accessed via the {@link Throwable#getCause()}
  * method, as well as the aforementioned "legacy method."
  *
+ * @since 1.2
  * @see PrivilegedExceptionAction
  * @see AccessController#doPrivileged(PrivilegedExceptionAction)
  * @see AccessController#doPrivileged(PrivilegedExceptionAction,AccessControlContext)
diff --git a/ojluni/src/main/java/java/security/ProtectionDomain.java b/ojluni/src/main/java/java/security/ProtectionDomain.java
index 9e117a6..47e9fb1 100644
--- a/ojluni/src/main/java/java/security/ProtectionDomain.java
+++ b/ojluni/src/main/java/java/security/ProtectionDomain.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, 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
diff --git a/ojluni/src/main/java/java/security/ProviderException.java b/ojluni/src/main/java/java/security/ProviderException.java
index b372ee7..48c3638 100644
--- a/ojluni/src/main/java/java/security/ProviderException.java
+++ b/ojluni/src/main/java/java/security/ProviderException.java
@@ -32,6 +32,7 @@
  * throw specialized, provider-specific runtime errors.
  *
  * @author Benjamin Renaud
+ * @since 1.1
  */
 public class ProviderException extends RuntimeException {
 
diff --git a/ojluni/src/main/java/java/security/PublicKey.java b/ojluni/src/main/java/java/security/PublicKey.java
index df49807..986f9d0 100644
--- a/ojluni/src/main/java/java/security/PublicKey.java
+++ b/ojluni/src/main/java/java/security/PublicKey.java
@@ -34,6 +34,7 @@
  * See, for example, the DSAPublicKey interface in
  * {@code java.security.interfaces}.
  *
+ * @since 1.1
  * @see Key
  * @see PrivateKey
  * @see Certificate
diff --git a/ojluni/src/main/java/java/security/SecurityPermission.java b/ojluni/src/main/java/java/security/SecurityPermission.java
index f02755c..a4eefcc 100644
--- a/ojluni/src/main/java/java/security/SecurityPermission.java
+++ b/ojluni/src/main/java/java/security/SecurityPermission.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
diff --git a/ojluni/src/main/java/java/security/SignatureException.java b/ojluni/src/main/java/java/security/SignatureException.java
index 2e1fa59..7788e12 100644
--- a/ojluni/src/main/java/java/security/SignatureException.java
+++ b/ojluni/src/main/java/java/security/SignatureException.java
@@ -29,6 +29,7 @@
  * This is the generic Signature exception.
  *
  * @author Benjamin Renaud
+ * @since 1.1
  */
 
 public class SignatureException extends GeneralSecurityException {
diff --git a/ojluni/src/main/java/java/security/SignedObject.java b/ojluni/src/main/java/java/security/SignedObject.java
index 9ac864e..e14c0fa 100644
--- a/ojluni/src/main/java/java/security/SignedObject.java
+++ b/ojluni/src/main/java/java/security/SignedObject.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
@@ -81,13 +81,12 @@
  * verification in an attempt to bypass a security check.
  *
  * <p> The signature algorithm can be, among others, the NIST standard
- * DSA, using DSA and SHA-1.  The algorithm is specified using the
+ * DSA, using DSA and SHA-256.  The algorithm is specified using the
  * same convention as that for signatures. The DSA algorithm using the
- * SHA-1 message digest algorithm can be specified, for example, as
- * "SHA/DSA" or "SHA-1/DSA" (they are equivalent).  In the case of
- * RSA, there are multiple choices for the message digest algorithm,
- * so the signing algorithm could be specified as, for example,
- * "MD2/RSA", "MD5/RSA" or "SHA-1/RSA".  The algorithm name must be
+ * SHA-256 message digest algorithm can be specified, for example, as
+ * "SHA256withDSA".  In the case of
+ * RSA the signing algorithm could be specified as, for example,
+ * "SHA256withRSA".  The algorithm name must be
  * specified, as there is no default.
  *
  * <p> The name of the Cryptography Package Provider is designated
@@ -114,6 +113,7 @@
  * @see Signature
  *
  * @author Li Gong
+ * @since 1.2
  */
 
 public final class SignedObject implements Serializable {
diff --git a/ojluni/src/main/java/java/security/Signer.java b/ojluni/src/main/java/java/security/Signer.java
index 077538d..56d3780 100644
--- a/ojluni/src/main/java/java/security/Signer.java
+++ b/ojluni/src/main/java/java/security/Signer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 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
@@ -38,13 +38,15 @@
  * @see Identity
  *
  * @author Benjamin Renaud
+ * @since 1.1
  *
- * @deprecated This class is no longer used. Its functionality has been
- * replaced by {@code java.security.KeyStore}, the
- * {@code java.security.cert} package, and
- * {@code java.security.Principal}.
+ * @deprecated This class is deprecated and subject to removal in a future
+ *     version of Java SE. It has been replaced by
+ *     {@code java.security.KeyStore}, the {@code java.security.cert} package,
+ *     and {@code java.security.Principal}.
  */
-@Deprecated
+@Deprecated(since="1.2", forRemoval=true)
+@SuppressWarnings("removal")
 public abstract class Signer extends Identity {
 
     private static final long serialVersionUID = -1763464102261361480L;
diff --git a/ojluni/src/main/java/java/security/Timestamp.java b/ojluni/src/main/java/java/security/Timestamp.java
index f66d288..668a611 100644
--- a/ojluni/src/main/java/java/security/Timestamp.java
+++ b/ojluni/src/main/java/java/security/Timestamp.java
@@ -141,7 +141,7 @@
      *         its signer's certificate.
      */
     public String toString() {
-        StringBuffer sb = new StringBuffer();
+        StringBuilder sb = new StringBuilder();
         sb.append("(");
         sb.append("timestamp: " + timestamp);
         List<? extends Certificate> certs = signerCertPath.getCertificates();
diff --git a/ojluni/src/main/java/java/security/UnresolvedPermission.java b/ojluni/src/main/java/java/security/UnresolvedPermission.java
index 6f3bf4a..7e535da 100644
--- a/ojluni/src/main/java/java/security/UnresolvedPermission.java
+++ b/ojluni/src/main/java/java/security/UnresolvedPermission.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
diff --git a/ojluni/src/main/java/java/security/interfaces/DSAKey.java b/ojluni/src/main/java/java/security/interfaces/DSAKey.java
index d78b3e1..64ac8c4 100644
--- a/ojluni/src/main/java/java/security/interfaces/DSAKey.java
+++ b/ojluni/src/main/java/java/security/interfaces/DSAKey.java
@@ -35,6 +35,7 @@
  *
  * @author Benjamin Renaud
  * @author Josh Bloch
+ * @since 1.1
  */
 public interface DSAKey {
 
diff --git a/ojluni/src/main/java/java/security/interfaces/DSAKeyPairGenerator.java b/ojluni/src/main/java/java/security/interfaces/DSAKeyPairGenerator.java
index e50cfd2..96e55eb 100644
--- a/ojluni/src/main/java/java/security/interfaces/DSAKeyPairGenerator.java
+++ b/ojluni/src/main/java/java/security/interfaces/DSAKeyPairGenerator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
@@ -32,9 +32,12 @@
  *
  * <p>The {@code initialize} methods may each be called any number
  * of times. If no {@code initialize} method is called on a
- * DSAKeyPairGenerator, the default is to generate 1024-bit keys, using
- * precomputed p, q and g parameters and an instance of SecureRandom as
- * the random bit source.
+ * DSAKeyPairGenerator, each provider that implements this interface
+ * should supply (and document) a default initialization. Note that
+ * defaults may vary across different providers. Additionally, the default
+ * value for a provider may change in a future version. Therefore, it is
+ * recommended to explicitly initialize the DSAKeyPairGenerator instead
+ * of relying on provider-specific defaults.
  *
  * <p>Users wishing to indicate DSA-specific parameters, and to generate a key
  * pair suitable for use with the DSA algorithm typically
@@ -45,16 +48,17 @@
  * KeyPairGenerator {@code getInstance} method with "DSA"
  * as its argument.
  *
- * <li>Initialize the generator by casting the result to a DSAKeyPairGenerator
- * and calling one of the
- * {@code initialize} methods from this DSAKeyPairGenerator interface.
+ * <li>Check if the returned key pair generator is an instance of
+ * DSAKeyPairGenerator before casting the result to a DSAKeyPairGenerator
+ * and calling one of the {@code initialize} methods from this
+ * DSAKeyPairGenerator interface.
  *
  * <li>Generate a key pair by calling the {@code generateKeyPair}
- * method from the KeyPairGenerator class.
+ * method of the KeyPairGenerator class.
  *
  * </ol>
  *
- * <p>Note: it is not always necessary to do do algorithm-specific
+ * <p>Note: it is not always necessary to do algorithm-specific
  * initialization for a DSA key pair generator. That is, it is not always
  * necessary to call an {@code initialize} method in this interface.
  * Algorithm-independent initialization using the {@code initialize} method
@@ -63,8 +67,9 @@
  * parameters.
  *
  * <p>Note: Some earlier implementations of this interface may not support
- * larger sizes of DSA parameters such as 2048 and 3072-bit.
+ * larger values of DSA parameters such as 3072-bit.
  *
+ * @since 1.1
  * @see java.security.KeyPairGenerator
  */
 public interface DSAKeyPairGenerator {
@@ -96,8 +101,7 @@
      * p, q and g parameters. If it is false, the method uses precomputed
      * parameters for the modulus length requested. If there are no
      * precomputed parameters for that modulus length, an exception will be
-     * thrown. It is guaranteed that there will always be
-     * default parameters for modulus lengths of 512 and 1024 bits.
+     * thrown.
      *
      * @param modlen the modulus length in bits. Valid values are any
      * multiple of 64 between 512 and 1024, inclusive, 2048, and 3072.
diff --git a/ojluni/src/main/java/java/security/interfaces/DSAParams.java b/ojluni/src/main/java/java/security/interfaces/DSAParams.java
index 8c46ed5..2eafe87 100644
--- a/ojluni/src/main/java/java/security/interfaces/DSAParams.java
+++ b/ojluni/src/main/java/java/security/interfaces/DSAParams.java
@@ -38,6 +38,7 @@
  *
  * @author Benjamin Renaud
  * @author Josh Bloch
+ * @since 1.1
  */
 public interface DSAParams {
 
diff --git a/ojluni/src/main/java/java/security/interfaces/DSAPrivateKey.java b/ojluni/src/main/java/java/security/interfaces/DSAPrivateKey.java
index 81ab358..b23a5c1 100644
--- a/ojluni/src/main/java/java/security/interfaces/DSAPrivateKey.java
+++ b/ojluni/src/main/java/java/security/interfaces/DSAPrivateKey.java
@@ -37,6 +37,7 @@
  * @see DSAPublicKey
  *
  * @author Benjamin Renaud
+ * @since 1.1
  */
 public interface DSAPrivateKey extends DSAKey, java.security.PrivateKey {
 
diff --git a/ojluni/src/main/java/java/security/interfaces/DSAPublicKey.java b/ojluni/src/main/java/java/security/interfaces/DSAPublicKey.java
index e56b795..fb4a2f3 100644
--- a/ojluni/src/main/java/java/security/interfaces/DSAPublicKey.java
+++ b/ojluni/src/main/java/java/security/interfaces/DSAPublicKey.java
@@ -37,6 +37,7 @@
  * @see DSAPrivateKey
  *
  * @author Benjamin Renaud
+ * @since 1.1
  */
 public interface DSAPublicKey extends DSAKey, java.security.PublicKey {
 
diff --git a/ojluni/src/main/java/java/security/interfaces/RSAMultiPrimePrivateCrtKey.java b/ojluni/src/main/java/java/security/interfaces/RSAMultiPrimePrivateCrtKey.java
index f85d96a..fd42c5c 100644
--- a/ojluni/src/main/java/java/security/interfaces/RSAMultiPrimePrivateCrtKey.java
+++ b/ojluni/src/main/java/java/security/interfaces/RSAMultiPrimePrivateCrtKey.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2018, 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
@@ -30,8 +30,8 @@
 
 /**
  * The interface to an RSA multi-prime private key, as defined in the
- * PKCS#1 v2.1, using the <i>Chinese Remainder Theorem</i>
- * (CRT) information values.
+ * <a href="https://tools.ietf.org/rfc/rfc8017.txt">PKCS#1 v2.2</a> standard,
+ * using the <i>Chinese Remainder Theorem</i> (CRT) information values.
  *
  * @author Valerie Peng
  *
diff --git a/ojluni/src/main/java/java/security/interfaces/RSAPrivateCrtKey.java b/ojluni/src/main/java/java/security/interfaces/RSAPrivateCrtKey.java
index 0408fea..e6acef1 100644
--- a/ojluni/src/main/java/java/security/interfaces/RSAPrivateCrtKey.java
+++ b/ojluni/src/main/java/java/security/interfaces/RSAPrivateCrtKey.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2018, 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
@@ -28,10 +28,12 @@
 import java.math.BigInteger;
 
 /**
- * The interface to an RSA private key, as defined in the PKCS#1 standard,
+ * The interface to an RSA private key, as defined in the
+ * <a href="https://tools.ietf.org/rfc/rfc8017.txt">PKCS#1 v2.2</a> standard,
  * using the <i>Chinese Remainder Theorem</i> (CRT) information values.
  *
  * @author Jan Luehe
+ * @since 1.2
  *
  *
  * @see RSAPrivateKey
diff --git a/ojluni/src/main/java/java/security/interfaces/RSAPrivateKey.java b/ojluni/src/main/java/java/security/interfaces/RSAPrivateKey.java
index 5d69ad6..390da4e 100644
--- a/ojluni/src/main/java/java/security/interfaces/RSAPrivateKey.java
+++ b/ojluni/src/main/java/java/security/interfaces/RSAPrivateKey.java
@@ -31,6 +31,7 @@
  * The interface to an RSA private key.
  *
  * @author Jan Luehe
+ * @since 1.2
  *
  *
  * @see RSAPrivateCrtKey
diff --git a/ojluni/src/main/java/java/security/interfaces/RSAPublicKey.java b/ojluni/src/main/java/java/security/interfaces/RSAPublicKey.java
index a698c05..f195306 100644
--- a/ojluni/src/main/java/java/security/interfaces/RSAPublicKey.java
+++ b/ojluni/src/main/java/java/security/interfaces/RSAPublicKey.java
@@ -31,6 +31,7 @@
  * The interface to an RSA public key.
  *
  * @author Jan Luehe
+ * @since 1.2
  *
  */
 
diff --git a/ojluni/src/main/java/java/security/interfaces/XECKey.java b/ojluni/src/main/java/java/security/interfaces/XECKey.java
new file mode 100644
index 0000000..7412daa
--- /dev/null
+++ b/ojluni/src/main/java/java/security/interfaces/XECKey.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018, 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.security.interfaces;
+
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * An interface for an elliptic curve public/private key as defined by
+ * RFC 7748. These keys are distinct from the keys represented by
+ * {@code ECKey}, and they are intended for use with algorithms based on RFC
+ * 7748 such as the XDH {@code KeyAgreement} algorithm. This interface allows
+ * access to the algorithm parameters associated with the key.
+ *
+ * @since 11
+ */
+public interface XECKey {
+    /**
+     * Returns the algorithm parameters associated
+     * with the key.
+     *
+     * @return the associated algorithm parameters
+     */
+    AlgorithmParameterSpec getParams();
+}
+
diff --git a/ojluni/src/main/java/java/security/interfaces/XECPrivateKey.java b/ojluni/src/main/java/java/security/interfaces/XECPrivateKey.java
new file mode 100644
index 0000000..5476777
--- /dev/null
+++ b/ojluni/src/main/java/java/security/interfaces/XECPrivateKey.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018, 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.security.interfaces;
+
+import java.security.PrivateKey;
+import java.util.Optional;
+
+/**
+ * An interface for an elliptic curve private key as defined by RFC 7748.
+ * These keys are distinct from the keys represented by {@code ECPrivateKey},
+ * and they are intended for use with algorithms based on RFC 7748 such as the
+ * XDH {@code KeyAgreement} algorithm.
+ *
+ * An XEC private key is an encoded scalar value as described in RFC 7748.
+ * The decoding procedure defined in this RFC includes an operation that forces
+ * certain bits of the key to either 1 or 0. This operation is known as
+ * "pruning" or "clamping" the private key. Arrays returned by this interface
+ * are unpruned, and implementations will need to prune the array before
+ * using it in any numerical operations.
+ *
+ * @since 11
+ */
+public interface XECPrivateKey extends XECKey, PrivateKey {
+
+    /**
+     * Get the scalar value encoded as an unpruned byte array. A new copy of
+     * the array is returned each time this method is called.
+     *
+     * @return the unpruned encoded scalar value, or an empty Optional if the
+     *     scalar cannot be extracted (e.g. if the provider is a hardware token
+     *     and the private key is not allowed to leave the crypto boundary).
+     */
+    Optional<byte[]> getScalar();
+}
+
diff --git a/ojluni/src/main/java/java/security/interfaces/XECPublicKey.java b/ojluni/src/main/java/java/security/interfaces/XECPublicKey.java
new file mode 100644
index 0000000..6ec200b
--- /dev/null
+++ b/ojluni/src/main/java/java/security/interfaces/XECPublicKey.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018, 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.security.interfaces;
+
+import java.math.BigInteger;
+import java.security.PublicKey;
+
+/**
+ * An interface for an elliptic curve public key as defined by RFC 7748.
+ * These keys are distinct from the keys represented by {@code ECPublicKey},
+ * and they are intended for use with algorithms based on RFC 7748 such as the
+ * XDH {@code KeyAgreement} algorithm.
+ *
+ * An XEC public key is a particular point on the curve, which is represented
+ * using only its u-coordinate as described in RFC 7748. A u-coordinate is an
+ * element of the field of integers modulo some value that is determined by
+ * the algorithm parameters. This field element is represented by a BigInteger
+ * which may hold any value. That is, the BigInteger is not restricted to the
+ * range of canonical field elements.
+ *
+ * @since 11
+ */
+public interface XECPublicKey extends XECKey, PublicKey {
+
+    /**
+     * Get the u coordinate of the point.
+     *
+     * @return the u-coordinate, represented using a BigInteger which may hold
+     *          any value
+     */
+    BigInteger getU();
+
+}
+
diff --git a/ojluni/src/main/java/java/security/spec/XECPrivateKeySpec.java b/ojluni/src/main/java/java/security/spec/XECPrivateKeySpec.java
new file mode 100644
index 0000000..02687b5
--- /dev/null
+++ b/ojluni/src/main/java/java/security/spec/XECPrivateKeySpec.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2018, 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.security.spec;
+
+import java.util.Objects;
+
+/**
+ * A class representing elliptic curve private keys as defined in RFC 7748,
+ * including the curve and other algorithm parameters. The private key is
+ * represented as an encoded scalar value. The decoding procedure defined in
+ * the RFC includes an operation that forces certain bits of the key to either
+ * 1 or 0. This operation is known as "pruning" or "clamping" the private key.
+ * All arrays in this spec are unpruned, and implementations will need to prune
+ * the array before using it in any numerical operations.
+ *
+ * @since 11
+ */
+public class XECPrivateKeySpec implements KeySpec {
+
+    private final AlgorithmParameterSpec params;
+    private final byte[] scalar;
+
+    /**
+     * Construct a private key spec using the supplied parameters and
+     * encoded scalar value.
+     *
+     * @param params the algorithm parameters
+     * @param scalar the unpruned encoded scalar value. This array is copied
+     *               to protect against subsequent modification.
+     *
+     * @throws NullPointerException if {@code params} or {@code scalar}
+     *                              is null.
+     */
+    public XECPrivateKeySpec(AlgorithmParameterSpec params, byte[] scalar) {
+        Objects.requireNonNull(params, "params must not be null");
+        Objects.requireNonNull(scalar, "scalar must not be null");
+
+        this.params = params;
+        this.scalar = scalar.clone();
+    }
+
+    /**
+     * Get the algorithm parameters that define the curve and other settings.
+     *
+     * @return the algorithm parameters
+     */
+    public AlgorithmParameterSpec getParams() {
+        return params;
+    }
+
+    /**
+     * Get the scalar value encoded as an unpruned byte array. A new copy of
+     * the array is returned each time this method is called.
+     *
+     * @return the unpruned encoded scalar value
+     */
+    public byte[] getScalar() {
+        return scalar.clone();
+    }
+}
diff --git a/ojluni/src/main/java/java/security/spec/XECPublicKeySpec.java b/ojluni/src/main/java/java/security/spec/XECPublicKeySpec.java
new file mode 100644
index 0000000..162cf10
--- /dev/null
+++ b/ojluni/src/main/java/java/security/spec/XECPublicKeySpec.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2018, 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.security.spec;
+
+import java.math.BigInteger;
+import java.util.Objects;
+
+/**
+ * A class representing elliptic curve public keys as defined in RFC 7748,
+ * including the curve and other algorithm parameters. The public key is a
+ * particular point on the curve, which is represented using only its
+ * u-coordinate. A u-coordinate is an element of the field of integers modulo
+ * some value that is determined by the algorithm parameters. This field
+ * element is represented by a BigInteger which may hold any value. That is,
+ * the BigInteger is not restricted to the range of canonical field elements.
+ *
+ * @since 11
+ */
+public class XECPublicKeySpec implements KeySpec {
+
+    private final AlgorithmParameterSpec params;
+    private final BigInteger u;
+
+    /**
+     * Construct a public key spec using the supplied parameters and
+     * u coordinate.
+     *
+     * @param params the algorithm parameters
+     * @param u the u-coordinate of the point, represented using a BigInteger
+     *          which may hold any value
+     *
+     * @throws NullPointerException if {@code params} or {@code u}
+     *                              is null.
+     */
+    public XECPublicKeySpec(AlgorithmParameterSpec params, BigInteger u) {
+        Objects.requireNonNull(params, "params must not be null");
+        Objects.requireNonNull(u, "u must not be null");
+
+        this.params = params;
+        this.u = u;
+    }
+
+    /**
+     * Get the algorithm parameters that define the curve and other settings.
+     *
+     * @return the parameters
+     */
+    public AlgorithmParameterSpec getParams() {
+        return params;
+    }
+
+    /**
+     * Get the u coordinate of the point.
+     *
+     * @return the u-coordinate, represented using a BigInteger which may hold
+     *          any value
+     */
+    public BigInteger getU() {
+        return u;
+    }
+}
diff --git a/ojluni/src/test/java/lang/invoke/InvokeGenericTest.java b/ojluni/src/test/java/lang/invoke/InvokeGenericTest.java
new file mode 100644
index 0000000..ef201ca
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/InvokeGenericTest.java
@@ -0,0 +1,520 @@
+/*
+ * Copyright (c) 2009, 2013, 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.
+ */
+
+/* @test
+ * @summary unit tests for java.lang.invoke.MethodHandle.invoke
+ * @compile InvokeGenericTest.java
+ * @run testng/othervm test.java.lang.invoke.InvokeGenericTest
+ */
+
+package test.java.lang.invoke;
+
+import java.lang.invoke.*;
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+import java.lang.reflect.*;
+import java.util.*;
+import org.testng.*;
+import static org.testng.AssertJUnit.*;
+import org.testng.annotations.*;
+
+/**
+ *
+ * @author jrose
+ */
+@SuppressWarnings("cast")  // various casts help emphasize arguments to invokeExact
+public class InvokeGenericTest {
+    // How much output?
+    static int verbosity = 0;
+    static {
+        String vstr = System.getProperty("test.java.lang.invoke.InvokeGenericTest.verbosity");
+        if (vstr != null)  verbosity = Integer.parseInt(vstr);
+    }
+
+//    public static void main(String... av) throws Throwable {
+//        new InvokeGenericTest().testFirst();
+//    }
+
+    @Test
+    public void testFirst() throws Throwable {
+        verbosity += 9; try {
+            // left blank for debugging
+        } finally { printCounts(); verbosity -= 9; }
+    }
+
+    public InvokeGenericTest() {
+    }
+
+    String testName;
+    static int allPosTests, allNegTests;
+    int posTests, negTests;
+    @AfterMethod
+    public void printCounts() {
+        if (verbosity >= 2 && (posTests | negTests) != 0) {
+            System.out.println();
+            if (posTests != 0)  System.out.println("=== "+testName+": "+posTests+" positive test cases run");
+            if (negTests != 0)  System.out.println("=== "+testName+": "+negTests+" negative test cases run");
+            allPosTests += posTests;
+            allNegTests += negTests;
+            posTests = negTests = 0;
+        }
+    }
+    void countTest(boolean positive) {
+        if (positive) ++posTests;
+        else          ++negTests;
+    }
+    void countTest() { countTest(true); }
+    void startTest(String name) {
+        if (testName != null)  printCounts();
+        if (verbosity >= 1)
+            System.out.println("["+name+"]");
+        posTests = negTests = 0;
+        testName = name;
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        calledLog.clear();
+        calledLog.add(null);
+        nextArgVal = INITIAL_ARG_VAL;
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+        int posTests = allPosTests, negTests = allNegTests;
+        if (verbosity >= 2 && (posTests | negTests) != 0) {
+            System.out.println();
+            if (posTests != 0)  System.out.println("=== "+posTests+" total positive test cases");
+            if (negTests != 0)  System.out.println("=== "+negTests+" total negative test cases");
+        }
+    }
+
+    static List<Object> calledLog = new ArrayList<>();
+    static Object logEntry(String name, Object... args) {
+        return Arrays.asList(name, Arrays.asList(args));
+    }
+    static Object called(String name, Object... args) {
+        Object entry = logEntry(name, args);
+        calledLog.add(entry);
+        return entry;
+    }
+    static void assertCalled(String name, Object... args) {
+        Object expected = logEntry(name, args);
+        Object actual   = calledLog.get(calledLog.size() - 1);
+        if (expected.equals(actual) && verbosity < 9)  return;
+        System.out.println("assertCalled "+name+":");
+        System.out.println("expected:   "+expected);
+        System.out.println("actual:     "+actual);
+        System.out.println("ex. types:  "+getClasses(expected));
+        System.out.println("act. types: "+getClasses(actual));
+        assertEquals("previous method call", expected, actual);
+    }
+    static void printCalled(MethodHandle target, String name, Object... args) {
+        if (verbosity >= 3)
+            System.out.println("calling MH="+target+" to "+name+Arrays.toString(args));
+    }
+
+    static Object castToWrapper(Object value, Class<?> dst) {
+        Object wrap = null;
+        if (value instanceof Number)
+            wrap = castToWrapperOrNull(((Number)value).longValue(), dst);
+        if (value instanceof Character)
+            wrap = castToWrapperOrNull((char)(Character)value, dst);
+        if (wrap != null)  return wrap;
+        return dst.cast(value);
+    }
+
+    static Object castToWrapperOrNull(long value, Class<?> dst) {
+        if (dst == int.class || dst == Integer.class)
+            return (int)(value);
+        if (dst == long.class || dst == Long.class)
+            return (long)(value);
+        if (dst == char.class || dst == Character.class)
+            return (char)(value);
+        if (dst == short.class || dst == Short.class)
+            return (short)(value);
+        if (dst == float.class || dst == Float.class)
+            return (float)(value);
+        if (dst == double.class || dst == Double.class)
+            return (double)(value);
+        if (dst == byte.class || dst == Byte.class)
+            return (byte)(value);
+        if (dst == boolean.class || dst == boolean.class)
+            return ((value % 29) & 1) == 0;
+        return null;
+    }
+
+    static final int ONE_MILLION = (1000*1000),  // first int value
+                     TEN_BILLION = (10*1000*1000*1000),  // scale factor to reach upper 32 bits
+                     INITIAL_ARG_VAL = ONE_MILLION << 1;  // <<1 makes space for sign bit;
+    static long nextArgVal;
+    static long nextArg(boolean moreBits) {
+        long val = nextArgVal++;
+        long sign = -(val & 1); // alternate signs
+        val >>= 1;
+        if (moreBits)
+            // Guarantee some bits in the high word.
+            // In any case keep the decimal representation simple-looking,
+            // with lots of zeroes, so as not to make the printed decimal
+            // strings unnecessarily noisy.
+            val += (val % ONE_MILLION) * TEN_BILLION;
+        return val ^ sign;
+    }
+    static int nextArg() {
+        // Produce a 32-bit result something like ONE_MILLION+(smallint).
+        // Example: 1_000_042.
+        return (int) nextArg(false);
+    }
+    static long nextArg(Class<?> kind) {
+        if (kind == long.class   || kind == Long.class ||
+            kind == double.class || kind == Double.class)
+            // produce a 64-bit result something like
+            // ((TEN_BILLION+1) * (ONE_MILLION+(smallint)))
+            // Example: 10_000_420_001_000_042.
+            return nextArg(true);
+        return (long) nextArg();
+    }
+
+    static Object randomArg(Class<?> param) {
+        Object wrap = castToWrapperOrNull(nextArg(param), param);
+        if (wrap != null) {
+            return wrap;
+        }
+//        import sun.invoke.util.Wrapper;
+//        Wrapper wrap = Wrapper.forBasicType(dst);
+//        if (wrap == Wrapper.OBJECT && Wrapper.isWrapperType(dst))
+//            wrap = Wrapper.forWrapperType(dst);
+//        if (wrap != Wrapper.OBJECT)
+//            return wrap.wrap(nextArg++);
+        if (param.isInterface()) {
+            for (Class<?> c : param.getClasses()) {
+                if (param.isAssignableFrom(c) && !c.isInterface())
+                    { param = c; break; }
+            }
+        }
+        if (param.isInterface() || param.isAssignableFrom(String.class))
+            return "#"+nextArg();
+        else
+            try {
+                return param.newInstance();
+            } catch (InstantiationException | IllegalAccessException ex) {
+            }
+        return null;  // random class not Object, String, Integer, etc.
+    }
+    static Object[] randomArgs(Class<?>... params) {
+        Object[] args = new Object[params.length];
+        for (int i = 0; i < args.length; i++)
+            args[i] = randomArg(params[i]);
+        return args;
+    }
+    static Object[] randomArgs(int nargs, Class<?> param) {
+        Object[] args = new Object[nargs];
+        for (int i = 0; i < args.length; i++)
+            args[i] = randomArg(param);
+        return args;
+    }
+
+    static final Object ANON_OBJ = new Object();
+    static Object zeroArg(Class<?> param) {
+        Object x = castToWrapperOrNull(0L, param);
+        if (x != null)  return x;
+        if (param.isInterface() || param.isAssignableFrom(String.class))  return "\"\"";
+        if (param == Object.class)  return ANON_OBJ;
+        if (param.getComponentType() != null)  return Array.newInstance(param.getComponentType(), 0);
+        return null;
+    }
+    static Object[] zeroArgs(Class<?>... params) {
+        Object[] args = new Object[params.length];
+        for (int i = 0; i < args.length; i++)
+            args[i] = zeroArg(params[i]);
+        return args;
+    }
+    static Object[] zeroArgs(List<Class<?>> params) {
+        return zeroArgs(params.toArray(new Class<?>[0]));
+    }
+
+    @SafeVarargs @SuppressWarnings("varargs")
+    static <T, E extends T> T[] array(Class<T[]> atype, E... a) {
+        return Arrays.copyOf(a, a.length, atype);
+    }
+    @SafeVarargs @SuppressWarnings("varargs")
+    static <T> T[] cat(T[] a, T... b) {
+        int alen = a.length, blen = b.length;
+        if (blen == 0)  return a;
+        T[] c = Arrays.copyOf(a, alen + blen);
+        System.arraycopy(b, 0, c, alen, blen);
+        return c;
+    }
+    static Integer[] boxAll(int... vx) {
+        Integer[] res = new Integer[vx.length];
+        for (int i = 0; i < res.length; i++) {
+            res[i] = vx[i];
+        }
+        return res;
+    }
+    static Object getClasses(Object x) {
+        if (x == null)  return x;
+        if (x instanceof String)  return x;  // keep the name
+        if (x instanceof List) {
+            // recursively report classes of the list elements
+            Object[] xa = ((List)x).toArray();
+            for (int i = 0; i < xa.length; i++)
+                xa[i] = getClasses(xa[i]);
+            return Arrays.asList(xa);
+        }
+        return x.getClass().getSimpleName();
+    }
+
+    static MethodHandle changeArgTypes(MethodHandle target, Class<?> argType) {
+        return changeArgTypes(target, 0, 999, argType);
+    }
+    static MethodHandle changeArgTypes(MethodHandle target,
+            int beg, int end, Class<?> argType) {
+        MethodType targetType = target.type();
+        end = Math.min(end, targetType.parameterCount());
+        ArrayList<Class<?>> argTypes = new ArrayList<>(targetType.parameterList());
+        Collections.fill(argTypes.subList(beg, end), argType);
+        MethodType ttype2 = MethodType.methodType(targetType.returnType(), argTypes);
+        return target.asType(ttype2);
+    }
+
+    // This lookup is good for all members in and under InvokeGenericTest.
+    static final Lookup LOOKUP = MethodHandles.lookup();
+
+    Map<List<Class<?>>, MethodHandle> CALLABLES = new HashMap<>();
+    MethodHandle callable(List<Class<?>> params) {
+        MethodHandle mh = CALLABLES.get(params);
+        if (mh == null) {
+            mh = collector_MH.asType(methodType(Object.class, params));
+            CALLABLES.put(params, mh);
+        }
+        return mh;
+    }
+    MethodHandle callable(Class<?>... params) {
+        return callable(Arrays.asList(params));
+    }
+    private static Object collector(Object... args) {
+        return Arrays.asList(args);
+    }
+    private static final MethodHandle collector_MH;
+    static {
+        try {
+            collector_MH
+                = LOOKUP.findStatic(LOOKUP.lookupClass(),
+                                    "collector",
+                                    methodType(Object.class, Object[].class));
+        } catch (ReflectiveOperationException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Test
+    public void testSimple() throws Throwable {
+        startTest("testSimple");
+        countTest();
+        String[] args = { "one", "two" };
+        MethodHandle mh = callable(Object.class, String.class);
+        Object res; List<?> resl;
+        res = resl = (List<?>) mh.invoke((String)args[0], (Object)args[1]);
+        //System.out.println(res);
+        assertEquals(Arrays.asList(args), res);
+    }
+
+    @Test
+    public void testSimplePrims() throws Throwable {
+        startTest("testSimplePrims");
+        countTest();
+        int[] args = { 1, 2 };
+        MethodHandle mh = callable(Object.class, Object.class);
+        Object res; List<?> resl;
+        res = resl = (List<?>) mh.invoke(args[0], args[1]);
+        //System.out.println(res);
+        assertEquals(Arrays.toString(args), res.toString());
+    }
+
+    @Test
+    public void testAlternateName() throws Throwable {
+        startTest("testAlternateName");
+        countTest();
+        String[] args = { "one", "two" };
+        MethodHandle mh = callable(Object.class, String.class);
+        Object res; List<?> resl;
+        res = resl = (List<?>) mh.invoke((String)args[0], (Object)args[1]);
+        //System.out.println(res);
+        assertEquals(Arrays.asList(args), res);
+    }
+
+    @Test
+    public void testWrongArgumentCount() throws Throwable {
+        startTest("testWrongArgumentCount");
+        for (int i = 0; i <= 10; i++) {
+            testWrongArgumentCount(Collections.<Class<?>>nCopies(i, Integer.class));
+            if (i <= 4) {
+                testWrongArgumentCount(Collections.<Class<?>>nCopies(i, int.class));
+                testWrongArgumentCount(Collections.<Class<?>>nCopies(i, long.class));
+            }
+        }
+    }
+    public void testWrongArgumentCount(List<Class<?>> params) throws Throwable {
+        int max = params.size();
+        for (int i = 0; i < max; i++) {
+            List<Class<?>> params2 = params.subList(0, i);
+            for (int k = 0; k <= 2; k++) {
+                if (k == 1)  params  = methodType(Object.class,  params).generic().parameterList();
+                if (k == 2)  params2 = methodType(Object.class, params2).generic().parameterList();
+                testWrongArgumentCount(params, params2);
+                testWrongArgumentCount(params2, params);
+            }
+        }
+    }
+    public void testWrongArgumentCount(List<Class<?>> expect, List<Class<?>> observe) throws Throwable {
+        countTest(false);
+        if (expect.equals(observe))
+            assert(false);
+        MethodHandle target = callable(expect);
+        Object[] args = zeroArgs(observe);
+        Object junk;
+        try {
+            switch (args.length) {
+            case 0:
+                junk = target.invoke(); break;
+            case 1:
+                junk = target.invoke(args[0]); break;
+            case 2:
+                junk = target.invoke(args[0], args[1]); break;
+            case 3:
+                junk = target.invoke(args[0], args[1], args[2]); break;
+            case 4:
+                junk = target.invoke(args[0], args[1], args[2], args[3]); break;
+            default:
+                junk = target.invokeWithArguments(args); break;
+            }
+        } catch (WrongMethodTypeException ex) {
+            return;
+        } catch (Exception ex) {
+            throw new RuntimeException("wrong exception calling "+target+" on "+Arrays.asList(args), ex);
+        }
+        throw new RuntimeException("bad success calling "+target+" on "+Arrays.asList(args));
+    }
+
+    /** Make a list of all combinations of the given types, with the given arities.
+     *  A void return type is possible iff the first type is void.class.
+     */
+    static List<MethodType> allMethodTypes(int minargc, int maxargc, Class<?>... types) {
+        ArrayList<MethodType> result = new ArrayList<>();
+        if (types.length > 0) {
+            ArrayList<MethodType> argcTypes = new ArrayList<>();
+            // build arity-zero types first
+            for (Class<?> rtype : types) {
+                argcTypes.add(MethodType.methodType(rtype));
+            }
+            if (types[0] == void.class)
+                // void is not an argument type
+                types = Arrays.copyOfRange(types, 1, types.length);
+            for (int argc = 0; argc <= maxargc; argc++) {
+                if (argc >= minargc)
+                    result.addAll(argcTypes);
+                if (argc >= maxargc)
+                    break;
+                ArrayList<MethodType> prevTypes = argcTypes;
+                argcTypes = new ArrayList<>();
+                for (MethodType prevType : prevTypes) {
+                    for (Class<?> ptype : types) {
+                        argcTypes.add(prevType.insertParameterTypes(argc, ptype));
+                    }
+                }
+            }
+        }
+        return Collections.unmodifiableList(result);
+    }
+    static List<MethodType> allMethodTypes(int argc, Class<?>... types) {
+        return allMethodTypes(argc, argc, types);
+    }
+
+    MethodHandle toString_MH;
+
+    @Test
+    public void testReferenceConversions() throws Throwable {
+        startTest("testReferenceConversions");
+        toString_MH = LOOKUP.
+            findVirtual(Object.class, "toString", MethodType.methodType(String.class));
+        Object[] args = { "one", "two" };
+        for (MethodType type : allMethodTypes(2, Object.class, String.class, CharSequence.class)) {
+            testReferenceConversions(type, args);
+        }
+    }
+    public void testReferenceConversions(MethodType type, Object... args) throws Throwable {
+        countTest();
+        int nargs = args.length;
+        List<Object> argList = Arrays.asList(args);
+        String expectString = argList.toString();
+        if (verbosity > 3)  System.out.println("target type: "+type+expectString);
+        MethodHandle mh = callable(type.parameterList());
+        mh = MethodHandles.filterReturnValue(mh, toString_MH);
+        mh = mh.asType(type);
+        Object res = null;
+        if (nargs == 2) {
+            res = mh.invoke((Object)args[0], (Object)args[1]);
+            assertEquals(expectString, res);
+            res = mh.invoke((String)args[0], (Object)args[1]);
+            assertEquals(expectString, res);
+            res = mh.invoke((Object)args[0], (String)args[1]);
+            assertEquals(expectString, res);
+            res = mh.invoke((String)args[0], (String)args[1]);
+            assertEquals(expectString, res);
+            res = mh.invoke((String)args[0], (CharSequence)args[1]);
+            assertEquals(expectString, res);
+            res = mh.invoke((CharSequence)args[0], (Object)args[1]);
+            assertEquals(expectString, res);
+            res = (String) mh.invoke((Object)args[0], (Object)args[1]);
+            assertEquals(expectString, res);
+            res = (String) mh.invoke((String)args[0], (Object)args[1]);
+            assertEquals(expectString, res);
+            res = (CharSequence) mh.invoke((String)args[0], (Object)args[1]);
+            assertEquals(expectString, res);
+        } else {
+            assert(false);  // write this code
+        }
+        //System.out.println(res);
+    }
+
+
+    @Test
+    public void testBoxConversions() throws Throwable {
+        startTest("testBoxConversions");
+        countTest();
+        Object[] args = { 1, 2 };
+        MethodHandle mh = callable(Object.class, int.class);
+        Object res; List<?> resl; int resi;
+        res = resl = (List<?>) mh.invoke((int)args[0], (Object)args[1]);
+        //System.out.println(res);
+        assertEquals(Arrays.asList(args), res);
+        mh = MethodHandles.identity(int.class);
+        mh = MethodHandles.dropArguments(mh, 1, int.class);
+        res = resi = (int) mh.invoke((Object) args[0], (Object) args[1]);
+        assertEquals(args[0], res);
+        res = resi = (int) mh.invoke((int) args[0], (Object) args[1]);
+        assertEquals(args[0], res);
+    }
+
+}
diff --git a/ojluni/src/test/java/lang/invoke/InvokeWithArgumentsTest.java b/ojluni/src/test/java/lang/invoke/InvokeWithArgumentsTest.java
new file mode 100644
index 0000000..63a2101
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/InvokeWithArgumentsTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.
+ */
+
+/* @test
+ * @summary basic tests for MethodHandle.invokeWithArguments
+ * @run testng test.java.lang.invoke.InvokeWithArgumentsTest
+ */
+
+package test.java.lang.invoke;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.WrongMethodTypeException;
+
+import static java.lang.invoke.MethodType.methodType;
+
+public class InvokeWithArgumentsTest {
+    static final MethodHandles.Lookup L = MethodHandles.lookup();
+
+    static Object[] arity(Object o1, Object o2, Object... a) {
+        return a;
+    }
+
+    @Test
+    public void testArity() throws Throwable {
+        MethodHandle mh = L.findStatic(L.lookupClass(), "arity",
+                                       methodType(Object[].class, Object.class, Object.class, Object[].class));
+
+        try {
+            mh.invokeWithArguments("");
+            Assert.fail("WrongMethodTypeException expected");
+        } catch (WrongMethodTypeException e) {}
+    }
+
+    static Object[] passThrough(String... a) {
+        return a;
+    }
+
+    static Object[] pack(Object o, Object... a) {
+        return a;
+    }
+
+    @Test
+    public void testArrayNoPassThrough() throws Throwable {
+        String[] actual = {"A", "B"};
+
+        MethodHandle mh = L.findStatic(L.lookupClass(), "passThrough",
+                                       methodType(Object[].class, String[].class));
+
+        // Note: the actual array is not preserved, the elements will be
+        // unpacked and then packed into a new array before invoking the method
+        String[] expected = (String[]) mh.invokeWithArguments(actual);
+
+        Assert.assertTrue(actual != expected, "Array should not pass through");
+        Assert.assertEquals(actual, expected, "Array contents should be equal");
+    }
+
+    @Test
+    public void testArrayPack() throws Throwable {
+        String[] actual = new String[]{"A", "B"};
+
+        MethodHandle mh = L.findStatic(L.lookupClass(), "pack",
+                                       methodType(Object[].class, Object.class, Object[].class));
+
+        // Note: since String[] can be cast to Object, the actual String[] array
+        // will cast to Object become the single element of a new Object[] array
+        Object[] expected = (Object[]) mh.invokeWithArguments("", actual);
+
+        Assert.assertEquals(1, expected.length, "Array should contain just one element");
+        Assert.assertTrue(actual == expected[0], "Array should pass through");
+    }
+
+    static void intArray(int... a) {
+    }
+
+    @Test
+    public void testPrimitiveArrayWithNull() throws Throwable {
+        MethodHandle mh = L.findStatic(L.lookupClass(), "intArray",
+                                       methodType(void.class, int[].class));
+        try {
+            mh.invokeWithArguments(null, null);
+            Assert.fail("NullPointerException expected");
+        } catch (NullPointerException e) {}
+    }
+
+    @Test
+    public void testPrimitiveArrayWithRef() throws Throwable {
+        MethodHandle mh = L.findStatic(L.lookupClass(), "intArray",
+                                       methodType(void.class, int[].class));
+        try {
+            mh.invokeWithArguments("A", "B");
+            Assert.fail("ClassCastException expected");
+        } catch (ClassCastException e) {}
+    }
+
+
+    static void numberArray(Number... a) {
+    }
+
+    @Test
+    public void testRefArrayWithCast() throws Throwable {
+        MethodHandle mh = L.findStatic(L.lookupClass(), "numberArray",
+                                       methodType(void.class, Number[].class));
+        // All numbers, should not throw
+        mh.invokeWithArguments(1, 1.0, 1.0F, 1L);
+
+        try {
+            mh.invokeWithArguments("A");
+            Assert.fail("ClassCastException expected");
+        } catch (ClassCastException e) {}
+    }
+}
diff --git a/ojluni/src/test/java/lang/invoke/MethodHandlesTest.java b/ojluni/src/test/java/lang/invoke/MethodHandlesTest.java
new file mode 100644
index 0000000..314d2a2
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/MethodHandlesTest.java
@@ -0,0 +1,948 @@
+/*
+ * Copyright (c) 2009, 2018, 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.
+ */
+
+package test.java.lang.invoke;
+
+import org.junit.*;
+import test.java.lang.invoke.remote.RemoteExample;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author jrose
+ */
+public abstract class MethodHandlesTest {
+
+    static final Class<?> THIS_CLASS = MethodHandlesTest.class;
+    // How much output?
+    static int verbosity = 0;
+
+    static {
+        String vstr = System.getProperty(THIS_CLASS.getSimpleName()+".verbosity");
+        if (vstr == null)
+            vstr = System.getProperty(THIS_CLASS.getName()+".verbosity");
+        if (vstr != null)  verbosity = Integer.parseInt(vstr);
+    }
+
+    // Set this true during development if you want to fast-forward to
+    // a particular new, non-working test.  Tests which are known to
+    // work (or have recently worked) test this flag and return on true.
+    static final boolean CAN_SKIP_WORKING;
+
+    static {
+        String vstr = System.getProperty(THIS_CLASS.getSimpleName()+".CAN_SKIP_WORKING");
+        if (vstr == null)
+            vstr = System.getProperty(THIS_CLASS.getName()+".CAN_SKIP_WORKING");
+        CAN_SKIP_WORKING = Boolean.parseBoolean(vstr);
+    }
+
+    // Set 'true' to do about 15x fewer tests, especially those redundant with RicochetTest.
+    // This might be useful with -Xcomp stress tests that compile all method handles.
+    static boolean CAN_TEST_LIGHTLY = Boolean.getBoolean(THIS_CLASS.getName()+".CAN_TEST_LIGHTLY");
+
+    static final int MAX_ARG_INCREASE = 3;
+
+    String testName;
+    static int allPosTests, allNegTests;
+    int posTests, negTests;
+
+    @After
+    public void printCounts() {
+        if (verbosity >= 2 && (posTests | negTests) != 0) {
+            System.out.println();
+            if (posTests != 0)  System.out.println("=== "+testName+": "+posTests+" positive test cases run");
+            if (negTests != 0)  System.out.println("=== "+testName+": "+negTests+" negative test cases run");
+            allPosTests += posTests;
+            allNegTests += negTests;
+            posTests = negTests = 0;
+        }
+    }
+
+    void countTest(boolean positive) {
+        if (positive) ++posTests;
+        else          ++negTests;
+    }
+
+    void countTest() { countTest(true); }
+
+    void startTest(String name) {
+        if (testName != null)  printCounts();
+        if (verbosity >= 1)
+            System.out.println(name);
+        posTests = negTests = 0;
+        testName = name;
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        calledLog.clear();
+        calledLog.add(null);
+        nextArgVal = INITIAL_ARG_VAL;
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+        int posTests = allPosTests, negTests = allNegTests;
+        if (verbosity >= 0 && (posTests | negTests) != 0) {
+            System.out.println();
+            if (posTests != 0)  System.out.println("=== "+posTests+" total positive test cases");
+            if (negTests != 0)  System.out.println("=== "+negTests+" total negative test cases");
+        }
+    }
+
+    static List<Object> calledLog = new ArrayList<>();
+
+    static Object logEntry(String name, Object... args) {
+        return Arrays.asList(name, Arrays.asList(args));
+    }
+
+    public static Object called(String name, Object... args) {
+        Object entry = logEntry(name, args);
+        calledLog.add(entry);
+        return entry;
+    }
+
+    static void assertCalled(String name, Object... args) {
+        Object expected = logEntry(name, args);
+        Object actual   = calledLog.get(calledLog.size() - 1);
+        if (expected.equals(actual) && verbosity < 9)  return;
+        System.out.println("assertCalled "+name+":");
+        System.out.println("expected:   "+deepToString(expected));
+        System.out.println("actual:     "+actual);
+        System.out.println("ex. types:  "+getClasses(expected));
+        System.out.println("act. types: "+getClasses(actual));
+        assertEquals("previous method call", expected, actual);
+    }
+
+    static void printCalled(MethodHandle target, String name, Object... args) {
+        if (verbosity >= 3)
+            System.out.println("calling MH="+target+" to "+name+deepToString(args));
+    }
+
+    static String deepToString(Object x) {
+        if (x == null)  return "null";
+        if (x instanceof Collection)
+            x = ((Collection)x).toArray();
+        if (x instanceof Object[]) {
+            Object[] ax = (Object[]) x;
+            ax = Arrays.copyOf(ax, ax.length, Object[].class);
+            for (int i = 0; i < ax.length; i++)
+                ax[i] = deepToString(ax[i]);
+            x = Arrays.deepToString(ax);
+        }
+        if (x.getClass().isArray())
+            try {
+                x = Arrays.class.getMethod("toString", x.getClass()).invoke(null, x);
+            } catch (ReflectiveOperationException ex) { throw new Error(ex); }
+        assert(!(x instanceof Object[]));
+        return x.toString();
+    }
+
+    static Object castToWrapper(Object value, Class<?> dst) {
+        Object wrap = null;
+        if (value instanceof Number)
+            wrap = castToWrapperOrNull(((Number)value).longValue(), dst);
+        if (value instanceof Character)
+            wrap = castToWrapperOrNull((char)(Character)value, dst);
+        if (wrap != null)  return wrap;
+        return dst.cast(value);
+    }
+
+    @SuppressWarnings("cast")  // primitive cast to (long) is part of the pattern
+    static Object castToWrapperOrNull(long value, Class<?> dst) {
+        if (dst == int.class || dst == Integer.class)
+            return (int)(value);
+        if (dst == long.class || dst == Long.class)
+            return (long)(value);
+        if (dst == char.class || dst == Character.class)
+            return (char)(value);
+        if (dst == short.class || dst == Short.class)
+            return (short)(value);
+        if (dst == float.class || dst == Float.class)
+            return (float)(value);
+        if (dst == double.class || dst == Double.class)
+            return (double)(value);
+        if (dst == byte.class || dst == Byte.class)
+            return (byte)(value);
+        if (dst == boolean.class || dst == boolean.class)
+            return ((value % 29) & 1) == 0;
+        return null;
+    }
+
+    static final int ONE_MILLION = (1000*1000),  // first int value
+                     TEN_BILLION = (10*1000*1000*1000),  // scale factor to reach upper 32 bits
+                     INITIAL_ARG_VAL = ONE_MILLION << 1;  // <<1 makes space for sign bit;
+    static long nextArgVal;
+
+    static long nextArg(boolean moreBits) {
+        long val = nextArgVal++;
+        long sign = -(val & 1); // alternate signs
+        val >>= 1;
+        if (moreBits)
+            // Guarantee some bits in the high word.
+            // In any case keep the decimal representation simple-looking,
+            // with lots of zeroes, so as not to make the printed decimal
+            // strings unnecessarily noisy.
+            val += (val % ONE_MILLION) * TEN_BILLION;
+        return val ^ sign;
+    }
+
+    static int nextArg() {
+        // Produce a 32-bit result something like ONE_MILLION+(smallint).
+        // Example: 1_000_042.
+        return (int) nextArg(false);
+    }
+
+    static long nextArg(Class<?> kind) {
+        if (kind == long.class   || kind == Long.class ||
+            kind == double.class || kind == Double.class)
+            // produce a 64-bit result something like
+            // ((TEN_BILLION+1) * (ONE_MILLION+(smallint)))
+            // Example: 10_000_420_001_000_042.
+            return nextArg(true);
+        return (long) nextArg();
+    }
+
+    static Object randomArg(Class<?> param) {
+        Object wrap = castToWrapperOrNull(nextArg(param), param);
+        if (wrap != null) {
+            return wrap;
+        }
+        //import sun.invoke.util.Wrapper;
+        //Wrapper wrap = Wrapper.forBasicType(dst);
+        //if (wrap == Wrapper.OBJECT && Wrapper.isWrapperType(dst))
+        //   wrap = Wrapper.forWrapperType(dst);
+        //   if (wrap != Wrapper.OBJECT)
+        //       return wrap.wrap(nextArg++);
+        if (param.isInterface()) {
+            for (Class<?> c : param.getClasses()) {
+                if (param.isAssignableFrom(c) && !c.isInterface())
+                    { param = c; break; }
+            }
+        }
+        if (param.isArray()) {
+            Class<?> ctype = param.getComponentType();
+            Object arg = Array.newInstance(ctype, 2);
+            Array.set(arg, 0, randomArg(ctype));
+            return arg;
+        }
+        if (param.isInterface() && param.isAssignableFrom(List.class))
+            return Arrays.asList("#"+nextArg());
+        if (param.isInterface() || param.isAssignableFrom(String.class))
+            return "#"+nextArg();
+        else
+            try {
+                return param.newInstance();
+            } catch (InstantiationException | IllegalAccessException ex) {
+            }
+        return null;  // random class not Object, String, Integer, etc.
+    }
+
+    static Object[] randomArgs(Class<?>... params) {
+        Object[] args = new Object[params.length];
+        for (int i = 0; i < args.length; i++)
+            args[i] = randomArg(params[i]);
+        return args;
+    }
+
+    static Object[] randomArgs(int nargs, Class<?> param) {
+        Object[] args = new Object[nargs];
+        for (int i = 0; i < args.length; i++)
+            args[i] = randomArg(param);
+        return args;
+    }
+
+    static Object[] randomArgs(List<Class<?>> params) {
+        return randomArgs(params.toArray(new Class<?>[params.size()]));
+    }
+
+    @SafeVarargs @SuppressWarnings("varargs")
+    static <T, E extends T> T[] array(Class<T[]> atype, E... a) {
+        return Arrays.copyOf(a, a.length, atype);
+    }
+
+    @SafeVarargs @SuppressWarnings("varargs")
+    static <T> T[] cat(T[] a, T... b) {
+        int alen = a.length, blen = b.length;
+        if (blen == 0)  return a;
+        T[] c = Arrays.copyOf(a, alen + blen);
+        System.arraycopy(b, 0, c, alen, blen);
+        return c;
+    }
+
+    static Integer[] boxAll(int... vx) {
+        Integer[] res = new Integer[vx.length];
+        for (int i = 0; i < res.length; i++) {
+            res[i] = vx[i];
+        }
+        return res;
+    }
+
+    static Object getClasses(Object x) {
+        if (x == null)  return x;
+        if (x instanceof String)  return x;  // keep the name
+        if (x instanceof List) {
+            // recursively report classes of the list elements
+            Object[] xa = ((List)x).toArray();
+            for (int i = 0; i < xa.length; i++)
+                xa[i] = getClasses(xa[i]);
+            return Arrays.asList(xa);
+        }
+        return x.getClass().getSimpleName();
+    }
+
+    /** Return lambda(arg...[arity]) { new Object[]{ arg... } } */
+    static MethodHandle varargsList(int arity) {
+        return ValueConversions.varargsList(arity);
+    }
+
+    /** Return lambda(arg...[arity]) { Arrays.asList(arg...) } */
+    static MethodHandle varargsArray(int arity) {
+        return ValueConversions.varargsArray(arity);
+    }
+
+    static MethodHandle varargsArray(Class<?> arrayType, int arity) {
+        return ValueConversions.varargsArray(arrayType, arity);
+    }
+
+    /** Variation of varargsList, but with the given rtype. */
+    static MethodHandle varargsList(int arity, Class<?> rtype) {
+        MethodHandle list = varargsList(arity);
+        MethodType listType = list.type().changeReturnType(rtype);
+        if (List.class.isAssignableFrom(rtype) || rtype == void.class || rtype == Object.class) {
+            // OK
+        } else if (rtype.isAssignableFrom(String.class)) {
+            if (LIST_TO_STRING == null)
+                try {
+                    LIST_TO_STRING = PRIVATE.findStatic(PRIVATE.lookupClass(), "listToString",
+                                                        MethodType.methodType(String.class, List.class));
+                } catch (NoSuchMethodException | IllegalAccessException ex) { throw new RuntimeException(ex); }
+            list = MethodHandles.filterReturnValue(list, LIST_TO_STRING);
+        } else if (rtype.isPrimitive()) {
+            if (LIST_TO_INT == null)
+                try {
+                    LIST_TO_INT = PRIVATE.findStatic(PRIVATE.lookupClass(), "listToInt",
+                                                     MethodType.methodType(int.class, List.class));
+                } catch (NoSuchMethodException | IllegalAccessException ex) { throw new RuntimeException(ex); }
+            list = MethodHandles.filterReturnValue(list, LIST_TO_INT);
+            list = MethodHandles.explicitCastArguments(list, listType);
+        } else {
+            throw new RuntimeException("varargsList: "+rtype);
+        }
+        return list.asType(listType);
+    }
+
+    /** Variation of varargsList, but with the given ptypes and rtype. */
+    static MethodHandle varargsList(List<Class<?>> ptypes, Class<?> rtype) {
+        MethodHandle list = varargsList(ptypes.size(), rtype);
+        return list.asType(MethodType.methodType(rtype, ptypes));
+    }
+
+    private static MethodHandle LIST_TO_STRING, LIST_TO_INT;
+    private static String listToString(List<?> x) { return x.toString(); }
+    private static int listToInt(List<?> x) { return x.toString().hashCode(); }
+
+    static MethodHandle changeArgTypes(MethodHandle target, Class<?> argType) {
+        return changeArgTypes(target, 0, 999, argType);
+    }
+
+    static MethodHandle changeArgTypes(MethodHandle target,
+            int beg, int end, Class<?> argType) {
+        MethodType targetType = target.type();
+        end = Math.min(end, targetType.parameterCount());
+        ArrayList<Class<?>> argTypes = new ArrayList<>(targetType.parameterList());
+        Collections.fill(argTypes.subList(beg, end), argType);
+        MethodType ttype2 = MethodType.methodType(targetType.returnType(), argTypes);
+        return target.asType(ttype2);
+    }
+
+    static MethodHandle addTrailingArgs(MethodHandle target, int nargs, Class<?> argClass) {
+        int targetLen = target.type().parameterCount();
+        int extra = (nargs - targetLen);
+        if (extra <= 0)  return target;
+        List<Class<?>> fakeArgs = Collections.<Class<?>>nCopies(extra, argClass);
+        return MethodHandles.dropArguments(target, targetLen, fakeArgs);
+    }
+
+    // This lookup is good for all members in and under MethodHandlesTest.
+    static final Lookup PRIVATE = MethodHandles.lookup();
+    // This lookup is good for package-private members but not private ones.
+    static final Lookup PACKAGE = PackageSibling.lookup();
+    // This lookup is good for public members and protected members of PubExample
+    static final Lookup SUBCLASS = RemoteExample.lookup();
+    // This lookup is good only for public members in exported packages.
+    static final Lookup PUBLIC  = MethodHandles.publicLookup();
+
+    // Subject methods...
+    static class Example implements IntExample {
+        final String name;
+        public Example() { name = "Example#"+nextArg(); }
+        protected Example(String name) { this.name = name; }
+        @SuppressWarnings("LeakingThisInConstructor")
+        protected Example(int x) { this(); called("protected <init>", this, x); }
+        //Example(Void x) { does not exist; lookup elicts NoSuchMethodException }
+        @Override public String toString() { return name; }
+
+        public void            v0()     { called("v0", this); }
+        protected void         pro_v0() { called("pro_v0", this); }
+        void                   pkg_v0() { called("pkg_v0", this); }
+        private void           pri_v0() { called("pri_v0", this); }
+        public static void     s0()     { called("s0"); }
+        protected static void  pro_s0() { called("pro_s0"); }
+        static void            pkg_s0() { called("pkg_s0"); }
+        private static void    pri_s0() { called("pri_s0"); }
+
+        public Object          v1(Object x) { return called("v1", this, x); }
+        public Object          v2(Object x, Object y) { return called("v2", this, x, y); }
+        public Object          v2(Object x, int    y) { return called("v2", this, x, y); }
+        public Object          v2(int    x, Object y) { return called("v2", this, x, y); }
+        public Object          v2(int    x, int    y) { return called("v2", this, x, y); }
+        public static Object   s1(Object x) { return called("s1", x); }
+        public static Object   s2(int x)    { return called("s2", x); }
+        public static Object   s3(long x)   { return called("s3", x); }
+        public static Object   s4(int x, int y) { return called("s4", x, y); }
+        public static Object   s5(long x, int y) { return called("s5", x, y); }
+        public static Object   s6(int x, long y) { return called("s6", x, y); }
+        public static Object   s7(float x, double y) { return called("s7", x, y); }
+
+        // for testing findConstructor:
+        public Example(String x, int y) { this.name = x+y; called("Example.<init>", x, y); }
+        public Example(int x, String y) { this.name = x+y; called("Example.<init>", x, y); }
+        public Example(int x, int    y) { this.name = x+""+y; called("Example.<init>", x, y); }
+        public Example(int x, long   y) { this.name = x+""+y; called("Example.<init>", x, y); }
+        public Example(int x, float  y) { this.name = x+""+y; called("Example.<init>", x, y); }
+        public Example(int x, double y) { this.name = x+""+y; called("Example.<init>", x, y); }
+        public Example(int x, int    y, int z) { this.name = x+""+y+""+z; called("Example.<init>", x, y, z); }
+        public Example(int x, int    y, int z, int a) { this.name = x+""+y+""+z+""+a; called("Example.<init>", x, y, z, a); }
+
+        static final Lookup EXAMPLE = MethodHandles.lookup();  // for testing findSpecial
+    }
+
+    static final Lookup EXAMPLE = Example.EXAMPLE;
+    public static class PubExample extends Example {
+        public PubExample() { this("PubExample"); }
+        protected PubExample(String prefix) { super(prefix+"#"+nextArg()); }
+        protected void         pro_v0() { called("Pub/pro_v0", this); }
+        protected static void  pro_s0() { called("Pub/pro_s0"); }
+    }
+
+    static class SubExample extends Example {
+        @Override public void  v0()     { called("Sub/v0", this); }
+        @Override void         pkg_v0() { called("Sub/pkg_v0", this); }
+        @SuppressWarnings("LeakingThisInConstructor")
+        private      SubExample(int x)  { called("<init>", this, x); }
+        public SubExample() { super("SubExample#"+nextArg()); }
+    }
+
+    public static interface IntExample {
+        public void            v0();
+        public default void    vd() { called("vd", this); }
+        public static class Impl implements IntExample {
+            public void        v0()     { called("Int/v0", this); }
+            final String name;
+            public Impl() { name = "Impl#"+nextArg(); }
+            @Override public String toString() { return name; }
+        }
+    }
+
+    static interface SubIntExample extends IntExample { }
+
+    static final Object[][][] ACCESS_CASES = {
+        { { false, PUBLIC }, { false, SUBCLASS }, { false, PACKAGE }, { false, PRIVATE }, { false, EXAMPLE } }, //[0]: all false
+        { { false, PUBLIC }, { false, SUBCLASS }, { false, PACKAGE }, { true, PRIVATE }, { true, EXAMPLE } }, //[1]: only PRIVATE
+        { { false, PUBLIC }, { false, SUBCLASS }, { true, PACKAGE }, { true, PRIVATE }, { true, EXAMPLE } }, //[2]: PUBLIC false
+        { { false, PUBLIC }, { true, SUBCLASS }, { true, PACKAGE }, { true, PRIVATE }, { true, EXAMPLE } }, //[3]: subclass OK
+        { { true, PUBLIC }, { true, SUBCLASS }, { true, PACKAGE }, { true, PRIVATE }, { true, EXAMPLE } }, //[4]: all true
+    };
+
+    static Object[][] accessCases(Class<?> defc, String name, boolean isSpecial) {
+        Object[][] cases;
+        if (name.contains("pri_") || isSpecial) {
+            cases = ACCESS_CASES[1]; // PRIVATE only
+        } else if (name.contains("pkg_") || !Modifier.isPublic(defc.getModifiers())) {
+            cases = ACCESS_CASES[2]; // not PUBLIC
+        } else if (name.contains("pro_")) {
+            cases = ACCESS_CASES[3]; // PUBLIC class, protected member
+        } else {
+            assertTrue(name.indexOf('_') < 0 || name.contains("fin_"));
+            boolean pubc = Modifier.isPublic(defc.getModifiers());
+            if (pubc)
+                cases = ACCESS_CASES[4]; // all access levels
+            else
+                cases = ACCESS_CASES[2]; // PACKAGE but not PUBLIC
+        }
+        if (defc != Example.class && cases[cases.length-1][1] == EXAMPLE)
+            cases = Arrays.copyOfRange(cases, 0, cases.length-1);
+        return cases;
+    }
+
+    static Object[][] accessCases(Class<?> defc, String name) {
+        return accessCases(defc, name, false);
+    }
+
+    static Lookup maybeMoveIn(Lookup lookup, Class<?> defc) {
+        if (lookup == PUBLIC || lookup == SUBCLASS || lookup == PACKAGE)
+            // external views stay external
+            return lookup;
+        return lookup.in(defc);
+    }
+
+    /** Is findVirtual (etc.) of "&lt;init&lt;" supposed to elicit a NoSuchMethodException? */
+    static final boolean INIT_REF_CAUSES_NSME = true;
+
+    static void assertExceptionClass(Class<? extends Throwable> expected,
+                                     Throwable actual) {
+        if (expected.isInstance(actual))  return;
+        actual.printStackTrace();
+        assertEquals(expected, actual.getClass());
+    }
+
+    static final boolean DEBUG_METHOD_HANDLE_NAMES = Boolean.getBoolean("java.lang.invoke.MethodHandle.DEBUG_NAMES");
+
+    // rough check of name string
+    static void assertNameStringContains(MethodHandle x, String s) {
+        if (!DEBUG_METHOD_HANDLE_NAMES) {
+            // ignore s
+            assertEquals("MethodHandle"+x.type(), x.toString());
+            return;
+        }
+        if (x.toString().contains(s))  return;
+        assertEquals(s, x);
+    }
+
+    public static class HasFields {
+        boolean fZ = false;
+        byte fB = (byte)'B';
+        short fS = (short)'S';
+        char fC = 'C';
+        int fI = 'I';
+        long fJ = 'J';
+        float fF = 'F';
+        double fD = 'D';
+        static boolean sZ = true;
+        static byte sB = 1+(byte)'B';
+        static short sS = 1+(short)'S';
+        static char sC = 1+'C';
+        static int sI = 1+'I';
+        static long sJ = 1+'J';
+        static float sF = 1+'F';
+        static double sD = 1+'D';
+
+        Object fL = 'L';
+        String fR = "R";
+        static Object sL = 'M';
+        static String sR = "S";
+
+        static final Object[][] CASES;
+        static {
+            ArrayList<Object[]> cases = new ArrayList<>();
+            Object types[][] = {
+                {'L',Object.class}, {'R',String.class},
+                {'I',int.class}, {'J',long.class},
+                {'F',float.class}, {'D',double.class},
+                {'Z',boolean.class}, {'B',byte.class},
+                {'S',short.class}, {'C',char.class},
+            };
+            HasFields fields = new HasFields();
+            for (Object[] t : types) {
+                for (int kind = 0; kind <= 1; kind++) {
+                    boolean isStatic = (kind != 0);
+                    char btc = (Character)t[0];
+                    String name = (isStatic ? "s" : "f") + btc;
+                    Class<?> type = (Class<?>) t[1];
+                    Object value;
+                    Field field;
+                        try {
+                        field = HasFields.class.getDeclaredField(name);
+                    } catch (NoSuchFieldException | SecurityException ex) {
+                        throw new InternalError("no field HasFields."+name);
+                    }
+                    try {
+                        value = field.get(fields);
+                    } catch (IllegalArgumentException | IllegalAccessException ex) {
+                        throw new InternalError("cannot fetch field HasFields."+name);
+                    }
+                    if (type == float.class) {
+                        float v = 'F';
+                        if (isStatic)  v++;
+                        assertTrue(value.equals(v));
+                    }
+                    assertTrue(name.equals(field.getName()));
+                    assertTrue(type.equals(field.getType()));
+                    assertTrue(isStatic == (Modifier.isStatic(field.getModifiers())));
+                    cases.add(new Object[]{ field, value });
+                }
+            }
+            cases.add(new Object[]{ new Object[]{ false, HasFields.class, "bogus_fD", double.class }, Error.class });
+            cases.add(new Object[]{ new Object[]{ true,  HasFields.class, "bogus_sL", Object.class }, Error.class });
+            CASES = cases.toArray(new Object[0][]);
+        }
+    }
+
+    static final int TEST_UNREFLECT = 1, TEST_FIND_FIELD = 2, TEST_FIND_STATIC = 3, TEST_SETTER = 0x10, TEST_BOUND = 0x20, TEST_NPE = 0x40;
+
+    static boolean testModeMatches(int testMode, boolean isStatic) {
+        switch (testMode) {
+        case TEST_FIND_STATIC:          return isStatic;
+        case TEST_FIND_FIELD:           return !isStatic;
+        case TEST_UNREFLECT:            return true;  // unreflect matches both
+        }
+        throw new InternalError("testMode="+testMode);
+    }
+
+    static class Callee {
+        static Object id() { return called("id"); }
+        static Object id(Object x) { return called("id", x); }
+        static Object id(Object x, Object y) { return called("id", x, y); }
+        static Object id(Object x, Object y, Object z) { return called("id", x, y, z); }
+        static Object id(Object... vx) { return called("id", vx); }
+        static MethodHandle ofType(int n) {
+            return ofType(Object.class, n);
+        }
+        static MethodHandle ofType(Class<?> rtype, int n) {
+            if (n == -1)
+                return ofType(MethodType.methodType(rtype, Object[].class));
+            return ofType(MethodType.genericMethodType(n).changeReturnType(rtype));
+        }
+        static MethodHandle ofType(Class<?> rtype, Class<?>... ptypes) {
+            return ofType(MethodType.methodType(rtype, ptypes));
+        }
+        static MethodHandle ofType(MethodType type) {
+            Class<?> rtype = type.returnType();
+            String pfx = "";
+            if (rtype != Object.class)
+                pfx = rtype.getSimpleName().substring(0, 1).toLowerCase();
+            String name = pfx+"id";
+            try {
+                return PRIVATE.findStatic(Callee.class, name, type);
+            } catch (NoSuchMethodException | IllegalAccessException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+
+    static Object invokee(Object... args) {
+        return called("invokee", args).hashCode();
+    }
+
+    protected static final String MISSING_ARG = "missingArg";
+    protected static final String MISSING_ARG_2 = "missingArg#2";
+
+    static Object targetIfEquals() {
+        return called("targetIfEquals");
+    }
+
+    static Object fallbackIfNotEquals() {
+        return called("fallbackIfNotEquals");
+    }
+
+    static Object targetIfEquals(Object x) {
+        assertEquals(x, MISSING_ARG);
+        return called("targetIfEquals", x);
+    }
+
+    static Object fallbackIfNotEquals(Object x) {
+        assertFalse(x.toString(), x.equals(MISSING_ARG));
+        return called("fallbackIfNotEquals", x);
+    }
+
+    static Object targetIfEquals(Object x, Object y) {
+        assertEquals(x, y);
+        return called("targetIfEquals", x, y);
+    }
+
+    static Object fallbackIfNotEquals(Object x, Object y) {
+        assertFalse(x.toString(), x.equals(y));
+        return called("fallbackIfNotEquals", x, y);
+    }
+
+    static Object targetIfEquals(Object x, Object y, Object z) {
+        assertEquals(x, y);
+        return called("targetIfEquals", x, y, z);
+    }
+
+    static Object fallbackIfNotEquals(Object x, Object y, Object z) {
+        assertFalse(x.toString(), x.equals(y));
+        return called("fallbackIfNotEquals", x, y, z);
+    }
+
+    static boolean loopIntPred(int a) {
+        if (verbosity >= 5) {
+            System.out.println("int pred " + a + " -> " + (a < 7));
+        }
+        return a < 7;
+    }
+
+    static boolean loopDoublePred(int a, double b) {
+        if (verbosity >= 5) {
+            System.out.println("double pred (a=" + a + ") " + b + " -> " + (b > 0.5));
+        }
+        return b > 0.5;
+    }
+
+    static boolean loopStringPred(int a, double b, String c) {
+        if (verbosity >= 5) {
+            System.out.println("String pred (a=" + a + ",b=" + b + ") " + c + " -> " + (c.length() <= 9));
+        }
+        return c.length() <= 9;
+    }
+
+    static int loopIntStep(int a) {
+        if (verbosity >= 5) {
+            System.out.println("int step " + a + " -> " + (a + 1));
+        }
+        return a + 1;
+    }
+
+    static double loopDoubleStep(int a, double b) {
+        if (verbosity >= 5) {
+            System.out.println("double step (a=" + a + ") " + b + " -> " + (b / 2.0));
+        }
+        return b / 2.0;
+    }
+
+    static String loopStringStep(int a, double b, String c) {
+        if (verbosity >= 5) {
+            System.out.println("String step (a=" + a + ",b=" + b + ") " + c + " -> " + (c + a));
+        }
+        return c + a;
+    }
+
+    static void vtarget(String[] a) {
+        // naught, akin to identity
+    }
+
+    static void vtargetThrow(String[] a) throws Exception {
+        throw new Exception("thrown");
+    }
+
+    static void vcleanupPassThrough(Throwable t, String[] a) {
+        assertNull(t);
+        // naught, akin to identity
+    }
+
+    static void vcleanupAugment(Throwable t, String[] a) {
+        assertNull(t);
+        a[0] = "augmented";
+    }
+
+    static void vcleanupCatch(Throwable t, String[] a) {
+        assertNotNull(t);
+        a[0] = "caught";
+    }
+
+    static void vcleanupThrow(Throwable t, String[] a) throws Exception {
+        assertNotNull(t);
+        throw new Exception("rethrown");
+    }
+}
+// Local abbreviated copy of sun.invoke.util.ValueConversions
+// This guy tests access from outside the same package member, but inside
+// the package itself.
+class ValueConversions {
+    private static final Lookup IMPL_LOOKUP = MethodHandles.lookup();
+    private static final Object[] NO_ARGS_ARRAY = {};
+    private static Object[] makeArray(Object... args) { return args; }
+    private static Object[] array() { return NO_ARGS_ARRAY; }
+    private static Object[] array(Object a0)
+                { return makeArray(a0); }
+    private static Object[] array(Object a0, Object a1)
+                { return makeArray(a0, a1); }
+    private static Object[] array(Object a0, Object a1, Object a2)
+                { return makeArray(a0, a1, a2); }
+    private static Object[] array(Object a0, Object a1, Object a2, Object a3)
+                { return makeArray(a0, a1, a2, a3); }
+    private static Object[] array(Object a0, Object a1, Object a2, Object a3,
+                                  Object a4)
+                { return makeArray(a0, a1, a2, a3, a4); }
+    private static Object[] array(Object a0, Object a1, Object a2, Object a3,
+                                  Object a4, Object a5)
+                { return makeArray(a0, a1, a2, a3, a4, a5); }
+    private static Object[] array(Object a0, Object a1, Object a2, Object a3,
+                                  Object a4, Object a5, Object a6)
+                { return makeArray(a0, a1, a2, a3, a4, a5, a6); }
+    private static Object[] array(Object a0, Object a1, Object a2, Object a3,
+                                  Object a4, Object a5, Object a6, Object a7)
+                { return makeArray(a0, a1, a2, a3, a4, a5, a6, a7); }
+    private static Object[] array(Object a0, Object a1, Object a2, Object a3,
+                                  Object a4, Object a5, Object a6, Object a7,
+                                  Object a8)
+                { return makeArray(a0, a1, a2, a3, a4, a5, a6, a7, a8); }
+    private static Object[] array(Object a0, Object a1, Object a2, Object a3,
+                                  Object a4, Object a5, Object a6, Object a7,
+                                  Object a8, Object a9)
+                { return makeArray(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); }
+
+    static MethodHandle[] makeArrays() {
+        ArrayList<MethodHandle> arrays = new ArrayList<>();
+        MethodHandles.Lookup lookup = IMPL_LOOKUP;
+        for (;;) {
+            int nargs = arrays.size();
+            MethodType type = MethodType.genericMethodType(nargs).changeReturnType(Object[].class);
+            String name = "array";
+            MethodHandle array = null;
+            try {
+                array = lookup.findStatic(ValueConversions.class, name, type);
+            } catch (ReflectiveOperationException ex) {
+                // break from loop!
+            }
+            if (array == null)  break;
+            arrays.add(array);
+        }
+        assertTrue(arrays.size() == 11);  // current number of methods
+        return arrays.toArray(new MethodHandle[0]);
+    }
+
+    static final MethodHandle[] ARRAYS = makeArrays();
+
+    /** Return a method handle that takes the indicated number of Object
+     *  arguments and returns an Object array of them, as if for varargs.
+     */
+    public static MethodHandle varargsArray(int nargs) {
+        if (nargs < ARRAYS.length)
+            return ARRAYS[nargs];
+        return MethodHandles.identity(Object[].class).asCollector(Object[].class, nargs);
+    }
+
+    public static MethodHandle varargsArray(Class<?> arrayType, int nargs) {
+        Class<?> elemType = arrayType.getComponentType();
+        MethodType vaType = MethodType.methodType(arrayType, Collections.<Class<?>>nCopies(nargs, elemType));
+        MethodHandle mh = varargsArray(nargs);
+        if (arrayType != Object[].class)
+            mh = MethodHandles.filterReturnValue(mh, CHANGE_ARRAY_TYPE.bindTo(arrayType));
+        return mh.asType(vaType);
+    }
+
+    static Object changeArrayType(Class<?> arrayType, Object[] a) {
+        Class<?> elemType = arrayType.getComponentType();
+        if (!elemType.isPrimitive())
+            return Arrays.copyOf(a, a.length, arrayType.asSubclass(Object[].class));
+        Object b = java.lang.reflect.Array.newInstance(elemType, a.length);
+        for (int i = 0; i < a.length; i++)
+            java.lang.reflect.Array.set(b, i, a[i]);
+        return b;
+    }
+
+    private static final MethodHandle CHANGE_ARRAY_TYPE;
+    static {
+        try {
+            CHANGE_ARRAY_TYPE = IMPL_LOOKUP.findStatic(ValueConversions.class, "changeArrayType",
+                                                       MethodType.methodType(Object.class, Class.class, Object[].class));
+        } catch (NoSuchMethodException | IllegalAccessException ex) {
+            Error err = new InternalError("uncaught exception");
+            err.initCause(ex);
+            throw err;
+        }
+    }
+
+    private static final List<Object> NO_ARGS_LIST = Arrays.asList(NO_ARGS_ARRAY);
+    private static List<Object> makeList(Object... args) { return Arrays.asList(args); }
+    private static List<Object> list() { return NO_ARGS_LIST; }
+    private static List<Object> list(Object a0)
+                { return makeList(a0); }
+    private static List<Object> list(Object a0, Object a1)
+                { return makeList(a0, a1); }
+    private static List<Object> list(Object a0, Object a1, Object a2)
+                { return makeList(a0, a1, a2); }
+    private static List<Object> list(Object a0, Object a1, Object a2, Object a3)
+                { return makeList(a0, a1, a2, a3); }
+    private static List<Object> list(Object a0, Object a1, Object a2, Object a3,
+                                     Object a4)
+                { return makeList(a0, a1, a2, a3, a4); }
+    private static List<Object> list(Object a0, Object a1, Object a2, Object a3,
+                                     Object a4, Object a5)
+                { return makeList(a0, a1, a2, a3, a4, a5); }
+    private static List<Object> list(Object a0, Object a1, Object a2, Object a3,
+                                     Object a4, Object a5, Object a6)
+                { return makeList(a0, a1, a2, a3, a4, a5, a6); }
+    private static List<Object> list(Object a0, Object a1, Object a2, Object a3,
+                                     Object a4, Object a5, Object a6, Object a7)
+                { return makeList(a0, a1, a2, a3, a4, a5, a6, a7); }
+    private static List<Object> list(Object a0, Object a1, Object a2, Object a3,
+                                     Object a4, Object a5, Object a6, Object a7,
+                                     Object a8)
+                { return makeList(a0, a1, a2, a3, a4, a5, a6, a7, a8); }
+    private static List<Object> list(Object a0, Object a1, Object a2, Object a3,
+                                     Object a4, Object a5, Object a6, Object a7,
+                                     Object a8, Object a9)
+                { return makeList(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); }
+
+    static MethodHandle[] makeLists() {
+        ArrayList<MethodHandle> lists = new ArrayList<>();
+        MethodHandles.Lookup lookup = IMPL_LOOKUP;
+        for (;;) {
+            int nargs = lists.size();
+            MethodType type = MethodType.genericMethodType(nargs).changeReturnType(List.class);
+            String name = "list";
+            MethodHandle list = null;
+            try {
+                list = lookup.findStatic(ValueConversions.class, name, type);
+            } catch (ReflectiveOperationException ex) {
+                // break from loop!
+            }
+            if (list == null)  break;
+            lists.add(list);
+        }
+        assertTrue(lists.size() == 11);  // current number of methods
+        return lists.toArray(new MethodHandle[0]);
+    }
+
+    static final MethodHandle[] LISTS = makeLists();
+    static final MethodHandle AS_LIST;
+
+    static {
+        try {
+            AS_LIST = IMPL_LOOKUP.findStatic(Arrays.class, "asList", MethodType.methodType(List.class, Object[].class));
+        } catch (NoSuchMethodException | IllegalAccessException ex) { throw new RuntimeException(ex); }
+    }
+
+    /** Return a method handle that takes the indicated number of Object
+     *  arguments and returns List.
+     */
+    public static MethodHandle varargsList(int nargs) {
+        if (nargs < LISTS.length)
+            return LISTS[nargs];
+        return AS_LIST.asCollector(Object[].class, nargs);
+    }
+}
+// This guy tests access from outside the same package member, but inside
+// the package itself.
+class PackageSibling {
+    static Lookup lookup() {
+        return MethodHandles.lookup();
+    }
+}
diff --git a/ojluni/src/test/java/lang/invoke/MethodTypeTest.java b/ojluni/src/test/java/lang/invoke/MethodTypeTest.java
new file mode 100644
index 0000000..c2ae4e0
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/MethodTypeTest.java
@@ -0,0 +1,567 @@
+/*
+ * Copyright (c) 2008, 2013, 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.
+ */
+
+/* @test
+ * @summary unit tests for java.lang.invoke.MethodType
+ * @compile MethodTypeTest.java
+ * @run testng/othervm test.java.lang.invoke.MethodTypeTest
+ */
+
+package test.java.lang.invoke;
+
+import java.io.IOException;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+
+import java.util.*;
+import org.testng.*;
+import static org.testng.AssertJUnit.*;
+import org.testng.annotations.*;
+
+/**
+ *
+ * @author jrose
+ */
+public class MethodTypeTest {
+
+    private Class<?> rtype;
+    private Class<?>[] ptypes;
+    private MethodType mt_viS, mt_OO, mt_OO2, mt_vv, mt_Vv, mt_Ov;
+    private MethodType mt_iSI, mt_ISi, mt_ISI, mt_iSi;
+    private MethodType mt_viO, mt_iO2, mt_OOi, mt_iOi;
+    private MethodType mt_VIO, mt_IO2, mt_OOI, mt_IOI, mt_VIS;
+    private MethodType mt_vOiSzA, mt_OO99;
+    private MethodType[] GALLERY;
+    private Method compareTo;
+
+    @BeforeMethod
+    public void setUp() throws Exception {
+        rtype = void.class;
+        ptypes = new Class<?>[] { int.class, String.class };
+
+        mt_viS = MethodType.methodType(void.class, int.class, String.class);
+        mt_OO = MethodType.methodType(Object.class, Object.class);
+        mt_OO2 = MethodType.methodType(Object.class, Object.class, Object.class);
+        mt_vv = MethodType.methodType(void.class);
+        mt_Vv = MethodType.methodType(Void.class);
+        mt_Ov = MethodType.methodType(Object.class);
+        mt_iSI = MethodType.methodType(int.class, String.class, Integer.class);
+        mt_ISi = MethodType.methodType(Integer.class, String.class, int.class);
+        mt_ISI = MethodType.methodType(Integer.class, String.class, Integer.class);
+        mt_iSi = MethodType.methodType(int.class, String.class, int.class);
+
+        compareTo = String.class.getDeclaredMethod("compareTo", String.class);
+
+        mt_viO = MethodType.methodType(void.class, int.class, Object.class);
+        mt_iO2 = MethodType.methodType(int.class, Object.class, Object.class);
+        mt_OOi = MethodType.methodType(Object.class, Object.class, int.class);
+        mt_iOi = MethodType.methodType(int.class, Object.class, int.class);
+
+        mt_VIO = MethodType.methodType(Void.class, Integer.class, Object.class);
+        mt_IO2 = MethodType.methodType(Integer.class, Object.class, Object.class);
+        mt_OOI = MethodType.methodType(Object.class, Object.class, Integer.class);
+        mt_IOI = MethodType.methodType(Integer.class, Object.class, Integer.class);
+        mt_VIS = MethodType.methodType(Void.class, Integer.class, String.class);
+
+        mt_vOiSzA = MethodType.methodType(void.class, Object.class, int.class, String.class, boolean.class, Object[].class);
+        mt_OO99 = MethodType.genericMethodType(99);
+
+        GALLERY = new MethodType[] {
+            mt_viS, mt_OO, mt_OO2, mt_vv, mt_Vv, mt_Ov,
+            mt_iSI, mt_ISi, mt_ISI, mt_iSi,
+            mt_viO, mt_iO2, mt_OOi, mt_iOi,
+            mt_VIO, mt_IO2, mt_OOI, mt_IOI,
+            mt_VIS, mt_vOiSzA, mt_OO99
+        };
+    }
+
+    @AfterMethod
+    public void tearDown() throws Exception {
+    }
+
+    /** Make sure the method types are all distinct. */
+    @Test
+    public void testDistinct() {
+        List<MethodType> gallery2 = new ArrayList<>();
+        for (MethodType mt : GALLERY) {
+            assertFalse(mt.toString(), gallery2.contains(mt));
+            gallery2.add(mt);
+        }
+        // check self-equality also:
+        assertEquals(Arrays.asList(GALLERY), gallery2);
+    }
+
+    /**
+     * Test of make method, of class MethodType.
+     */
+    @Test
+    public void testMake_Class_ClassArr() {
+        System.out.println("make (from type array)");
+        MethodType result = MethodType.methodType(rtype, ptypes);
+        assertSame(mt_viS, result);
+    }
+
+    /**
+     * Test of make method, of class MethodType.
+     */
+    @Test
+    public void testMake_Class_List() {
+        System.out.println("make (from type list)");
+        MethodType result = MethodType.methodType(rtype, Arrays.asList(ptypes));
+        assertSame(mt_viS, result);
+    }
+
+    /**
+     * Test of make method, of class MethodType.
+     */
+    @Test
+    public void testMake_3args() {
+        System.out.println("make (from type with varargs)");
+        MethodType result = MethodType.methodType(rtype, ptypes[0], ptypes[1]);
+        assertSame(mt_viS, result);
+    }
+
+    /**
+     * Test of make method, of class MethodType.
+     */
+    @Test
+    public void testMake_Class() {
+        System.out.println("make (from single type)");
+        Class<?> rt = Integer.class;
+        MethodType expResult = MethodType.methodType(rt, new Class<?>[0]);
+        MethodType result = MethodType.methodType(rt);
+        assertSame(expResult, result);
+    }
+
+    @Test
+    public void testMakeGeneric() {
+        System.out.println("makeGeneric");
+        int objectArgCount = 2;
+        MethodType expResult = mt_OO2;
+        MethodType result = MethodType.genericMethodType(objectArgCount);
+        assertSame(expResult, result);
+    }
+
+    /**
+     * Test of make method, of class MethodType.
+     */
+    @Test
+    public void testMake_MethodType() {
+        System.out.println("make (from rtype, MethodType)");
+        MethodType expResult = mt_iO2;
+        MethodType result = MethodType.methodType(int.class, mt_IO2);
+        assertSame(expResult, result);
+    }
+
+    /**
+     * Test of make method, of class MethodType.
+     */
+    @Test
+    public void testMake_String_ClassLoader() {
+        System.out.println("make (from bytecode signature)");
+        ClassLoader loader = null;
+        MethodType[] instances = {mt_viS, mt_OO2, mt_vv, mt_Ov, mt_iSI, mt_ISi, mt_ISI, mt_iSi};
+        String obj = "Ljava/lang/Object;";
+        assertEquals(obj, concat(Object.class));
+        String[] expResults = {
+            "(ILjava/lang/String;)V",
+            concat("(", obj, 2, ")", Object.class),
+            "()V", "()"+obj,
+            concat("(", String.class, Integer.class, ")I"),
+            concat("(", String.class, "I)", Integer.class),
+            concat("(", String.class, Integer.class, ")", Integer.class),
+            concat("(", String.class, "I)I")
+        };
+        for (int i = 0; i < instances.length; i++) {
+            MethodType instance = instances[i];
+            String result = instance.toMethodDescriptorString();
+            assertEquals("#"+i, expResults[i], result);
+            MethodType parsed = MethodType.fromMethodDescriptorString(result, loader);
+            assertSame("--#"+i, instance, parsed);
+        }
+    }
+    private static String concat(Object... parts) {
+        StringBuilder sb = new StringBuilder();
+        Object prevPart = "";
+        for (Object part : parts) {
+            if (part instanceof Class) {
+                part = "L"+((Class)part).getName()+";";
+            }
+            if (part instanceof Integer) {
+                for (int n = (Integer) part; n > 1; n--)
+                    sb.append(prevPart);
+                part = "";
+            }
+            sb.append(part);
+            prevPart = part;
+        }
+        return sb.toString().replace('.', '/');
+    }
+
+    @Test
+    public void testHasPrimitives() {
+        System.out.println("hasPrimitives");
+        MethodType[] instances = {mt_viS, mt_OO2, mt_vv, mt_Ov, mt_iSI, mt_ISi, mt_ISI, mt_iSi};
+        boolean[] expResults =   {true,   false,  true,  false, true,   true,   false,  true};
+        for (int i = 0; i < instances.length; i++) {
+            boolean result = instances[i].hasPrimitives();
+            assertEquals("#"+i, expResults[i], result);
+        }
+    }
+
+    @Test
+    public void testHasWrappers() {
+        System.out.println("hasWrappers");
+        MethodType[] instances = {mt_viS, mt_OO2, mt_vv, mt_Ov, mt_iSI, mt_ISi, mt_ISI, mt_iSi};
+        boolean[] expResults =   {false,  false,  false, false, true,   true,   true,   false};
+        for (int i = 0; i < instances.length; i++) {
+            System.out.println("  hasWrappers "+instances[i]);
+            boolean result = instances[i].hasWrappers();
+            assertEquals("#"+i, expResults[i], result);
+        }
+    }
+
+    @Test
+    public void testErase() {
+        System.out.println("erase");
+        MethodType[] instances  = {mt_viS, mt_OO2, mt_vv, mt_Ov, mt_iSI, mt_ISi, mt_ISI, mt_iSi};
+        MethodType[] expResults = {mt_viO, mt_OO2, mt_vv, mt_Ov, mt_iO2, mt_OOi, mt_OO2, mt_iOi};
+        for (int i = 0; i < instances.length; i++) {
+            MethodType result = instances[i].erase();
+            assertSame("#"+i, expResults[i], result);
+        }
+    }
+
+    @Test
+    public void testGeneric() {
+        System.out.println("generic");
+        MethodType[] instances =  {mt_viS, mt_OO2, mt_vv, mt_Ov, mt_iSI, mt_ISi, mt_ISI, mt_iSi};
+        MethodType[] expResults = {mt_OO2, mt_OO2, mt_Ov, mt_Ov, mt_OO2, mt_OO2, mt_OO2, mt_OO2};
+        for (int i = 0; i < instances.length; i++) {
+            MethodType result = instances[i].generic();
+            assertSame("#"+i, expResults[i], result);
+        }
+    }
+
+    @Test
+    public void testWrap() {
+        System.out.println("wrap");
+        MethodType[] instances =  {mt_viS, mt_OO2, mt_vv, mt_Ov, mt_iSI, mt_ISi, mt_ISI, mt_iSi};
+        MethodType[] expResults = {mt_VIS, mt_OO2, mt_Vv, mt_Ov, mt_ISI, mt_ISI, mt_ISI, mt_ISI};
+        for (int i = 0; i < instances.length; i++) {
+            MethodType result = instances[i].wrap();
+            assertSame("#"+i, expResults[i], result);
+        }
+    }
+
+    @Test
+    public void testUnwrap() {
+        System.out.println("unwrap");
+        MethodType[] instances =  {mt_viS, mt_OO2, mt_vv, mt_Ov, mt_iSI, mt_ISi, mt_ISI, mt_iSi};
+        MethodType[] expResults = {mt_viS, mt_OO2, mt_vv, mt_Ov, mt_iSi, mt_iSi, mt_iSi, mt_iSi};
+        for (int i = 0; i < instances.length; i++) {
+            MethodType result = instances[i].unwrap();
+            assertSame("#"+i, expResults[i], result);
+        }
+    }
+
+    /**
+     * Test of parameterType method, of class MethodType.
+     */
+    @Test
+    public void testParameterType() {
+        System.out.println("parameterType");
+        for (int num = 0; num < ptypes.length; num++) {
+            MethodType instance = mt_viS;
+            Class<?> expResult = ptypes[num];
+            Class<?> result = instance.parameterType(num);
+            assertSame(expResult, result);
+        }
+    }
+
+    /**
+     * Test of parameterCount method, of class MethodType.
+     */
+    @Test
+    public void testParameterCount() {
+        System.out.println("parameterCount");
+        MethodType instance = mt_viS;
+        int expResult = 2;
+        int result = instance.parameterCount();
+        assertEquals(expResult, result);
+    }
+
+    /**
+     * Test of returnType method, of class MethodType.
+     */
+    @Test
+    public void testReturnType() {
+        System.out.println("returnType");
+        MethodType instance = mt_viS;
+        Class<?> expResult = void.class;
+        Class<?> result = instance.returnType();
+        assertSame(expResult, result);
+    }
+
+    /**
+     * Test of parameterList method, of class MethodType.
+     */
+    @Test
+    public void testParameterList() {
+        System.out.println("parameterList");
+        MethodType instance = mt_viS;
+        List<Class<?>> expResult = Arrays.asList(ptypes);
+        List<Class<?>> result = instance.parameterList();
+        assertEquals(expResult, result);
+    }
+
+    /**
+     * Test of parameterArray method, of class MethodType.
+     */
+    @Test
+    public void testParameterArray() {
+        System.out.println("parameterArray");
+        MethodType instance = mt_viS;
+        Class<?>[] expResult = ptypes;
+        Class<?>[] result = instance.parameterArray();
+        assertEquals(Arrays.asList(expResult), Arrays.asList(result));
+    }
+
+    /**
+     * Test of equals method, of class MethodType.
+     */
+    @Test
+    public void testEquals_Object() {
+        System.out.println("equals");
+        Object x = null;
+        MethodType instance = mt_viS;
+        boolean expResult = false;
+        boolean result = instance.equals(x);
+        assertEquals(expResult, result);
+    }
+
+    /**
+     * Test of equals method, of class MethodType.
+     */
+    @Test
+    public void testEquals_MethodType() {
+        System.out.println("equals");
+        MethodType that = mt_viS;
+        MethodType instance = mt_viS;
+        boolean expResult = true;
+        boolean result = instance.equals(that);
+        assertEquals(expResult, result);
+    }
+
+    /**
+     * Test of hashCode method, of class MethodType.
+     */
+    @Test
+    public void testHashCode() {
+        System.out.println("hashCode");
+        MethodType instance = mt_viS;
+        ArrayList<Class<?>> types = new ArrayList<>();
+        types.add(instance.returnType());
+        types.addAll(instance.parameterList());
+        int expResult = types.hashCode();
+        int result = instance.hashCode();
+        assertEquals(expResult, result);
+    }
+
+    /**
+     * Test of toString method, of class MethodType.
+     */
+    @Test
+    public void testToString() {
+        System.out.println("toString");
+        MethodType[] instances = {mt_viS, mt_OO2, mt_vv, mt_Ov, mt_iSI, mt_ISi, mt_ISI, mt_iSi};
+        //String expResult = "void[int, class java.lang.String]";
+        String[] expResults = {
+            "(int,String)void",
+            "(Object,Object)Object",
+            "()void",
+            "()Object",
+            "(String,Integer)int",
+            "(String,int)Integer",
+            "(String,Integer)Integer",
+            "(String,int)int"
+        };
+        for (int i = 0; i < instances.length; i++) {
+            MethodType instance = instances[i];
+            String result = instance.toString();
+            System.out.println("#"+i+":"+result);
+            assertEquals("#"+i, expResults[i], result);
+        }
+    }
+
+    private static byte[] writeSerial(Object x) throws java.io.IOException {
+        try (java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream();
+             java.io.ObjectOutputStream out = new java.io.ObjectOutputStream(bout)
+             ) {
+            out.writeObject(x);
+            out.flush();
+            return bout.toByteArray();
+        }
+    }
+    private static Object readSerial(byte[] wire) throws java.io.IOException, ClassNotFoundException {
+        try (java.io.ByteArrayInputStream bin = new java.io.ByteArrayInputStream(wire);
+             java.io.ObjectInputStream in = new java.io.ObjectInputStream(bin)) {
+            return in.readObject();
+        }
+    }
+    private static void testSerializedEquality(Object x) throws java.io.IOException, ClassNotFoundException {
+        if (x instanceof Object[])
+            x = Arrays.asList((Object[]) x);  // has proper equals method
+        byte[] wire = writeSerial(x);
+        Object y = readSerial(wire);
+        assertEquals(x, y);
+    }
+
+    /** Test (de-)serialization. */
+    @Test
+    public void testSerialization() throws Throwable {
+        System.out.println("serialization");
+        for (MethodType mt : GALLERY) {
+            testSerializedEquality(mt);
+        }
+        testSerializedEquality(GALLERY);
+
+        // Make a list of mixed objects:
+        List<Object> stuff = new ArrayList<>();
+        Collections.addAll(stuff, GALLERY);  // copy #1
+        Object[] triples = Arrays.copyOfRange(GALLERY, 0, GALLERY.length/2);
+        Collections.addAll(stuff, triples);  // copy #3 (partial)
+        for (MethodType mt : GALLERY) {
+            Collections.addAll(stuff, mt.parameterArray());
+        }
+        Collections.shuffle(stuff, new Random(292));
+        Collections.addAll(stuff, GALLERY);  // copy #2
+        testSerializedEquality(stuff);
+    }
+
+    /** Test serialization formats. */
+    @Test
+    public void testPortableSerialFormat() throws Throwable {
+        System.out.println("portable serial format");
+        boolean generateData = false;
+        //generateData = true;  // set this true to generate the following input data:
+        Object[][] cases = {
+            { mt_vv, new byte[] {  // ()void
+                    (byte)0xac, (byte)0xed, (byte)0x00, (byte)0x05, (byte)0x73, (byte)0x72, (byte)0x00, (byte)0x1b,
+                    (byte)0x6a, (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2e, (byte)0x6c, (byte)0x61, (byte)0x6e,
+                    (byte)0x67, (byte)0x2e, (byte)0x69, (byte)0x6e, (byte)0x76, (byte)0x6f, (byte)0x6b, (byte)0x65,
+                    (byte)0x2e, (byte)0x4d, (byte)0x65, (byte)0x74, (byte)0x68, (byte)0x6f, (byte)0x64, (byte)0x54,
+                    (byte)0x79, (byte)0x70, (byte)0x65, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
+                    (byte)0x00, (byte)0x01, (byte)0x24, (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x78, (byte)0x70,
+                    (byte)0x76, (byte)0x72, (byte)0x00, (byte)0x04, (byte)0x76, (byte)0x6f, (byte)0x69, (byte)0x64,
+                    (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
+                    (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x78, (byte)0x70, (byte)0x75, (byte)0x72, (byte)0x00,
+                    (byte)0x12, (byte)0x5b, (byte)0x4c, (byte)0x6a, (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2e,
+                    (byte)0x6c, (byte)0x61, (byte)0x6e, (byte)0x67, (byte)0x2e, (byte)0x43, (byte)0x6c, (byte)0x61,
+                    (byte)0x73, (byte)0x73, (byte)0x3b, (byte)0xab, (byte)0x16, (byte)0xd7, (byte)0xae, (byte)0xcb,
+                    (byte)0xcd, (byte)0x5a, (byte)0x99, (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x78, (byte)0x70,
+                    (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x78,
+                } },
+            { mt_OO, new byte[] {  // (Object)Object
+                    (byte)0xac, (byte)0xed, (byte)0x00, (byte)0x05, (byte)0x73, (byte)0x72, (byte)0x00, (byte)0x1b,
+                    (byte)0x6a, (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2e, (byte)0x6c, (byte)0x61, (byte)0x6e,
+                    (byte)0x67, (byte)0x2e, (byte)0x69, (byte)0x6e, (byte)0x76, (byte)0x6f, (byte)0x6b, (byte)0x65,
+                    (byte)0x2e, (byte)0x4d, (byte)0x65, (byte)0x74, (byte)0x68, (byte)0x6f, (byte)0x64, (byte)0x54,
+                    (byte)0x79, (byte)0x70, (byte)0x65, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
+                    (byte)0x00, (byte)0x01, (byte)0x24, (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x78, (byte)0x70,
+                    (byte)0x76, (byte)0x72, (byte)0x00, (byte)0x10, (byte)0x6a, (byte)0x61, (byte)0x76, (byte)0x61,
+                    (byte)0x2e, (byte)0x6c, (byte)0x61, (byte)0x6e, (byte)0x67, (byte)0x2e, (byte)0x4f, (byte)0x62,
+                    (byte)0x6a, (byte)0x65, (byte)0x63, (byte)0x74, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
+                    (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x78,
+                    (byte)0x70, (byte)0x75, (byte)0x72, (byte)0x00, (byte)0x12, (byte)0x5b, (byte)0x4c, (byte)0x6a,
+                    (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2e, (byte)0x6c, (byte)0x61, (byte)0x6e, (byte)0x67,
+                    (byte)0x2e, (byte)0x43, (byte)0x6c, (byte)0x61, (byte)0x73, (byte)0x73, (byte)0x3b, (byte)0xab,
+                    (byte)0x16, (byte)0xd7, (byte)0xae, (byte)0xcb, (byte)0xcd, (byte)0x5a, (byte)0x99, (byte)0x02,
+                    (byte)0x00, (byte)0x00, (byte)0x78, (byte)0x70, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01,
+                    (byte)0x71, (byte)0x00, (byte)0x7e, (byte)0x00, (byte)0x03, (byte)0x78,
+                } },
+            { mt_vOiSzA, new byte[] {  // (Object,int,String,boolean,Object[])void
+                    (byte)0xac, (byte)0xed, (byte)0x00, (byte)0x05, (byte)0x73, (byte)0x72, (byte)0x00, (byte)0x1b,
+                    (byte)0x6a, (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2e, (byte)0x6c, (byte)0x61, (byte)0x6e,
+                    (byte)0x67, (byte)0x2e, (byte)0x69, (byte)0x6e, (byte)0x76, (byte)0x6f, (byte)0x6b, (byte)0x65,
+                    (byte)0x2e, (byte)0x4d, (byte)0x65, (byte)0x74, (byte)0x68, (byte)0x6f, (byte)0x64, (byte)0x54,
+                    (byte)0x79, (byte)0x70, (byte)0x65, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
+                    (byte)0x00, (byte)0x01, (byte)0x24, (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x78, (byte)0x70,
+                    (byte)0x76, (byte)0x72, (byte)0x00, (byte)0x04, (byte)0x76, (byte)0x6f, (byte)0x69, (byte)0x64,
+                    (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
+                    (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x78, (byte)0x70, (byte)0x75, (byte)0x72, (byte)0x00,
+                    (byte)0x12, (byte)0x5b, (byte)0x4c, (byte)0x6a, (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2e,
+                    (byte)0x6c, (byte)0x61, (byte)0x6e, (byte)0x67, (byte)0x2e, (byte)0x43, (byte)0x6c, (byte)0x61,
+                    (byte)0x73, (byte)0x73, (byte)0x3b, (byte)0xab, (byte)0x16, (byte)0xd7, (byte)0xae, (byte)0xcb,
+                    (byte)0xcd, (byte)0x5a, (byte)0x99, (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x78, (byte)0x70,
+                    (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x05, (byte)0x76, (byte)0x72, (byte)0x00, (byte)0x10,
+                    (byte)0x6a, (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2e, (byte)0x6c, (byte)0x61, (byte)0x6e,
+                    (byte)0x67, (byte)0x2e, (byte)0x4f, (byte)0x62, (byte)0x6a, (byte)0x65, (byte)0x63, (byte)0x74,
+                    (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
+                    (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x78, (byte)0x70, (byte)0x76, (byte)0x72, (byte)0x00,
+                    (byte)0x03, (byte)0x69, (byte)0x6e, (byte)0x74, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
+                    (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x78,
+                    (byte)0x70, (byte)0x76, (byte)0x72, (byte)0x00, (byte)0x10, (byte)0x6a, (byte)0x61, (byte)0x76,
+                    (byte)0x61, (byte)0x2e, (byte)0x6c, (byte)0x61, (byte)0x6e, (byte)0x67, (byte)0x2e, (byte)0x53,
+                    (byte)0x74, (byte)0x72, (byte)0x69, (byte)0x6e, (byte)0x67, (byte)0xa0, (byte)0xf0, (byte)0xa4,
+                    (byte)0x38, (byte)0x7a, (byte)0x3b, (byte)0xb3, (byte)0x42, (byte)0x02, (byte)0x00, (byte)0x00,
+                    (byte)0x78, (byte)0x70, (byte)0x76, (byte)0x72, (byte)0x00, (byte)0x07, (byte)0x62, (byte)0x6f,
+                    (byte)0x6f, (byte)0x6c, (byte)0x65, (byte)0x61, (byte)0x6e, (byte)0x00, (byte)0x00, (byte)0x00,
+                    (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
+                    (byte)0x78, (byte)0x70, (byte)0x76, (byte)0x72, (byte)0x00, (byte)0x13, (byte)0x5b, (byte)0x4c,
+                    (byte)0x6a, (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2e, (byte)0x6c, (byte)0x61, (byte)0x6e,
+                    (byte)0x67, (byte)0x2e, (byte)0x4f, (byte)0x62, (byte)0x6a, (byte)0x65, (byte)0x63, (byte)0x74,
+                    (byte)0x3b, (byte)0x90, (byte)0xce, (byte)0x58, (byte)0x9f, (byte)0x10, (byte)0x73, (byte)0x29,
+                    (byte)0x6c, (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x78, (byte)0x70, (byte)0x78,
+                } },
+        };
+        for (Object[] c : cases) {
+            MethodType mt = (MethodType) c[0];
+            System.out.println("deserialize "+mt);
+            byte[] wire = (byte[]) c[1];
+            if (generateData) {
+                System.out.println("<generateData>");
+                wire = writeSerial(mt);
+                final String INDENT = "                ";
+                System.out.print("{  // "+mt);
+                for (int i = 0; i < wire.length; i++) {
+                    if (i % 8 == 0) { System.out.println(); System.out.print(INDENT+"   "); }
+                    String hex = Integer.toHexString(wire[i] & 0xFF);
+                    if (hex.length() == 1)  hex = "0"+hex;
+                    System.out.print(" (byte)0x"+hex+",");
+                }
+                System.out.println();
+                System.out.println(INDENT+"}");
+                System.out.println("</generateData>");
+                System.out.flush();
+            }
+            Object decode;
+            try {
+                decode = readSerial(wire);
+            } catch (IOException | ClassNotFoundException ex) {
+                decode = ex;  // oops!
+            }
+            assertEquals(mt, decode);
+        }
+    }
+}
diff --git a/ojluni/src/test/java/lang/invoke/RicochetTest.java b/ojluni/src/test/java/lang/invoke/RicochetTest.java
new file mode 100644
index 0000000..943f17e
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/RicochetTest.java
@@ -0,0 +1,699 @@
+/*
+ * Copyright (c) 2011, 2013, 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.
+ */
+
+/* @test
+ * @summary unit tests for recursive method handles
+ * @run junit/othervm/timeout=3600 -XX:+IgnoreUnrecognizedVMOptions -XX:-VerifyDependencies -DRicochetTest.MAX_ARITY=10 test.java.lang.invoke.RicochetTest
+ */
+/*
+ * @ignore The following test creates an unreasonable number of adapters in -Xcomp mode (7049122)
+ * @run junit/othervm -DRicochetTest.MAX_ARITY=255 test.java.lang.invoke.RicochetTest
+ */
+
+package test.java.lang.invoke;
+
+import java.lang.invoke.*;
+import java.util.*;
+import org.junit.*;
+import static java.lang.invoke.MethodType.*;
+import static java.lang.invoke.MethodHandles.*;
+import static org.junit.Assert.*;
+
+
+/**
+ *
+ * @author jrose
+ */
+public class RicochetTest {
+    private static final Class<?> CLASS = RicochetTest.class;
+    private static final int MAX_ARITY = Integer.getInteger(CLASS.getSimpleName()+".MAX_ARITY", 40);
+
+    public static void main(String... av) throws Throwable {
+        RicochetTest test = new RicochetTest();
+        if (av.length > 0)  test.testOnly = Arrays.asList(av).toString();
+        if (REPEAT == 1 || test.testOnly != null) {
+            test.testAll();
+            if (test.testOnlyTests == null)  throw new RuntimeException("no matching test: "+test.testOnly);
+        } else if (REPEAT == 0) {
+            org.junit.runner.JUnitCore.runClasses(RicochetTest.class);
+        } else {
+            verbose(1, "REPEAT="+REPEAT);
+            for (int i = 0; i < REPEAT; i++) {
+                test.testRepetition = (i+1);
+                verbose(0, "[#"+test.testRepetition+"]");
+                test.testAll();
+            }
+        }
+    }
+    int testRepetition;
+
+    public void testAll() throws Throwable {
+        testNull();
+        testBoxInteger();
+        testFilterReturnValue();
+        testFilterObject();
+        testBoxLong();
+        testFilterInteger();
+        testIntSpreads();
+        testByteSpreads();
+        testLongSpreads();
+        testIntCollects();
+        testReturns();
+        testRecursion();
+    }
+
+    @Test
+    public void testNull() throws Throwable {
+        if (testRepetition > (1+REPEAT/100))  return;  // trivial test
+        if (!startTest("testNull"))  return;
+        assertEquals(opI(37), opI.invokeWithArguments(37));
+        assertEqualFunction(opI, opI);
+    }
+
+    @Test
+    public void testBoxInteger() throws Throwable {
+        if (!startTest("testBoxInteger"))  return;
+        assertEqualFunction(opI, opI.asType(opL_I.type()).asType(opI.type()));
+    }
+
+    @Test
+    public void testFilterReturnValue() throws Throwable {
+        if (!startTest("testFilterReturnValue"))  return;
+        int[] ints = { 12, 23, 34, 45, 56, 67, 78, 89 };
+        Object res = list8ints.invokeExact(ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7]);
+        assertEquals(Arrays.toString(ints), res.toString());
+        MethodHandle idreturn = filterReturnValue(list8ints, identity(Object.class));
+        res = idreturn.invokeExact(ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7]);
+        assertEquals(Arrays.toString(ints), res.toString());
+        MethodHandle add0 = addL.bindTo(0);
+        assertEqualFunction(filterReturnValue(opL2, add0), opL2);
+    }
+
+    @Test
+    public void testFilterObject() throws Throwable {
+        if (!startTest("testFilterObject"))  return;
+        MethodHandle add0 = addL.bindTo(0);
+        assertEqualFunction(sequence(opL2, add0), opL2);
+        int bump13 = -13;  // value near 20 works as long as test values are near [-80..80]
+        MethodHandle add13   = addL.bindTo(bump13);
+        MethodHandle add13_0 = addL.bindTo(opI2(bump13, 0));
+        MethodHandle add13_1 = addL.bindTo(opI2(0, bump13));
+        assertEqualFunction(sequence(opL2, add13_0),
+                            filterArguments(opL2, 0, add13));
+        assertEqualFunction(sequence(opL2, add13_1),
+                            filterArguments(opL2, 1, add13));
+        System.out.println("[testFilterObject done]");
+    }
+
+    @Test
+    public void testBoxLong() throws Throwable {
+        if (!startTest("testBoxLong"))  return;
+        assertEqualFunction(opJ, opJ.asType(opL_J.type()).asType(opJ.type()));
+    }
+
+    @Test
+    public void testFilterInteger() throws Throwable {
+        if (!startTest("testFilterInteger"))  return;
+        assertEqualFunction(opI, sequence(convI_L, opL_I));
+    }
+
+    @Test
+    public void testIntSpreads() throws Throwable {
+        if (!startTest("testIntSpreads"))  return;
+        MethodHandle id = identity(int[].class);
+        final int MAX = MAX_ARITY-2;  // 253+1 would cause parameter overflow with 'this' added
+        for (int nargs = 0; nargs <= MAX; nargs++) {
+            if (nargs > 30 && nargs < MAX-20)  nargs += 10;
+            int[] args = new int[nargs];
+            for (int j = 0; j < args.length; j++)  args[j] = j + 11;
+            //System.out.println("testIntSpreads "+Arrays.toString(args));
+            int[] args1 = (int[]) id.invokeExact(args);
+            assertArrayEquals(args, args1);
+            MethodHandle coll = id.asCollector(int[].class, nargs);
+            int[] args2 = args;
+            switch (nargs) {
+                case 0:  args2 = (int[]) coll.invokeExact(); break;
+                case 1:  args2 = (int[]) coll.invokeExact(args[0]); break;
+                case 2:  args2 = (int[]) coll.invokeExact(args[0], args[1]); break;
+                case 3:  args2 = (int[]) coll.invokeExact(args[0], args[1], args[2]); break;
+                case 4:  args2 = (int[]) coll.invokeExact(args[0], args[1], args[2], args[3]); break;
+                case 5:  args2 = (int[]) coll.invokeExact(args[0], args[1], args[2], args[3], args[4]); break;
+            }
+            assertArrayEquals(args, args2);
+            MethodHandle mh = coll.asSpreader(int[].class, nargs);
+            int[] args3 = (int[]) mh.invokeExact(args);
+            assertArrayEquals(args, args3);
+        }
+    }
+
+    @Test
+    public void testByteSpreads() throws Throwable {
+        if (!startTest("testByteSpreads"))  return;
+        MethodHandle id = identity(byte[].class);
+        final int MAX = MAX_ARITY-2;  // 253+1 would cause parameter overflow with 'this' added
+        for (int nargs = 0; nargs <= MAX; nargs++) {
+            if (nargs > 30 && nargs < MAX-20)  nargs += 10;
+            byte[] args = new byte[nargs];
+            for (int j = 0; j < args.length; j++)  args[j] = (byte)(j + 11);
+            //System.out.println("testByteSpreads "+Arrays.toString(args));
+            byte[] args1 = (byte[]) id.invokeExact(args);
+            assertArrayEquals(args, args1);
+            MethodHandle coll = id.asCollector(byte[].class, nargs);
+            byte[] args2 = args;
+            switch (nargs) {
+                case 0:  args2 = (byte[]) coll.invokeExact(); break;
+                case 1:  args2 = (byte[]) coll.invokeExact(args[0]); break;
+                case 2:  args2 = (byte[]) coll.invokeExact(args[0], args[1]); break;
+                case 3:  args2 = (byte[]) coll.invokeExact(args[0], args[1], args[2]); break;
+                case 4:  args2 = (byte[]) coll.invokeExact(args[0], args[1], args[2], args[3]); break;
+                case 5:  args2 = (byte[]) coll.invokeExact(args[0], args[1], args[2], args[3], args[4]); break;
+            }
+            assertArrayEquals(args, args2);
+            MethodHandle mh = coll.asSpreader(byte[].class, nargs);
+            byte[] args3 = (byte[]) mh.invokeExact(args);
+            assertArrayEquals(args, args3);
+        }
+    }
+
+    @Test
+    public void testLongSpreads() throws Throwable {
+        if (!startTest("testLongSpreads"))  return;
+        MethodHandle id = identity(long[].class);
+        final int MAX = (MAX_ARITY - 2) / 2;  // 253/2+1 would cause parameter overflow with 'this' added
+        for (int nargs = 0; nargs <= MAX; nargs++) {
+            if (nargs > 30 && nargs < MAX-20)  nargs += 10;
+            long[] args = new long[nargs];
+            for (int j = 0; j < args.length; j++)  args[j] = (long)(j + 11);
+            //System.out.println("testLongSpreads "+Arrays.toString(args));
+            long[] args1 = (long[]) id.invokeExact(args);
+            assertArrayEquals(args, args1);
+            MethodHandle coll = id.asCollector(long[].class, nargs);
+            long[] args2 = args;
+            switch (nargs) {
+                case 0:  args2 = (long[]) coll.invokeExact(); break;
+                case 1:  args2 = (long[]) coll.invokeExact(args[0]); break;
+                case 2:  args2 = (long[]) coll.invokeExact(args[0], args[1]); break;
+                case 3:  args2 = (long[]) coll.invokeExact(args[0], args[1], args[2]); break;
+                case 4:  args2 = (long[]) coll.invokeExact(args[0], args[1], args[2], args[3]); break;
+                case 5:  args2 = (long[]) coll.invokeExact(args[0], args[1], args[2], args[3], args[4]); break;
+            }
+            assertArrayEquals(args, args2);
+            MethodHandle mh = coll.asSpreader(long[].class, nargs);
+            long[] args3 = (long[]) mh.invokeExact(args);
+            assertArrayEquals(args, args3);
+        }
+    }
+
+    @Test
+    public void testIntCollects() throws Throwable {
+        if (!startTest("testIntCollects"))  return;
+        for (MethodHandle lister : INT_LISTERS) {
+            int outputs = lister.type().parameterCount();
+            for (int collects = 0; collects <= Math.min(outputs, INT_COLLECTORS.length-1); collects++) {
+                int inputs = outputs - 1 + collects;
+                if (inputs < 0)  continue;
+                for (int pos = 0; pos + collects <= inputs; pos++) {
+                    MethodHandle collector = INT_COLLECTORS[collects];
+                    int[] args = new int[inputs];
+                    int ap = 0, arg = 31;
+                    for (int i = 0; i < pos; i++)
+                        args[ap++] = arg++ + 0;
+                    for (int i = 0; i < collects; i++)
+                        args[ap++] = arg++ + 10;
+                    while (ap < args.length)
+                        args[ap++] = arg++ + 20;
+                    // calculate piecemeal:
+                    //System.out.println("testIntCollects "+Arrays.asList(lister, pos, collector)+" on "+Arrays.toString(args));
+                    int[] collargs = Arrays.copyOfRange(args, pos, pos+collects);
+                    int coll = (int) collector.asSpreader(int[].class, collargs.length).invokeExact(collargs);
+                    int[] listargs = Arrays.copyOfRange(args, 0, outputs);
+                    System.arraycopy(args, pos+collects, listargs, pos+1, outputs - (pos+1));
+                    listargs[pos] = coll;
+                    //System.out.println("  coll="+coll+" listargs="+Arrays.toString(listargs));
+                    Object expect = lister.asSpreader(int[].class, listargs.length).invokeExact(listargs);
+                    //System.out.println("  expect="+expect);
+
+                    // now use the combined MH, and test the output:
+                    MethodHandle mh = collectArguments(lister, pos, int[].class, INT_COLLECTORS[collects]);
+                    if (mh == null)  continue;  // no infix collection, yet
+                    assert(mh.type().parameterCount() == inputs);
+                    Object observe = mh.asSpreader(int[].class, args.length).invokeExact(args);
+                    assertEquals(expect, observe);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testByteCollects() throws Throwable {
+        if (!startTest("testByteCollects"))  return;
+        for (MethodHandle lister : BYTE_LISTERS) {
+            int outputs = lister.type().parameterCount();
+            for (int collects = 0; collects <= Math.min(outputs, BYTE_COLLECTORS.length-1); collects++) {
+                int inputs = outputs - 1 + collects;
+                if (inputs < 0)  continue;
+                for (int pos = 0; pos + collects <= inputs; pos++) {
+                    MethodHandle collector = BYTE_COLLECTORS[collects];
+                    byte[] args = new byte[inputs];
+                    int ap = 0, arg = 31;
+                    for (int i = 0; i < pos; i++)
+                        args[ap++] = (byte)(arg++ + 0);
+                    for (int i = 0; i < collects; i++)
+                        args[ap++] = (byte)(arg++ + 10);
+                    while (ap < args.length)
+                        args[ap++] = (byte)(arg++ + 20);
+                    // calculate piecemeal:
+                    //System.out.println("testIntCollects "+Arrays.asList(lister, pos, collector)+" on "+Arrays.toString(args));
+                    byte[] collargs = Arrays.copyOfRange(args, pos, pos+collects);
+                    byte coll = (byte) collector.asSpreader(byte[].class, collargs.length).invokeExact(collargs);
+                    byte[] listargs = Arrays.copyOfRange(args, 0, outputs);
+                    System.arraycopy(args, pos+collects, listargs, pos+1, outputs - (pos+1));
+                    listargs[pos] = coll;
+                    //System.out.println("  coll="+coll+" listargs="+Arrays.toString(listargs));
+                    Object expect = lister.asSpreader(byte[].class, listargs.length).invokeExact(listargs);
+                    //System.out.println("  expect="+expect);
+
+                    // now use the combined MH, and test the output:
+                    MethodHandle mh = collectArguments(lister, pos, byte[].class, BYTE_COLLECTORS[collects]);
+                    if (mh == null)  continue;  // no infix collection, yet
+                    assert(mh.type().parameterCount() == inputs);
+                    Object observe = mh.asSpreader(byte[].class, args.length).invokeExact(args);
+                    assertEquals(expect, observe);
+                }
+            }
+        }
+    }
+
+    private static MethodHandle collectArguments(MethodHandle lister, int pos, Class<?> array, MethodHandle collector) {
+        int collects = collector.type().parameterCount();
+        int outputs = lister.type().parameterCount();
+        if (pos == outputs - 1)
+            return MethodHandles.filterArguments(lister, pos,
+                        collector.asSpreader(array, collects))
+                            .asCollector(array, collects);
+        //return MethodHandles.collectArguments(lister, pos, collector); //no such animal
+        return null;
+    }
+
+    private static final Class<?>[] RETURN_TYPES = {
+        Object.class, String.class, Integer.class,
+        int.class, long.class,
+        boolean.class, byte.class, char.class, short.class,
+        float.class, double.class,
+        void.class,
+    };
+
+    @Test
+    public void testReturns() throws Throwable {
+        if (!startTest("testReturns"))  return;
+        // fault injection:
+        int faultCount = 0;  // total of 1296 tests
+        faultCount = Integer.getInteger("testReturns.faultCount", 0);
+        for (Class<?> ret : RETURN_TYPES) {
+            // make a complicated identity function and pass something through it
+            System.out.println(ret.getSimpleName());
+            Class<?> vret = (ret == void.class) ? Void.class : ret;
+            MethodHandle id = // (vret)->ret
+                identity(vret).asType(methodType(ret, vret));
+            final int LENGTH = 4;
+            int[] index = {0};
+            Object vals = java.lang.reflect.Array.newInstance(vret, LENGTH);
+            MethodHandle indexGetter =  //()->int
+                insertArguments(arrayElementGetter(index.getClass()), 0, index, 0);
+            MethodHandle valSelector =  // (int)->vret
+                arrayElementGetter(vals.getClass()).bindTo(vals);
+            MethodHandle valGetter =  // ()->vret
+                foldArguments(valSelector, indexGetter);
+            if (ret != void.class) {
+                for (int i = 0; i < LENGTH; i++) {
+                    Object val = (i + 50);
+                    if (ret == boolean.class)  val = (i % 3 == 0);
+                    if (ret == String.class)   val = "#"+i;
+                    if (ret == char.class)     val = (char)('a'+i);
+                    if (ret == byte.class)     val = (byte)~i;
+                    if (ret == short.class)    val = (short)(1<<i);
+                    java.lang.reflect.Array.set(vals, i, val);
+                }
+            }
+            for (int i = 0; i < LENGTH; i++) {
+                Object val = java.lang.reflect.Array.get(vals, i);
+                System.out.println(i+" => "+val);
+                index[0] = i;
+                if (--faultCount == 0)  index[0] ^= 1;
+                Object x = valGetter.invokeWithArguments();
+                assertEquals(val, x);
+                // make a return-filter call:  x = id(valGetter())
+                if (--faultCount == 0)  index[0] ^= 1;
+                x = filterReturnValue(valGetter, id).invokeWithArguments();
+                assertEquals(val, x);
+                // make a filter call:  x = id(*,valGetter(),*)
+                for (int len = 1; len <= 4; len++) {
+                    for (int pos = 0; pos < len; pos++) {
+                        MethodHandle proj = id;  // lambda(..., vret x,...){x}
+                        for (int j = 0; j < len; j++) {
+                            if (j == pos)  continue;
+                            proj = dropArguments(proj, j, Object.class);
+                        }
+                        assert(proj.type().parameterCount() == len);
+                        // proj: (Object*, pos: vret, Object*)->ret
+                        assertEquals(vret, proj.type().parameterType(pos));
+                        MethodHandle vgFilter = dropArguments(valGetter, 0, Object.class);
+                        if (--faultCount == 0)  index[0] ^= 1;
+                        x = filterArguments(proj, pos, vgFilter).invokeWithArguments(new Object[len]);
+                        assertEquals(val, x);
+                    }
+                }
+                // make a fold call:
+                for (int len = 0; len <= 4; len++) {
+                    for (int fold = 0; fold <= len; fold++) {
+                        MethodHandle proj = id;  // lambda(ret x, ...){x}
+                        if (ret == void.class)  proj = constant(Object.class, null);
+                        int arg0 = (ret == void.class ? 0 : 1);
+                        for (int j = 0; j < len; j++) {
+                            proj = dropArguments(proj, arg0, Object.class);
+                        }
+                        assert(proj.type().parameterCount() == arg0 + len);
+                        // proj: (Object*, pos: vret, Object*)->ret
+                        if (arg0 != 0)  assertEquals(vret, proj.type().parameterType(0));
+                        MethodHandle vgFilter = valGetter.asType(methodType(ret));
+                        for (int j = 0; j < fold; j++) {
+                            vgFilter = dropArguments(vgFilter, j, Object.class);
+                        }
+                        x = foldArguments(proj, vgFilter).invokeWithArguments(new Object[len]);
+                        if (--faultCount == 0)  index[0] ^= 1;
+                        assertEquals(val, x);
+                    }
+                }
+            }
+        }
+        //System.out.println("faultCount="+faultCount);
+    }
+
+    @Test
+    public void testRecursion() throws Throwable {
+        if (!startTest("testRecursion"))  return;
+        final int LIMIT = 10;
+        for (int i = 0; i < LIMIT; i++) {
+            RFCB rfcb = new RFCB(i);
+            Object x = "x", y = "y";
+            Object result = rfcb.recursiveFunction(x, y);
+            verbose(1, result);
+        }
+    }
+    /** Recursive Function Control Block */
+    private static class RFCB {
+        java.util.Random random;
+        final MethodHandle[] fns;
+        int depth;
+        @SuppressWarnings("LeakingThisInConstructor")
+        RFCB(int seed) throws Throwable {
+            this.random = new java.util.Random(seed);
+            this.fns = new MethodHandle[Math.max(29, (1 << MAX_DEPTH-2)/3)];
+            java.util.Arrays.fill(fns, lookup().bind(this, "recursiveFunction", genericMethodType(2)));
+            for (int i = 5; i < fns.length; i++) {
+                switch (i % 4) {
+                case 0: fns[i] = filterArguments(fns[i - 5], 0, insertArguments(fns[i - 4], 1, ".")); break;
+                case 1: fns[i] = filterArguments(fns[i - 5], 1, insertArguments(fns[i - 3], 1, ".")); break;
+                case 2: fns[i] = filterReturnValue(fns[i - 5], insertArguments(fns[i - 2], 1, ".")); break;
+                }
+            }
+        }
+        Object recursiveFunction(Object x, Object y) throws Throwable {
+            depth++;
+            try {
+                final int ACTION_COUNT = 11;
+                switch (random.nextInt(ACTION_COUNT)) {
+                case 1:
+                    Throwable ex = new RuntimeException();
+                    ex.fillInStackTrace();
+                    if (VERBOSITY >= 2) ex.printStackTrace(System.out);
+                    x = "ST; " + x;
+                    break;
+                case 2:
+                    System.gc();
+                    x = "GC; " + x;
+                    break;
+                }
+                boolean isLeaf = (depth >= MAX_DEPTH);
+                if (isLeaf) {
+                    return Arrays.asList(x, y).toString();
+                }
+                return fns[random.nextInt(fns.length)].invokeExact(x, y);
+            } finally {
+                depth--;
+            }
+        }
+    }
+
+    private static MethodHandle sequence(MethodHandle mh1, MethodHandle... mhs) {
+        MethodHandle res = mh1;
+        for (MethodHandle mh2 : mhs)
+            res = filterReturnValue(res, mh2);
+        return res;
+    }
+    private static void assertEqualFunction(MethodHandle x, MethodHandle y) throws Throwable {
+        assertEquals(x.type(), y.type()); //??
+        MethodType t = x.type();
+        if (t.parameterCount() == 0) {
+            assertEqualFunctionAt(null, x, y);
+            return;
+        }
+        Class<?> ptype = t.parameterType(0);
+        if (ptype == long.class || ptype == Long.class) {
+            for (long i = -10; i <= 10; i++) {
+                assertEqualFunctionAt(i, x, y);
+            }
+        } else {
+            for (int i = -10; i <= 10; i++) {
+                assertEqualFunctionAt(i, x, y);
+            }
+        }
+    }
+    private static void assertEqualFunctionAt(Object v, MethodHandle x, MethodHandle y) throws Throwable {
+        Object[] args = new Object[x.type().parameterCount()];
+        Arrays.fill(args, v);
+        Object xval = invokeWithCatch(x, args);
+        Object yval = invokeWithCatch(y, args);
+        String msg = "ok";
+        if (!Objects.equals(xval, yval)) {
+            msg = ("applying "+x+" & "+y+" to "+v);
+        }
+        assertEquals(msg, xval, yval);
+    }
+    private static Object invokeWithCatch(MethodHandle mh, Object... args) throws Throwable {
+        try {
+            return mh.invokeWithArguments(args);
+        } catch (Throwable ex) {
+            System.out.println("threw: "+mh+Arrays.asList(args));
+            ex.printStackTrace(System.out);
+            return ex;
+        }
+    }
+
+    private static final Lookup LOOKUP = lookup();
+    private static MethodHandle findStatic(String name,
+                                           Class<?> rtype,
+                                           Class<?>... ptypes) {
+        try {
+            return LOOKUP.findStatic(LOOKUP.lookupClass(), name, methodType(rtype, ptypes));
+        } catch (ReflectiveOperationException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+    private static MethodHandle findStatic(String name,
+                                           Class<?> rtype,
+                                           List<?> ptypes) {
+        return findStatic(name, rtype, ptypes.toArray(new Class<?>[ptypes.size()]));
+    }
+    static int getProperty(String name, int dflt) {
+        String qual = LOOKUP.lookupClass().getName();
+        String prop = System.getProperty(qual+"."+name);
+        if (prop == null)  prop = System.getProperty(name);
+        if (prop == null)  return dflt;
+        return Integer.parseInt(prop);
+    }
+
+    private static int opI(int... xs) {
+        stress();
+        int base = 100;
+        int z = 0;
+        for (int x : xs) {
+            z = (z * base) + (x % base);
+        }
+        verbose("opI", xs.length, xs, z);
+        return z;
+    }
+    private static int opI2(int x, int y) { return opI(x, y); }  // x*100 + y%100
+    private static int opI3(int x, int y, int z) { return opI(x, y, z); }
+    private static int opI4(int w, int x, int y, int z) { return opI(w, x, y, z); }
+    private static int opI(int x) { return opI2(x, 37); }
+    private static Object opI_L(int x) { return (Object) opI(x); }
+    private static long opJ3(long x, long y, long z) { return (long) opI3((int)x, (int)y, (int)z); }
+    private static long opJ2(long x, long y) { return (long) opI2((int)x, (int)y); }
+    private static long opJ(long x) { return (long) opI((int)x); }
+    private static Object opL2(Object x, Object y) { return (Object) opI2((int)x, (int)y); }
+    private static Object opL(Object x) { return (Object) opI((int)x); }
+    private static int opL2_I(Object x, Object y) { return opI2((int)x, (int)y); }
+    private static int opL_I(Object x) { return opI((int)x); }
+    private static long opL_J(Object x) { return (long) opI((int)x); }
+    private static final MethodHandle opI, opI2, opI3, opI4, opI_L, opJ, opJ2, opJ3, opL2, opL, opL2_I, opL_I, opL_J;
+    static {
+        opI4 = findStatic("opI4", int.class, int.class, int.class, int.class, int.class);
+        opI3 = findStatic("opI3", int.class, int.class, int.class, int.class);
+        opI2 = findStatic("opI2", int.class, int.class, int.class);
+        opI = findStatic("opI", int.class, int.class);
+        opI_L = findStatic("opI_L", Object.class, int.class);
+        opJ = findStatic("opJ", long.class, long.class);
+        opJ2 = findStatic("opJ2", long.class, long.class, long.class);
+        opJ3 = findStatic("opJ3", long.class, long.class, long.class, long.class);
+        opL2 = findStatic("opL2", Object.class, Object.class, Object.class);
+        opL = findStatic("opL", Object.class, Object.class);
+        opL2_I = findStatic("opL2_I", int.class, Object.class, Object.class);
+        opL_I = findStatic("opL_I", int.class, Object.class);
+        opL_J = findStatic("opL_J", long.class, Object.class);
+    }
+    private static final MethodHandle[] INT_COLLECTORS = {
+        constant(int.class, 42), opI, opI2, opI3, opI4
+    };
+    private static final MethodHandle[] BYTE_COLLECTORS = {
+        constant(byte.class, (byte)42), i2b(opI), i2b(opI2), i2b(opI3), i2b(opI4)
+    };
+    private static final MethodHandle[] LONG_COLLECTORS = {
+        constant(long.class, 42), opJ, opJ2, opJ3
+    };
+
+    private static int addI(int x, int y) { stress(); return x+y; }
+    private static Object addL(Object x, Object y) { return addI((int)x, (int)y); }
+    private static final MethodHandle addI, addL;
+    static {
+        addI = findStatic("addI", int.class, int.class, int.class);
+        addL = findStatic("addL", Object.class, Object.class, Object.class);
+    }
+
+    private static Object list8ints(int a, int b, int c, int d, int e, int f, int g, int h) {
+        return Arrays.asList(a, b, c, d, e, f, g, h);
+    }
+    private static Object list8longs(long a, long b, long c, long d, long e, long f, long g, long h) {
+        return Arrays.asList(a, b, c, d, e, f, g, h);
+    }
+    private static final MethodHandle list8ints = findStatic("list8ints", Object.class,
+                                                             Collections.nCopies(8, int.class));
+    private static final MethodHandle list8longs = findStatic("list8longs", Object.class,
+                                                              Collections.nCopies(8, long.class));
+    private static final MethodHandle[] INT_LISTERS, LONG_LISTERS, BYTE_LISTERS;
+    static {
+        int listerCount = list8ints.type().parameterCount() + 1;
+        INT_LISTERS  = new MethodHandle[listerCount];
+        LONG_LISTERS = new MethodHandle[listerCount];
+        BYTE_LISTERS = new MethodHandle[listerCount];
+        MethodHandle lister = list8ints;
+        MethodHandle llister = list8longs;
+        for (int i = listerCount - 1; ; i--) {
+            INT_LISTERS[i] = lister;
+            LONG_LISTERS[i] = llister;
+            BYTE_LISTERS[i] = i2b(lister);
+            if (i == 0)  break;
+            lister  = insertArguments(lister,  i-1, 0);
+            llister = insertArguments(llister, i-1, 0L);
+        }
+    }
+    private static MethodHandle i2b(MethodHandle mh) {
+        return MethodHandles.explicitCastArguments(mh, subst(mh.type(), int.class, byte.class));
+    }
+    private static MethodType subst(MethodType mt, Class<?> from, Class<?> to) {
+        for (int i = 0; i < mt.parameterCount(); i++) {
+            if (mt.parameterType(i) == from)
+                mt = mt.changeParameterType(i, to);
+        }
+        if (mt.returnType() == from)
+            mt = mt.changeReturnType(to);
+        return mt;
+    }
+
+
+    private static Object  convI_L(int     x) { stress(); return (Object)  x; }
+    private static int     convL_I(Object  x) { stress(); return (int)     x; }
+    private static Object  convJ_L(long    x) { stress(); return (Object)  x; }
+    private static long    convL_J(Object  x) { stress(); return (long)    x; }
+    private static int     convJ_I(long    x) { stress(); return (int)     x; }
+    private static long    convI_J(int     x) { stress(); return (long)    x; }
+    private static final MethodHandle convI_L, convL_I, convJ_L, convL_J, convJ_I, convI_J;
+    static {
+        convI_L = findStatic("convI_L", Object.class, int.class);
+        convL_I = findStatic("convL_I", int.class, Object.class);
+        convJ_L = findStatic("convJ_L", Object.class, long.class);
+        convL_J = findStatic("convL_J", long.class, Object.class);
+        convJ_I = findStatic("convJ_I", int.class, long.class);
+        convI_J = findStatic("convI_J", long.class, int.class);
+    }
+
+    // stress modes:
+    private static final int MAX_DEPTH = getProperty("MAX_DEPTH", 5);
+    private static final int REPEAT = getProperty("REPEAT", 0);
+    private static final int STRESS = getProperty("STRESS", 0);
+    private static /*v*/ int STRESS_COUNT;
+    private static final Object[] SINK = new Object[4];
+    private static void stress() {
+        if (STRESS <= 0) return;
+        int count = STRESS + (STRESS_COUNT++ & 0x1);  // non-constant value
+        for (int i = 0; i < count; i++) {
+            SINK[i % SINK.length] = new Object[STRESS + i % (SINK.length + 1)];
+        }
+    }
+
+    // verbosity:
+    private static final int VERBOSITY = getProperty("VERBOSITY", 0) + (REPEAT == 0 ? 0 : -1);
+    private static void verbose(Object a, Object b, Object c, Object d) {
+        if (VERBOSITY <= 0)  return;
+        verbose(1, a, b, c, d);
+    }
+    private static void verbose(Object a, Object b, Object c) {
+        if (VERBOSITY <= 0)  return;
+        verbose(1, a, b, c);
+    }
+    private static void verbose(int level, Object a, Object... bcd) {
+        if (level > VERBOSITY)  return;
+        String m = a.toString();
+        if (bcd != null && bcd.length > 0) {
+            List<Object> l = new ArrayList<>(bcd.length);
+            for (Object x : bcd) {
+                if (x instanceof Object[])  x = Arrays.asList((Object[])x);
+                if (x instanceof int[])     x = Arrays.toString((int[])x);
+                if (x instanceof long[])    x = Arrays.toString((long[])x);
+                l.add(x);
+            }
+            m = m+Arrays.asList(bcd);
+        }
+        System.out.println(m);
+    }
+    String testOnly;
+    String testOnlyTests;
+    private boolean startTest(String name) {
+        if (testOnly != null && !testOnly.contains(name))
+            return false;
+        verbose(0, "["+name+"]");
+        testOnlyTests = (testOnlyTests == null) ? name : testOnlyTests+" "+name;
+        return true;
+    }
+
+}
diff --git a/ojluni/src/test/java/lang/invoke/common/test/java/lang/invoke/lib/CodeCacheOverflowProcessor.java b/ojluni/src/test/java/lang/invoke/common/test/java/lang/invoke/lib/CodeCacheOverflowProcessor.java
new file mode 100644
index 0000000..9561660
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/common/test/java/lang/invoke/lib/CodeCacheOverflowProcessor.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+package test.java.lang.invoke.lib;
+
+// Android-added: import of Function for filterException
+import java.util.function.Function;
+
+/**
+ * Helper class used to catch and process VirtualMachineError with message "Out
+ * of space in CodeCache". Some JSR292 tests run out of code cache size, so code
+ * cache overflows and VME is thrown. This VME is considered as non-critical in
+ * some JSR292 tests, so it should be processed to prevent test failure.
+ */
+public class CodeCacheOverflowProcessor {
+
+    /**
+     * Checks if an instance of Throwable is caused by VirtualMachineError with
+     * message "Out of space in CodeCache". May be used as filter in method
+     * {@code jdk.testlibrary.Utils.filterException}.
+     *
+     * @param t - Throwable to check.
+     * @return true if Throwable is caused by VME, false otherwise.
+     */
+    public static Boolean isThrowableCausedByVME(Throwable t) {
+        Throwable causeOfT = t;
+        do {
+            if (causeOfT instanceof VirtualMachineError
+                    && causeOfT.getMessage().matches(".*[Oo]ut of space"
+                            + " in CodeCache.*")) {
+                return true;
+            }
+            causeOfT = causeOfT != null ? causeOfT.getCause() : null;
+        } while (causeOfT != null && causeOfT != t);
+        return false;
+    }
+
+    /**
+     * Checks if the given test throws an exception caused by
+     * VirtualMachineError with message "Out of space in CodeCache", and, if VME
+     * takes place, processes it so that no exception is thrown, and prints its
+     * stack trace. If test throws exception not caused by VME, this method just
+     * re-throws this exception.
+     *
+     * @param test - test to check for and process VirtualMachineError.
+     * @return - an exception caused by VME or null
+     *           if test has thrown no exception.
+     * @throws Throwable - if test has thrown an exception
+     *                     that is not caused by VME.
+     */
+    public static Throwable runMHTest(ThrowingRunnable test) throws Throwable {
+        Throwable t = filterException(test::run,
+                CodeCacheOverflowProcessor::isThrowableCausedByVME);
+        if (t != null) {
+            System.err.printf("%nNon-critical exception caught becuse of"
+                    + " code cache size is not enough to run all test cases.%n%n");
+        }
+        return t;
+    }
+
+    // BEGIN Android-changed: these interfaces methods taken from jdk.testlibrary
+    /**
+     * Interface same as java.lang.Runnable but with
+     * method {@code run()} able to throw any Throwable.
+     */
+    public static interface ThrowingRunnable {
+        void run() throws Throwable;
+    }
+
+    /**
+     * Filters out an exception that may be thrown by the given
+     * test according to the given filter.
+     *
+     * @param test - method that is invoked and checked for exception.
+     * @param filter - function that checks if the thrown exception matches
+     *                 criteria given in the filter's implementation.
+     * @return - exception that matches the filter if it has been thrown or
+     *           {@code null} otherwise.
+     * @throws Throwable - if test has thrown an exception that does not
+     *                     match the filter.
+     */
+    public static Throwable filterException(ThrowingRunnable test,
+                                            Function<Throwable, Boolean> filter) throws Throwable {
+        try {
+            test.run();
+        } catch (Throwable t) {
+            if (filter.apply(t)) {
+                return t;
+            } else {
+                throw t;
+            }
+        }
+        return null;
+    }
+    // END Android-changed: these interfaces methods taken from jdk.testlibrary
+}
diff --git a/ojluni/src/test/java/lang/invoke/remote/RemoteExample.java b/ojluni/src/test/java/lang/invoke/remote/RemoteExample.java
new file mode 100644
index 0000000..5237f9d
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/remote/RemoteExample.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2009, 2013, 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.
+ */
+package test.java.lang.invoke.remote;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import test.java.lang.invoke.MethodHandlesTest;
+
+/**
+ * Out-of-package access into protected members of test.java.lang.invoke.remote.MethodHandle.PubExample.
+ */
+public class RemoteExample extends MethodHandlesTest.PubExample {
+    public RemoteExample() { super("RemoteExample"); }
+    public static Lookup lookup() { return MethodHandles.lookup(); }
+    public final     void fin_v0() { MethodHandlesTest.called("Rem/fin_v0", this); }
+    protected        void pro_v0() { MethodHandlesTest.called("Rem/pro_v0", this); }
+    protected static void pro_s0() { MethodHandlesTest.called("Rem/pro_s0"); }
+}
diff --git a/ojluni/src/test/java/util/Optional/Basic.java b/ojluni/src/test/java/util/Optional/Basic.java
index 5c82418..e49c0ed 100644
--- a/ojluni/src/test/java/util/Optional/Basic.java
+++ b/ojluni/src/test/java/util/Optional/Basic.java
@@ -33,7 +33,10 @@
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import static java.util.stream.Collectors.toList;
 
@@ -203,4 +206,69 @@
     public void testStreamPresent() {
         assertEquals(Optional.of("xyzzy").stream().collect(toList()), List.of("xyzzy"));
     }
+
+    // BEGIN Android-added: More tests for coverage http://b/203822442.
+    // Also improves coverage for Optional{Int,Long,Double}
+    private static final Optional<Integer> P = Optional.<Integer>of(3);
+    private static final Optional<Integer> E = Optional.<Integer>empty();
+
+    @Test
+    void testIfPresentOrElse_empty() {
+        AtomicInteger flag = new AtomicInteger(0);
+        E.ifPresentOrElse(integer -> flag.set(1), () -> flag.set(2));
+        assertEquals(flag.get(), 2);
+    }
+
+    @Test
+    void testIfPresentOrElse_present() {
+        AtomicInteger flag = new AtomicInteger(0);
+        P.ifPresentOrElse(integer -> flag.set(1), () -> flag.set(2));
+        assertEquals(flag.get(), 1);
+    }
+
+    @Test
+    void testOr_empty() {
+        Optional<Integer> o = E.or(() -> Optional.of(5));
+        assertEquals((int) o.get(), 5);
+    }
+
+    @Test
+    void testOr_present() {
+        Optional<Integer> o = P.or(() -> Optional.of(5));
+        assertEquals((int) o.get(), 3);
+    }
+
+    @Test
+    public void testStream_empty() {
+        Stream<Integer> s = E.stream();
+        assertEquals(s.collect(Collectors.toList()), List.of());
+    }
+
+    @Test
+    public void testStream_present() {
+        Stream<Integer> s = P.stream();
+        assertEquals(s.collect(Collectors.toList()), List.of(3));
+    }
+
+    @Test(expectedExceptions = NoSuchElementException.class)
+    public void testOrElseThrow_empty() {
+        E.orElseThrow();
+    }
+
+    @Test
+    public void testOrElseThrow_present() {
+        assertEquals((int) P.orElseThrow(), 3);
+    }
+
+    @Test
+    public void testIsEmpty_empty() {
+        assertTrue(E.isEmpty());
+    }
+
+    @Test
+    public void testIsEmpty_present() {
+        assertFalse(P.isEmpty());
+    }
+    // END Android-added: More tests for coverage http://b/203822442.
+
 }
diff --git a/openjdk_java_files.bp b/openjdk_java_files.bp
index 99971d1e..8ba1047 100644
--- a/openjdk_java_files.bp
+++ b/openjdk_java_files.bp
@@ -625,6 +625,7 @@
         "ojluni/src/main/java/java/security/interfaces/RSAPrivateCrtKey.java",
         "ojluni/src/main/java/java/security/interfaces/RSAPrivateKey.java",
         "ojluni/src/main/java/java/security/interfaces/RSAPublicKey.java",
+        "ojluni/src/main/java/java/security/interfaces/XECKey.java",
         "ojluni/src/main/java/java/security/interfaces/package-info.java",
         "ojluni/src/main/java/java/security/InvalidAlgorithmParameterException.java",
         "ojluni/src/main/java/java/security/InvalidKeyException.java",
@@ -696,6 +697,8 @@
         "ojluni/src/main/java/java/security/spec/RSAPrivateCrtKeySpec.java",
         "ojluni/src/main/java/java/security/spec/RSAPrivateKeySpec.java",
         "ojluni/src/main/java/java/security/spec/RSAPublicKeySpec.java",
+        "ojluni/src/main/java/java/security/spec/XECPrivateKeySpec.java",
+        "ojluni/src/main/java/java/security/spec/XECPublicKeySpec.java",
         "ojluni/src/main/java/java/security/spec/X509EncodedKeySpec.java",
         "ojluni/src/main/java/java/security/spec/package-info.java",
         "ojluni/src/main/java/java/security/Timestamp.java",