changeset 49697:868580e46733 datum

Add java.lang.compiler package; add Extractor support
author briangoetz
date Fri, 30 Mar 2018 15:33:52 -0400
parents 1be3e11b4413
children 4b1e008bd652
files src/java.base/share/classes/java/lang/compiler/Extractor.java src/java.base/share/classes/java/lang/compiler/ExtractorImpl.java src/java.base/share/classes/java/lang/compiler/PatternCarriers.java src/java.base/share/classes/module-info.java test/jdk/java/lang/compiler/ExtractorTest.java
diffstat 5 files changed, 744 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/compiler/Extractor.java	Fri Mar 30 15:33:52 2018 -0400
@@ -0,0 +1,370 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package java.lang.compiler;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+/**
+ * Supporting type for implementation of pattern matching.  An {@linkplain Extractor}
+ * is a constant bundle of method handles that describe a particular pattern, and
+ * suitable for storing in the constant pool.
+ *
+ * <p>An {@linkplain Extractor} is describe by a {@code descriptor} in the form
+ * of a {@link MethodType}.  The return value of the descriptor is ignored; the
+ * argument types of the descriptor indicate the types of the output binding
+ * variables.
+ *
+ * @author Brian Goetz
+ */
+public interface Extractor {
+    /**
+     * A method handle that attempts to perform a match.  It will have type
+     * {@code (Object)Object}.  It accepts the target to be matched, and returns
+     * an opaque carrier if the match succeeds, or {@code null} if it fails.
+     *
+     * @return the {@code tryMatch} method handle
+     */
+    MethodHandle tryMatch();
+
+    /**
+     * A method handle that extracts a component from the match carrier.  It
+     * will take the match carrier and return the corresponding match binding.
+     *
+     * @param i the index of the component
+     * @return the {@code component} method handle
+     */
+    MethodHandle component(int i);
+
+    /**
+     * The descriptor of the {@linkplain Extractor}.  The parameter types of
+     * the descriptor are the types of the binding variables.  The return type
+     * is ignored.
+     *
+     * @return the descriptor
+     */
+    MethodType descriptor();
+
+    /**
+     * Compose an extractor with a method handle that receives the bindings
+     *
+     * @param target method handle to receive the bindings
+     * @return the composed method handle
+     */
+    default MethodHandle compose(MethodHandle target) {
+        int count = descriptor().parameterCount();
+        MethodHandle[] components = new MethodHandle[count];
+        int[] reorder = new int[count];
+        for (int i=0; i<count; i++) {
+            components[i] = component(i);
+            reorder[i] = 0;
+        }
+
+        MethodHandle mh = MethodHandles.filterArguments(target, 0, components);
+        mh = MethodHandles.permuteArguments(mh, MethodType.methodType(target.type().returnType(), tryMatch().type().returnType()),
+                                            reorder);
+        mh = MethodHandles.filterArguments(mh, 0, tryMatch());
+        // @@@ What if pattern doesn't match?
+        return mh;
+    }
+
+
+    /**
+     * Create an {@linkplain Extractor} from its components
+     *
+     * @param descriptor the descriptor method type
+     * @param tryMatch the {@code tryMatch} method handle
+     * @param components the {@code components} method handles
+     * @return the {@linkplain Extractor}
+     */
+    public static Extractor of(MethodType descriptor,
+                               MethodHandle tryMatch,
+                               MethodHandle... components) {
+        return new ExtractorImpl(descriptor, tryMatch, components);
+    }
+
+    /**
+     * Create a lazy, self-carrier {@linkplain Extractor}
+     *
+     * @param descriptor the descriptor method type
+     * @param components the {@code component} method handles
+     * @return the {@linkplain Extractor}
+     */
+    public static Extractor ofLazy(MethodType descriptor,
+                                   MethodHandle... components) {
+        return of(descriptor, MethodHandles.identity(descriptor.returnType()), components);
+    }
+
+    /**
+     * Create a lazy, partial, self-carrier {@linkplain Extractor}
+     *
+     * @param descriptor the descriptor method type
+     * @param components the {@code component} method handles
+     * @param predicate a {@link MethodHandle} that accepts the target and returns
+     *                  boolean, indicating whether the pattern matched
+     * @return the {@linkplain Extractor}
+     */
+    public static Extractor ofLazyPartial(MethodType descriptor,
+                                          MethodHandle predicate,
+                                          MethodHandle... components) {
+        Class<?> targetType = descriptor.returnType();
+        MethodHandle tryMatch = MethodHandles.guardWithTest(predicate,
+                                                            MethodHandles.identity(targetType),
+                                                            MethodHandles.dropArguments(MethodHandles.constant(targetType, null), 0, targetType));
+        return of(descriptor, tryMatch, components);
+    }
+
+    /**
+     * Create a partial, self-carrier {@linkplain Extractor}
+     * @param descriptor the descriptor method type
+     * @param copier a {@link MethodHandle} that clones the target
+     * @param predicate a {@link MethodHandle} that accepts the target and returns
+     *                  boolean, indicating whether the pattern matched
+     * @param components the {@code component} method handles
+     * @return the {@linkplain Extractor}
+     */
+    public static Extractor ofPartial(MethodType descriptor,
+                                      MethodHandle copier,
+                                      MethodHandle predicate,
+                                      MethodHandle... components) {
+
+        Class<?> targetType = descriptor.returnType();
+        MethodHandle guarded = MethodHandles.guardWithTest(predicate,
+                                                           MethodHandles.identity(targetType),
+                                                           MethodHandles.dropArguments(MethodHandles.constant(targetType, null), 0, targetType));
+        MethodHandle tryMatch = MethodHandles.filterArguments(guarded, 0, copier);
+        return of(descriptor, tryMatch, components);
+    }
+
+    // target digester = (R, MH[CDESC]->Obj) -> Obj, where MH[...] is carrier ctor
+
+    /**
+     * Create a {@linkplain Extractor} using a carrier specified by a descriptor.
+     *
+     * <p>
+     *
+     * @param descriptor the extractor descriptor
+     * @param carrierFactory a method handle to create the carrier from the target
+     * @param digester a {@link MethodHandle} that accepts a target and a carrier
+     *                 factory method handle, and which calls the factory with the
+     *                 values extracted from the target
+     * @param components the {@code component} method handles
+     * @return the {@linkplain Extractor}
+     */
+    public static Extractor ofCarrier(MethodType descriptor,
+                                      MethodHandle carrierFactory,
+                                      MethodHandle digester,
+                                      MethodHandle... components) {
+        MethodHandle tryMatch = MethodHandles.insertArguments(digester, 1, carrierFactory);
+        return of(descriptor, tryMatch, components);
+    }
+
+    /**
+     * Create a {@linkplain Extractor} using a carrier specified by a descriptor.
+     *
+     * <p>
+     *
+     * @param descriptor the extractor descriptor
+     * @param carrierFactory a method handle to create the carrier from the target
+     * @param digester a {@link MethodHandle} that accepts a target and a carrier
+     *                 factory method handle, and which calls the factory with the
+     *                 values extracted from the target
+     * @param predicate Predicate, applied to the carrier values, determining
+     *                  whether there was a match
+     * @param components the {@code component} method handles
+     * @return the {@linkplain Extractor}
+     */
+    public static Extractor ofCarrierPartial(MethodType descriptor,
+                                             MethodHandle carrierFactory,
+                                             MethodHandle digester,
+                                             MethodHandle predicate,
+                                             MethodHandle... components) {
+        MethodHandle nuller = MethodHandles.constant(Object.class, null);
+        nuller = MethodHandles.dropArguments(nuller, 0, carrierFactory.type().parameterList());
+        MethodHandle guarded = MethodHandles.guardWithTest(predicate, carrierFactory, nuller);
+        MethodHandle tryMatch = MethodHandles.insertArguments(digester, 1, guarded);
+        return of(descriptor, tryMatch, components);
+    }
+
+    /**
+     * Create an {@linkplain Extractor} for a type pattern, with a single binding
+     * variable
+     *
+     * @param type the type to match against
+     * @return the {@linkplain Extractor}
+     */
+    public static Extractor ofType(Class<?> type) {
+        // tryMatch = (t instanceof type) ? t : null
+        // component = (type) o
+        return null;
+    }
+
+    /**
+     * Bootstrap for creating an {@linkplain Extractor} from components
+     *
+     * @param lookup ignored
+     * @param constantName ignored
+     * @param constantType Must be {@code Extractor.class}
+     * @param descriptor the descriptor method type
+     * @param tryMatch the {@code tryMatch} method handle
+     * @param components the {@code components} method handles
+     * @return the {@linkplain Extractor}
+     */
+    public static Extractor of(MethodHandles.Lookup lookup, String constantName, Class<Extractor> constantType,
+                               MethodType descriptor, MethodHandle tryMatch, MethodHandle... components) {
+        return Extractor.of(descriptor, tryMatch, components);
+    }
+
+    /**
+     * Bootstrap for creating a lazy, partial, self-carrier {@linkplain Extractor} from components
+     *
+     * @param lookup ignored
+     * @param constantName ignored
+     * @param constantType Must be {@code Extractor.class}
+     * @param descriptor the descriptor method type
+     * @param components the {@code components} method handles
+     * @return the {@linkplain Extractor}
+     */
+    public static Extractor ofLazy(MethodHandles.Lookup lookup, String constantName, Class<Extractor> constantType,
+                                   MethodType descriptor, MethodHandle... components) {
+        return Extractor.ofLazy(descriptor, components);
+    }
+
+    /**
+     * Bootstrap for creating a lazy, partial, self-carrier {@linkplain Extractor} from components
+     *
+     * @param lookup ignored
+     * @param constantName ignored
+     * @param constantType Must be {@code Extractor.class}
+     * @param descriptor the descriptor method type
+     * @param predicate predicate method handle, applied to target
+     * @param components the {@code components} method handles
+     * @return the {@linkplain Extractor}
+     */
+    public static Extractor ofLazyPartial(MethodHandles.Lookup lookup, String constantName, Class<Extractor> constantType,
+                                          MethodType descriptor, MethodHandle predicate, MethodHandle... components) {
+        return ofLazyPartial(descriptor, predicate, components);
+    }
+
+    /**
+     * Bootstrap for creating a lazy, partial, self-carrier {@linkplain Extractor} from components
+     *
+     * @param lookup ignored
+     * @param constantName ignored
+     * @param constantType Must be {@code Extractor.class}
+     * @param descriptor the descriptor method type
+     * @param copier a {@link MethodHandle} that clones the target
+     * @param predicate predicate method handle, applied to target
+     * @param components the {@code components} method handles
+     * @return the {@linkplain Extractor}
+     */
+    public static Extractor ofPartial(MethodHandles.Lookup lookup, String constantName, Class<Extractor> constantType,
+                                      MethodType descriptor, MethodHandle copier, MethodHandle predicate, MethodHandle... components) {
+        return ofPartial(descriptor, copier, predicate, components);
+    }
+
+
+    /**
+     * Create a {@linkplain Extractor} using a carrier specified by a descriptor.
+     *
+     * <p>
+     *
+     * @param lookup ignored
+     * @param constantName ignored
+     * @param constantType Must be {@code Extractor.class}
+     * @param descriptor the extractor descriptor
+     * @param carrierFactory a method handle to create the carrier from the target
+     * @param digester a {@link MethodHandle} that accepts a target and a carrier
+     *                 factory method handle, and which calls the factory with the
+     *                 values extracted from the target
+     * @param components the {@code component} method handles
+     * @return the {@linkplain Extractor}
+     */
+    public static Extractor ofCarrier(MethodHandles.Lookup lookup, String constantName, Class<Extractor> constantType,
+                                      MethodType descriptor,
+                                      MethodHandle carrierFactory,
+                                      MethodHandle digester,
+                                      MethodHandle... components) {
+        return ofCarrier(descriptor, carrierFactory, digester, components);
+    }
+
+    /**
+     * Create a {@linkplain Extractor} using a carrier specified by a descriptor.
+     *
+     * <p>
+     *
+     * @param lookup ignored
+     * @param constantName ignored
+     * @param constantType Must be {@code Extractor.class}
+     * @param descriptor the extractor descriptor
+     * @param carrierFactory a method handle to create the carrier from the target
+     * @param digester a {@link MethodHandle} that accepts a target and a carrier
+     *                 factory method handle, and which calls the factory with the
+     *                 values extracted from the target
+     * @param predicate Predicate, applied to the carrier values, determining
+     *                  whether there was a match
+     * @param components the {@code component} method handles
+     * @return the {@linkplain Extractor}
+     */
+    public static Extractor ofCarrierPartial(MethodHandles.Lookup lookup, String constantName, Class<Extractor> constantType,
+                                             MethodType descriptor,
+                                             MethodHandle carrierFactory,
+                                             MethodHandle digester,
+                                             MethodHandle predicate,
+                                             MethodHandle... components) {
+        return ofCarrierPartial(descriptor, carrierFactory, digester, predicate, components);
+    }
+
+    /**
+     * Bootstrap for extracting the {@code tryMatch} method handle from a {@linkplain Extractor}
+     *
+     * @param lookup ignored
+     * @param constantName ignored
+     * @param constantType Must be {@code MethodHandle.class}
+     * @param extractor the {@linkplain Extractor}
+     * @return the {@code tryMatch} method handle
+     */
+    public static MethodHandle extractorTryMatch(MethodHandles.Lookup lookup, String constantName, Class<MethodHandle> constantType,
+                                                 Extractor extractor) {
+        return extractor.tryMatch();
+    }
+
+    /**
+     * Bootstrap for extracting a {@code component} method handle from a {@linkplain Extractor}
+     *
+     * @param lookup ignored
+     * @param constantName ignored
+     * @param constantType Must be {@code MethodHandle.class}
+     * @param extractor the {@linkplain Extractor}
+     * @param i the component index
+     * @return the {@code component} method handle
+     */
+    public static MethodHandle extractorComponent(MethodHandles.Lookup lookup, String constantName, Class<MethodHandle> constantType,
+                                                  Extractor extractor, int i) {
+        return extractor.component(i);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/compiler/ExtractorImpl.java	Fri Mar 30 15:33:52 2018 -0400
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package java.lang.compiler;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+import java.util.List;
+
+/**
+ * Non-public implementation of {@link Extractor}
+ */
+class ExtractorImpl implements Extractor {
+    private final MethodHandle tryMatch;
+    private final List<MethodHandle> components;
+    private final MethodType descriptor;
+
+    /**
+     * Construct an {@link Extractor} from components
+     * Constraints:
+     *  - output of tryMatch must match input of components
+     *  - input of tryMatch must match descriptor
+     *  - output of components must match descriptor
+     *
+     * @param descriptor The {@code descriptor} method type
+     * @param tryMatch The {@code tryMatch} method handle
+     * @param components The {@code component} method handles
+     */
+    ExtractorImpl(MethodType descriptor, MethodHandle tryMatch, MethodHandle[] components) {
+        Class<?> carrierType = tryMatch.type().returnType();
+        if (descriptor.parameterCount() != components.length)
+            throw new IllegalArgumentException(String.format("MethodType %s arity should match component count %d", descriptor, components.length));
+        if (!descriptor.returnType().equals(tryMatch.type().parameterType(0)))
+            throw new IllegalArgumentException(String.format("Descriptor %s should match tryMatch input %s", descriptor, tryMatch.type()));
+        for (int i = 0; i < components.length; i++) {
+            MethodHandle component = components[i];
+            if (component.type().parameterCount() != 1
+                || component.type().returnType().equals(void.class)
+                || !component.type().parameterType(0).equals(carrierType))
+                throw new IllegalArgumentException("Invalid component descriptor " + component.type());
+            if (!component.type().returnType().equals(descriptor.parameterType(i)))
+                throw new IllegalArgumentException(String.format("Descriptor %s should match %d'th component %s", descriptor, i, component));
+        }
+
+        this.descriptor = descriptor;
+        this.tryMatch = tryMatch;
+        this.components = List.of(components);
+    }
+
+    @Override
+    public MethodHandle tryMatch() {
+        return tryMatch;
+    }
+
+    @Override
+    public MethodHandle component(int i) {
+        return components.get(i);
+    }
+
+    @Override
+    public MethodType descriptor() {
+        return descriptor;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/compiler/PatternCarriers.java	Fri Mar 30 15:33:52 2018 -0400
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package java.lang.compiler;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+/**
+ * PatternCarriers
+ */
+public class PatternCarriers {
+    private static final MethodHandle CARRIER_CTOR;
+    private static final MethodHandle CARRIER_GET;
+    static {
+        try {
+            CARRIER_CTOR = MethodHandles.lookup().findConstructor(DumbCarrier.class, MethodType.methodType(void.class, Object[].class));
+            CARRIER_GET = MethodHandles.lookup().findVirtual(DumbCarrier.class, "get", MethodType.methodType(Object.class, int.class));
+        }
+        catch (ReflectiveOperationException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
+    /**
+     * Returns a method handle with the given method type that instantiates
+     * a new carrier object.
+     *
+     * @param methodType the types of the carrier elements
+     * @return the carrier factory
+     */
+    public static MethodHandle carrierFactory(MethodType methodType) {
+        return CARRIER_CTOR.asType(methodType.changeReturnType(Object.class));
+    }
+
+    /**
+     * Returns a method handle that accepts a carrier and returns the i'th component
+     *
+     * @param methodType the type of the carrier elements
+     * @param i the index of the component
+     * @return the component method handle
+     */
+    public static MethodHandle carrierComponent(MethodType methodType, int i) {
+        return MethodHandles.insertArguments(CARRIER_GET, 1, i)
+                .asType(MethodType.methodType(methodType.parameterType(i), Object.class));
+    }
+
+    static class DumbCarrier {
+        Object[] args;
+
+        DumbCarrier(Object... args) {
+            this.args = args.clone();
+        }
+
+        Object get(int i) {
+            return args[i];
+        }
+    }
+}
--- a/src/java.base/share/classes/module-info.java	Mon Mar 26 12:11:12 2018 -0400
+++ b/src/java.base/share/classes/module-info.java	Fri Mar 30 15:33:52 2018 -0400
@@ -79,6 +79,7 @@
     exports java.io;
     exports java.lang;
     exports java.lang.annotation;
+    exports java.lang.compiler;
     exports java.lang.invoke;
     exports java.lang.module;
     exports java.lang.ref;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/compiler/ExtractorTest.java	Fri Mar 30 15:33:52 2018 -0400
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2012, 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.
+ */
+import java.lang.compiler.Extractor;
+import java.lang.compiler.PatternCarriers;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNotSame;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+
+/**
+ * @test
+ * @run testng ExtractorTest
+ * @summary unit tests for java.lang.compiler.Extractor
+ */
+@Test
+public class ExtractorTest {
+
+    private Object[] extract(Extractor extractor, Object target) throws Throwable {
+        int count = extractor.descriptor().parameterCount();
+        Object[] result = new Object[count + 1];
+        Object carrier = extractor.tryMatch().invoke(target);
+        if (carrier == null)
+            return null;
+        for (int i=0; i<count; i++)
+            result[i] = extractor.component(i).invoke(carrier);
+        result[count] = carrier;
+        return result;
+    }
+
+    private void assertExtracted(Object[] result, Object... args) {
+        assertNotNull(result);
+        assertEquals(result.length - 1, args.length);
+        for (int i = 0; i < args.length; i++) {
+            assertEquals(result[i], args[i]);
+        }
+    }
+
+    private void testExtractLazy(Extractor e, Object target, Object... args) throws Throwable {
+        Object[] result = extract(e, target);
+        assertExtracted(result, args);
+        assertSame(carrier(result), target);
+    }
+
+    private void testExtractEager(Extractor e, Object target, Object... args) throws Throwable {
+        Object[] result = extract(e, target);
+        assertExtracted(result, args);
+        assertNotSame(carrier(result), target);
+    }
+
+    private void assertExtractFail(Extractor e, Object target) throws Throwable {
+        Object[] result = extract(e, target);
+        assertNull(result);
+    }
+
+    private Object carrier(Object[] result) {
+        return result[result.length-1];
+    }
+
+    private static class TestClass {
+        static MethodHandle MH_S, MH_I, MH_L, MH_B;
+        static MethodHandle CONSTRUCTOR;
+        static MethodHandle COPIER;
+        static MethodHandle DIGESTER;
+        static MethodHandle NON_NULL;
+        static MethodHandle NON_NULL_EXPLODED;
+        static MethodType TYPE = MethodType.methodType(TestClass.class, String.class, int.class, long.class, byte.class);
+        static {
+            try {
+                MH_B = MethodHandles.lookup().findGetter(TestClass.class, "b", byte.class);
+                MH_S = MethodHandles.lookup().findGetter(TestClass.class, "s", String.class);
+                MH_I = MethodHandles.lookup().findGetter(TestClass.class, "i", int.class);
+                MH_L = MethodHandles.lookup().findGetter(TestClass.class, "l", long.class);
+                CONSTRUCTOR = MethodHandles.lookup().findConstructor(TestClass.class, TYPE.changeReturnType(void.class));
+                COPIER = MethodHandles.lookup().findVirtual(TestClass.class, "copy", MethodType.methodType(TestClass.class));
+                NON_NULL = MethodHandles.lookup().findVirtual(TestClass.class, "test", MethodType.methodType(boolean.class));
+                NON_NULL_EXPLODED = MethodHandles.lookup().findStatic(TestClass.class, "testExploded", MethodType.methodType(boolean.class, String.class, int.class, long.class, byte.class));
+                DIGESTER = MethodHandles.lookup().findVirtual(TestClass.class, "digest", MethodType.methodType(Object.class, MethodHandle.class));
+            }
+            catch (ReflectiveOperationException e) {
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+
+        String s;
+        int i;
+        long l;
+        byte b;
+
+        public TestClass(String s, int i, long l, byte b) {
+            this.s = s;
+            this.i = i;
+            this.l = l;
+            this.b = b;
+        }
+
+        TestClass copy() {
+            return new TestClass(s, i, l, b);
+        }
+
+        boolean test() {
+            return s != null;
+        }
+
+        static boolean testExploded(String s, int i, long l, byte b) {
+            return s != null;
+        }
+
+        Object digest(MethodHandle target) throws Throwable {
+            return target.invoke(s, i, l, b);
+        }
+    }
+
+    static final MethodHandle[] COMPONENTS = { TestClass.MH_S, TestClass.MH_I, TestClass.MH_L, TestClass.MH_B };
+    static final MethodHandle CARRIER_FACTORY = PatternCarriers.carrierFactory(TestClass.TYPE);
+    static final MethodHandle[] CARRIER_COMPONENTS = { PatternCarriers.carrierComponent(TestClass.TYPE, 0),
+                                                       PatternCarriers.carrierComponent(TestClass.TYPE, 1),
+                                                       PatternCarriers.carrierComponent(TestClass.TYPE, 2),
+                                                       PatternCarriers.carrierComponent(TestClass.TYPE, 3) };
+
+    public void testLazySelfTotal() throws Throwable {
+        Extractor e = Extractor.ofLazy(TestClass.TYPE, COMPONENTS);
+        testExtractLazy(e, new TestClass("foo", 3, 4L, (byte) 5),
+                        "foo", 3, 4L, (byte) 5);
+        testExtractLazy(e, new TestClass(null, 0, 0L, (byte) 0),
+                        null, 0, 0L, (byte) 0);
+    }
+
+    public void testEagerSelfTotal() throws Throwable {
+        Extractor e = Extractor.of(TestClass.TYPE, TestClass.COPIER, COMPONENTS);
+        testExtractEager(e, new TestClass("foo", 3, 4L, (byte) 5),
+                        "foo", 3, 4L, (byte) 5);
+        testExtractEager(e, new TestClass(null, 0, 0L, (byte) 0),
+                        null, 0, 0L, (byte) 0);
+    }
+
+    public void testLazySelfPartial() throws Throwable {
+        Extractor e = Extractor.ofLazyPartial(TestClass.TYPE, TestClass.NON_NULL, COMPONENTS);
+        testExtractLazy(e, new TestClass("foo", 3, 4L, (byte) 5),
+                        "foo", 3, 4L, (byte) 5);
+        assertExtractFail(e, new TestClass(null, 0, 0L, (byte) 0));
+    }
+
+    public void testEagerSelfPartial() throws Throwable {
+        Extractor e = Extractor.ofPartial(TestClass.TYPE, TestClass.COPIER, TestClass.NON_NULL, COMPONENTS);
+        testExtractEager(e, new TestClass("foo", 3, 4L, (byte) 5),
+                        "foo", 3, 4L, (byte) 5);
+        assertExtractFail(e, new TestClass(null, 0, 0L, (byte) 0));
+    }
+
+    public void testCarrierTotal() throws Throwable {
+        Extractor e = Extractor.ofCarrier(TestClass.TYPE, CARRIER_FACTORY, TestClass.DIGESTER, CARRIER_COMPONENTS);
+        testExtractEager(e, new TestClass("foo", 3, 4L, (byte) 5),
+                        "foo", 3, 4L, (byte) 5);
+        testExtractEager(e, new TestClass(null, 0, 0L, (byte) 0),
+                        null, 0, 0L, (byte) 0);
+    }
+
+    public void testCarrierPartial() throws Throwable {
+        Extractor e = Extractor.ofCarrierPartial(TestClass.TYPE, CARRIER_FACTORY, TestClass.DIGESTER, TestClass.NON_NULL_EXPLODED, CARRIER_COMPONENTS);
+        testExtractEager(e, new TestClass("foo", 3, 4L, (byte) 5),
+                        "foo", 3, 4L, (byte) 5);
+        assertExtractFail(e, new TestClass(null, 0, 0L, (byte) 0));
+    }
+
+    public void testClamshell() throws Throwable {
+        Extractor e = Extractor.ofCarrier(TestClass.TYPE, CARRIER_FACTORY, TestClass.DIGESTER, CARRIER_COMPONENTS);
+        MethodHandle mh = e.compose(TestClass.CONSTRUCTOR);
+        TestClass target = new TestClass("foo", 3, 4L, (byte) 5);
+        Object o = mh.invoke(target);
+        assertTrue(o instanceof TestClass);
+        TestClass copy = (TestClass) o;
+        assertEquals(target.s, copy.s);
+        assertEquals(target.i, copy.i);
+        assertEquals(target.l, copy.l);
+        assertEquals(target.b, copy.b);
+    }
+}