Package com.headius.invokebinder

Source Code of com.headius.invokebinder.Binder

/*
* Copyright 2012-2014 headius.
*
* 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 com.headius.invokebinder;

import com.headius.invokebinder.transform.Cast;
import com.headius.invokebinder.transform.Catch;
import com.headius.invokebinder.transform.Collect;
import com.headius.invokebinder.transform.Convert;
import com.headius.invokebinder.transform.Drop;
import com.headius.invokebinder.transform.Filter;
import com.headius.invokebinder.transform.FilterReturn;
import com.headius.invokebinder.transform.Fold;
import com.headius.invokebinder.transform.Insert;
import com.headius.invokebinder.transform.Permute;
import com.headius.invokebinder.transform.Spread;
import com.headius.invokebinder.transform.Transform;
import com.headius.invokebinder.transform.TryFinally;
import com.headius.invokebinder.transform.Varargs;

import java.io.PrintStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import java.util.logging.Logger;

/**
* The Binder class provides a DSL for building a chain of MethodHandles using
* various of the adaptations provided by java.lang.invoke.MethodHandles. The
* transformations are pushed into a stack, allowing the DSL to operate forward
* from an incoming signature rather than backward from a target handle. This
* is often conceptually easier to understand, and certainly easier to read.
*
* The transformations are also applied simultaneously to the starting
* java.lang.invoke.MethodType, allowing Binder to check at each step whether
* the adaptation is valid.
*
* Here's a typical use, starting with a signature that takes two Strings and
* returns a String, dropping and inserting arguments, casting to a target
* signature, and finally calling a target handle with that signature.
*
* <pre>
* MethodHandle mh = Binder
*     .from(String.class, String.class, String.class) // String w(String, String)
*     .drop(1, String.class) // String x(String)
*     .insert(0, 'hello') // String y(String, String)
*     .cast(String.class, CharSequence.class, Object.class) // String z(CharSequence, Object)String
*     .invoke(someTargetHandle);
* </pre>
*/
public class Binder {

    private final Logger logger = Logger.getLogger("Invoke Binder");
    private final List<Transform> transforms = new ArrayList();
    private final List<MethodType> types = new ArrayList();
    private final MethodType start;
    private final MethodHandles.Lookup lookup;

    /**
     * Construct a new Binder, starting from a given MethodType. Use a public
     * java.lang.invoke.MethodHandles.Lookup for retrieving direct handles.
     *
     * @param start the starting MethodType, for calls entering the eventual chain
     */
    public Binder(MethodType start) {
        this.start = start;
        this.types.add(0, start);
        this.lookup = MethodHandles.publicLookup();
    }

    /**
     * Construct a new Binder, starting from a given Lookup and MethodType.
     *
     * @param lookup the Lookup context to use for direct handles
     * @param start  the starting MethodType, for calls entering the eventual chain
     */
    public Binder(MethodHandles.Lookup lookup, MethodType start) {
        this.start = start;
        this.types.add(0, start);
        this.lookup = lookup;
    }

    /**
     * Construct a new Binder using the given invokebinder.
     *
     * @param source a Binder to duplicate
     */
    public Binder(Binder source) {
        this.start = source.start;
        this.types.addAll(source.types);
        this.transforms.addAll(source.transforms);
        this.lookup = source.lookup;
    }

    /**
     * Construct a new Binder using the given Lookup and invokebinder.
     *
     * @param lookup the Lookup context to use for direct handles
     * @param source the source Binder
     */
    public Binder(MethodHandles.Lookup lookup, Binder source) {
        this.start = source.start;
        this.types.addAll(source.types);
        this.transforms.addAll(source.transforms);
        this.lookup = lookup;
    }

    /**
     * Construct a new Binder using the given invokebinder plus an additional transform
     *
     * @param source    the source Binder
     * @param transform the additional Transform
     */
    public Binder(Binder source, Transform transform) {
        this.start = source.start;
        this.types.addAll(source.types);
        this.transforms.addAll(source.transforms);
        add(transform);
        this.lookup = source.lookup;
    }

    /**
     * Construct a new Binder using the given invokebinder plus an additional transform and current type
     *
     * @param source    the source Binder
     * @param transform the additional Transform
     * @param type      the new current type resulting from the transform
     */
    public Binder(Binder source, Transform transform, MethodType type) {
        this.start = source.start;
        this.types.addAll(source.types);
        this.transforms.addAll(source.transforms);
        add(transform, type);
        this.lookup = source.lookup;
    }

    /**
     * Construct a new Binder, starting from a given MethodType.
     *
     * @param start the starting MethodType, for calls entering the eventual chain
     * @return the Binder object
     */
    public static Binder from(MethodType start) {
        return new Binder(start);
    }

    /**
     * Construct a new Binder, starting from a given MethodType.
     *
     * @param lookup the Lookup context to use for direct handles
     * @param start  the starting MethodType, for calls entering the eventual chain
     * @return the Binder object
     */
    public static Binder from(MethodHandles.Lookup lookup, MethodType start) {
        return new Binder(lookup, start);
    }

    /**
     * Construct a new Binder using a return type.
     *
     * @param returnType the return type of the incoming signature
     * @return the Binder object
     */
    public static Binder from(Class returnType) {
        return from(MethodType.methodType(returnType));
    }

    /**
     * Construct a new Binder using a return type.
     *
     * @param lookup     the Lookup context to use for direct handles
     * @param returnType the return type of the incoming signature
     * @return the Binder object
     */
    public static Binder from(MethodHandles.Lookup lookup, Class returnType) {
        return from(lookup, MethodType.methodType(returnType));
    }

    /**
     * Construct a new Binder using a return type and argument types.
     *
     * @param returnType the return type of the incoming signature
     * @param argTypes   the argument types of the incoming signature
     * @return the Binder object
     */
    public static Binder from(Class returnType, Class[] argTypes) {
        return from(MethodType.methodType(returnType, argTypes));
    }

    /**
     * Construct a new Binder using a return type and argument types.
     *
     * @param lookup     the Lookup context to use for direct handles
     * @param returnType the return type of the incoming signature
     * @param argTypes   the argument types of the incoming signature
     * @return the Binder object
     */
    public static Binder from(MethodHandles.Lookup lookup, Class returnType, Class[] argTypes) {
        return from(lookup, MethodType.methodType(returnType, argTypes));
    }

    /**
     * Construct a new Binder using a return type and argument types.
     *
     * @param returnType the return type of the incoming signature
     * @param argType0   the first argument type of the incoming signature
     * @param argTypes   the remaining argument types of the incoming signature
     * @return the Binder object
     */
    public static Binder from(Class returnType, Class argType0, Class... argTypes) {
        return from(MethodType.methodType(returnType, argType0, argTypes));
    }

    /**
     * Construct a new Binder using a return type and argument types.
     *
     * @param lookup     the Lookup context to use for direct handles
     * @param returnType the return type of the incoming signature
     * @param argType0   the first argument type of the incoming signature
     * @param argTypes   the remaining argument types of the incoming signature
     * @return the Binder object
     */
    public static Binder from(MethodHandles.Lookup lookup, Class returnType, Class argType0, Class... argTypes) {
        return from(lookup, MethodType.methodType(returnType, argType0, argTypes));
    }

    /**
     * Construct a new Binder, starting from a given invokebinder.
     *
     * @param start the starting invokebinder; the new one will start with the current endpoint type
     *              of the given invokebinder
     * @return the Binder object
     */
    public static Binder from(Binder start) {
        return new Binder(start);
    }

    /**
     * Construct a new Binder, starting from a given invokebinder.
     *
     * @param lookup the Lookup context to use for direct handles
     * @param start  the starting invokebinder; the new one will start with the current endpoint type
     *               of the given invokebinder
     * @return the Binder object
     */
    public static Binder from(MethodHandles.Lookup lookup, Binder start) {
        return new Binder(lookup, start);
    }

    /**
     * Join this binder to an existing one by applying its transformations after
     * this one.
     *
     * @param other the Binder containing the set of transformations to append
     * @return a new Binder combining this Binder with the target Binder
     */
    public Binder to(Binder other) {
        assert type().equals(other.start);

        Binder newBinder = new Binder(this);

        for (ListIterator<Transform> iter = other.transforms.listIterator(other.transforms.size()); iter.hasPrevious(); ) {
            Transform t = iter.previous();
            newBinder.add(t);
        }

        return newBinder;
    }

    /**
     * Use an alternate java.lang.invoke.MethodHandles.Lookup as the default for
     * any direct handles created.
     *
     * @param lookup the new Lookup context to use
     * @return a new Binder with the given lookup
     */
    public Binder withLookup(MethodHandles.Lookup lookup) {
        return from(lookup, this);
    }

    /**
     * Add a Transform to the chain.
     *
     * @param transform
     */
    private void add(Transform transform) {
        add(transform, transform.down(types.get(0)));
    }

    /**
     * Add a Transform with an associated MethodType target to the chain.
     *
     * @param transform
     * @param target
     */
    private void add(Transform transform, MethodType target) {
        types.add(0, target);
        transforms.add(0, transform);
    }

    /**
     * The current MethodType, were the handle chain to terminate at this point.
     *
     * @return the current MethodType
     */
    public MethodType type() {
        return types.get(0);
    }

    /**
     * Println the current MethodType to the given stream.
     *
     * @param ps a PrintStream to which to println the current MethodType
     * @return this Binding
     */
    public Binder printType(PrintStream ps) {
        ps.println(types.get(0));
        return this;
    }

    /**
     * Println the current MethodType to stdout.
     *
     * @return this Binding
     */
    public Binder printType() {
        return printType(System.out);
    }

    /**
     * Log the current MethodType as info.
     *
     * @return this Binding
     */
    public Binder logType() {
        logger.info(types.get(0).toString());
        return this;
    }

    /**
     * Insert at the given index the given boolean value.
     *
     * @param index the index at which to insert the argument value
     * @param value the value to insert
     * @return a new Binder
     */
    public Binder insert(int index, boolean value) {
        return new Binder(this, new Insert(index, value));
    }

    /**
     * Insert at the given index the given byte value.
     *
     * @param index the index at which to insert the argument value
     * @param value the value to insert
     * @return a new Binder
     */
    public Binder insert(int index, byte value) {
        return new Binder(this, new Insert(index, value));
    }

    /**
     * Insert at the given index the given short value.
     *
     * @param index the index at which to insert the argument value
     * @param value the value to insert
     * @return a new Binder
     */
    public Binder insert(int index, short value) {
        return new Binder(this, new Insert(index, value));
    }

    /**
     * Insert at the given index the given char value.
     *
     * @param index the index at which to insert the argument value
     * @param value the value to insert
     * @return a new Binder
     */
    public Binder insert(int index, char value) {
        return new Binder(this, new Insert(index, value));
    }

    /**
     * Insert at the given index the given int value.
     *
     * @param index the index at which to insert the argument value
     * @param value the value to insert
     * @return a new Binder
     */
    public Binder insert(int index, int value) {
        return new Binder(this, new Insert(index, value));
    }

    /**
     * Insert at the given index the given long value.
     *
     * @param index the index at which to insert the argument value
     * @param value the value to insert
     * @return a new Binder
     */
    public Binder insert(int index, long value) {
        return new Binder(this, new Insert(index, value));
    }

    /**
     * Insert at the given index the given float value.
     *
     * @param index the index at which to insert the argument value
     * @param value the value to insert
     * @return a new Binder
     */
    public Binder insert(int index, float value) {
        return new Binder(this, new Insert(index, value));
    }

    /**
     * Insert at the given index the given double value.
     *
     * @param index the index at which to insert the argument value
     * @param value the value to insert
     * @return a new Binder
     */
    public Binder insert(int index, double value) {
        return new Binder(this, new Insert(index, value));
    }

    /**
     * Insert at the given index the given argument value(s).
     *
     * @param index  the index at which to insert the argument value
     * @param values the value(s) to insert
     * @return a new Binder
     */
    public Binder insert(int index, Object... values) {
        return new Binder(this, new Insert(index, values));
    }

    /**
     * Insert at the given index the given argument value.
     *
     * @param index the index at which to insert the argument value
     * @param type  the actual type to use, rather than getClass
     * @param value the value to insert
     * @return a new Binder
     */
    public Binder insert(int index, Class type, Object value) {
        return new Binder(this, new Insert(index, new Class[]{type}, value));
    }

    /**
     * Insert at the given index the given argument value(s).
     *
     * @param index  the index at which to insert the argument value
     * @param types  the actual types to use, rather than getClass
     * @param values the value(s) to insert
     * @return a new Binder
     */
    public Binder insert(int index, Class[] types, Object... values) {
        return new Binder(this, new Insert(index, types, values));
    }

    /**
     * Append to the argument list the given boolean value.
     *
     * @param value the value to append
     * @return a new Binder
     */
    public Binder append(boolean value) {
        return new Binder(this, new Insert(type().parameterCount(), value));
    }

    /**
     * Append to the argument list the given byte value.
     *
     * @param value the value to append
     * @return a new Binder
     */
    public Binder append(byte value) {
        return new Binder(this, new Insert(type().parameterCount(), value));
    }

    /**
     * Append to the argument list the given short value.
     *
     * @param value the value to append
     * @return a new Binder
     */
    public Binder append(short value) {
        return new Binder(this, new Insert(type().parameterCount(), value));
    }

    /**
     * Append to the argument list the given char value.
     *
     * @param value the value to append
     * @return a new Binder
     */
    public Binder append(char value) {
        return new Binder(this, new Insert(type().parameterCount(), value));
    }

    /**
     * Append to the argument list the given int value.
     *
     * @param value the value to append
     * @return a new Binder
     */
    public Binder append(int value) {
        return new Binder(this, new Insert(type().parameterCount(), value));
    }

    /**
     * Append to the argument list the given long value.
     *
     * @param value the value to append
     * @return a new Binder
     */
    public Binder append(long value) {
        return new Binder(this, new Insert(type().parameterCount(), value));
    }

    /**
     * Append to the argument list the given float value.
     *
     * @param value the value to append
     * @return a new Binder
     */
    public Binder append(float value) {
        return new Binder(this, new Insert(type().parameterCount(), value));
    }

    /**
     * Append to the argument list the given double value.
     *
     * @param value the value to append
     * @return a new Binder
     */
    public Binder append(double value) {
        return new Binder(this, new Insert(type().parameterCount(), value));
    }

    /**
     * Append to the argument list the given argument value(s).
     *
     * @param values the value(s) to append
     * @return a new Binder
     */
    public Binder append(Object... values) {
        return new Binder(this, new Insert(type().parameterCount(), values));
    }

    /**
     * Prepend to the argument list the given boolean value.
     *
     * @param value the value to prepend
     * @return a new Binder
     */
    public Binder prepend(boolean value) {
        return new Binder(this, new Insert(0, value));
    }

    /**
     * Prepend to the argument list the given byte value.
     *
     * @param value the value to prepend
     * @return a new Binder
     */
    public Binder prepend(byte value) {
        return new Binder(this, new Insert(0, value));
    }

    /**
     * Prepend to the argument list the given short value.
     *
     * @param value the value to prepend
     * @return a new Binder
     */
    public Binder prepend(short value) {
        return new Binder(this, new Insert(0, value));
    }

    /**
     * Prepend to the argument list the given char value.
     *
     * @param value the value to prepend
     * @return a new Binder
     */
    public Binder prepend(char value) {
        return new Binder(this, new Insert(0, value));
    }

    /**
     * Prepend to the argument list the given int value.
     *
     * @param value the value to prepend
     * @return a new Binder
     */
    public Binder prepend(int value) {
        return new Binder(this, new Insert(0, value));
    }

    /**
     * Prepend to the argument list the given long value.
     *
     * @param value the value to prepend
     * @return a new Binder
     */
    public Binder prepend(long value) {
        return new Binder(this, new Insert(0, value));
    }

    /**
     * Prepend to the argument list the given float value.
     *
     * @param value the value to prepend
     * @return a new Binder
     */
    public Binder prepend(float value) {
        return new Binder(this, new Insert(0, value));
    }

    /**
     * Prepend to the argument list the given double value.
     *
     * @param value the value to prepend
     * @return a new Binder
     */
    public Binder prepend(double value) {
        return new Binder(this, new Insert(0, value));
    }

    /**
     * Prepend to the argument list the given argument value(s).
     *
     * @param values the value(s) to prepend
     * @return a new Binder
     */
    public Binder prepend(Object... values) {
        return new Binder(this, new Insert(0, values));
    }

    /**
     * Append to the argument list the given argument value with the specified type.
     *
     * @param type  the actual type to use, rather than getClass
     * @param value the value to append
     * @return a new Binder
     */
    public Binder append(Class type, Object value) {
        return new Binder(this, new Insert(type().parameterCount(), new Class[]{type}, value));
    }

    /**
     * Append to the argument list the given argument values with the specified types.
     *
     * @param types  the actual types to use, rather than getClass
     * @param values the value(s) to append
     * @return a new Binder
     */
    public Binder append(Class[] types, Object... values) {
        return new Binder(this, new Insert(type().parameterCount(), types, values));
    }

    /**
     * Prepend to the argument list the given argument value with the specified type
     *
     * @param type  the actual type to use, rather than getClass
     * @param value the value(s) to prepend
     * @return a new Binder
     */
    public Binder prepend(Class type, Object value) {
        return new Binder(this, new Insert(0, new Class[]{type}, value));
    }

    /**
     * Prepend to the argument list the given argument values with the specified types.
     *
     * @param types  the actual types to use, rather than getClass
     * @param values the value(s) to prepend
     * @return a new Binder
     */
    public Binder prepend(Class[] types, Object... values) {
        return new Binder(this, new Insert(0, types, values));
    }

    /**
     * Drop a single argument at the given index.
     *
     * @param index the index at which to drop an argument
     * @return a new Binder
     */
    public Binder drop(int index) {
        return drop(index, 1);
    }

    /**
     * Drop from the given index a number of arguments.
     *
     * @param index the index at which to start dropping
     * @param count the number of arguments to drop
     * @return a new Binder
     */
    public Binder drop(int index, int count) {
        return new Binder(this, new Drop(index, Arrays.copyOfRange(type().parameterArray(), index, index + count)));
    }

    /**
     * Drop a single argument at the end of the argument list.
     *
     * @return a new Binder
     */
    public Binder dropLast() {
        return dropLast(1);
    }

    /**
     * Drop from the end of the argument list a number of arguments.
     *
     * @param count the number of arguments to drop
     * @return a new Binder
     */
    public Binder dropLast(int count) {
        assert count <= type().parameterCount();

        return drop(type().parameterCount() - count, count);
    }

    /**
     * Drop a single argument at the beginning of the argument list.
     *
     * @return a new Binder
     */
    public Binder dropFirst() {
        return dropFirst(1);
    }

    /**
     * Drop from the end of the argument list a number of arguments.
     *
     * @param count the number of arguments to drop
     * @return a new Binder
     */
    public Binder dropFirst(int count) {
        assert count <= type().parameterCount();

        return drop(0, count);
    }

    /**
     * Drop all arguments from this handle chain
     *
     * @return a new Binder
     */
    public Binder dropAll() {
        return drop(0, type().parameterCount());
    }

    /**
     * Convert the incoming arguments to the given MethodType. The conversions
     * applied are equivalent to those in MethodHandle.asType(MethodType).
     *
     * @param target the target MethodType
     * @return a new Binder
     */
    public Binder convert(MethodType target) {
        return new Binder(this, new Convert(type()), target);
    }

    /**
     * Convert the incoming arguments to the given MethodType. The conversions
     * applied are equivalent to those in MethodHandle.asType(MethodType).
     *
     * @param returnType the target return type
     * @param argTypes   the target argument types
     * @return a new Binder
     */
    public Binder convert(Class returnType, Class... argTypes) {
        return new Binder(this, new Convert(type()), MethodType.methodType(returnType, argTypes));
    }

    /**
     * Cast the incoming arguments to the given MethodType. The casts
     * applied are equivalent to those in MethodHandles.explicitCastArguments(mh, MethodType).
     *
     * @param type the target MethodType
     * @return a new Binder
     */
    public Binder cast(MethodType type) {
        return new Binder(this, new Cast(type()), type);
    }

    /**
     * Cast the incoming arguments to the given MethodType. The casts
     * applied are equivalent to those in MethodHandle.explicitCastArguments(MethodType).
     *
     * @param returnType the target return type
     * @param argTypes   the target argument types
     * @return a new Binder
     */
    public Binder cast(Class returnType, Class... argTypes) {
        return new Binder(this, new Cast(type()), MethodType.methodType(returnType, argTypes));
    }

    /**
     * Cast the incoming arguments to the given MethodType. The casts
     * applied are equivalent to those in MethodHandle.explicitCastArguments(MethodType).
     *
     * @param returnType the target return type
     * @param firstType  the first argument type, usually a target type
     * @param restTypes  the remaining target argument types
     * @return a new Binder
     */
    public Binder castVirtual(Class returnType, Class firstType, Class... restTypes) {
        return new Binder(this, new Cast(type()), MethodType.methodType(returnType, firstType, restTypes));
    }

    /**
     * Spread a trailing array argument into the specified argument types.
     *
     * @param spreadTypes the types into which to spread the incoming Object[]
     * @return a new Binder
     */
    public Binder spread(Class... spreadTypes) {
        if (spreadTypes.length == 0) {
            return dropLast();
        }

        return new Binder(this, new Spread(type(), spreadTypes));
    }

    /**
     * Spread a trailing array argument into the given number of arguments of
     * the type of the array.
     *
     * @param count the new count of arguments to spread from the trailing array
     * @return a new Binder
     */
    public Binder spread(int count) {
        if (count == 0) {
            return dropLast();
        }

        Class aryType = type().parameterType(type().parameterCount() - 1);

        assert aryType.isArray();

        Class[] spreadTypes = new Class[count];
        Arrays.fill(spreadTypes, aryType.getComponentType());

        return spread(spreadTypes);
    }

    /**
     * Box all incoming arguments from the given position onward into the given array type.
     *
     * @param index the index from which to start boxing args
     * @param type  the array type into which the args will be boxed
     * @return a new Binder
     */
    public Binder collect(int index, Class type) {
        return new Binder(this, new Collect(type(), index, type));
    }

    /**
     * Box a range of incoming arguments into the given array type.
     *
     * @param index the index from which to start boxing args
     * @param count the count of arguments to box
     * @param type  the array type into which the args will be boxed
     * @return a new Binder
     */
    public Binder collect(int index, int count, Class type) {
        return new Binder(this, new Collect(type(), index, count, type));
    }

    /**
     * Box all incoming arguments from the given position onward into the given array type.
     * This version accepts a variable number of incoming arguments.
     *
     * @param index the index from which to start boxing args
     * @param type  the array type into which the args will be boxed
     * @return a new Binder
     */
    public Binder varargs(int index, Class type) {
        return new Binder(this, new Varargs(type(), index, type));
    }

    /**
     * Permute the incoming arguments to a new sequence specified by the given values.
     *
     * Arguments may be duplicated or dropped in this sequence.
     *
     * @param reorder the int offsets of the incoming arguments in the desired permutation
     * @return a new Binder
     */
    public Binder permute(int... reorder) {
        return new Binder(this, new Permute(type(), reorder));
    }

    /**
     * Process the incoming arguments using the given handle, inserting the result
     * as the first argument.
     *
     * @param function the function that will process the incoming arguments. Its
     *                 signature must match the current signature's arguments exactly.
     * @return a new Binder
     */
    public Binder fold(MethodHandle function) {
        return new Binder(this, new Fold(function));
    }

    public Binder foldVoid(MethodHandle function) {
        if (type().returnType() == void.class) {
            return fold(function);
        } else {
            return fold(function.asType(function.type().changeReturnType(void.class)));
        }
    }

    /**
     * Process the incoming arguments by calling the given static method on the
     * given class, inserting the result as the first argument.
     *
     * @param lookup the java.lang.invoke.MethodHandles.Lookup to use
     * @param target the class on which the method is defined
     * @param method the method to invoke on the first argument
     * @return a new Binder
     */
    public Binder foldStatic(MethodHandles.Lookup lookup, Class target, String method) {
        return fold(Binder.from(type()).invokeStaticQuiet(lookup, target, method));
    }

    /**
     * Process the incoming arguments by calling the given static method on the
     * given class, inserting the result as the first argument.
     *
     * @param target the class on which the method is defined
     * @param method the method to invoke on the first argument
     * @return a new Binder
     */
    public Binder foldStatic(Class target, String method) {
        return foldStatic(lookup, target, method);
    }

    /**
     * Process the incoming arguments by calling the given method on the first
     * argument, inserting the result as the first argument.
     *
     * @param lookup the java.lang.invoke.MethodHandles.Lookup to use
     * @param method the method to invoke on the first argument
     * @return a new Binder
     */
    public Binder foldVirtual(MethodHandles.Lookup lookup, String method) {
        return fold(Binder.from(type()).printType().invokeVirtualQuiet(lookup, method));
    }

    /**
     * Process the incoming arguments by calling the given method on the first
     * argument, inserting the result as the first argument.
     *
     * @param method the method to invoke on the first argument
     * @return a new Binder
     */
    public Binder foldVirtual(String method) {
        return foldVirtual(lookup, method);
    }

    /**
     * Filter incoming arguments, starting at the given index, replacing each with the
     * result of calling the associated function in the given list.
     *
     * @param index     the index of the first argument to filter
     * @param functions the array of functions to transform the arguments
     * @return a new Binder
     */
    public Binder filter(int index, MethodHandle... functions) {
        return new Binder(this, new Filter(index, functions));
    }

    /**
     * Filter return value, using a function that produces the current return type
     * from another type. The new endpoint will have the return value that the
     * filter function accepts as an argument.
     *
     * @param function the array of functions to transform the arguments
     * @return a new Binder
     */
    public Binder filterReturn(MethodHandle function) {
        return new Binder(this, new FilterReturn(function));
    }

    /**
     * Apply transforms to run the given handle's logic as a "finally" block.
     *
     * try {
     * some_code // your eventual endpoint
     * } finally {
     * finally_logic // the given handle
     * }
     *
     * The layering uses a combination of catch and fold to reuse the same target
     * handle for both exceptional and non-exceptional paths. In essence, the
     * result is equivalent to using the given post logic as both an exception
     * handler (using catchException) and a "post fold" that runs after the main
     * downstream handles have run.
     *
     * @param post the logic that would live inside the "finally" block
     * @return a new Binder
     */
    public Binder tryFinally(MethodHandle post) {
        return new Binder(this, new TryFinally(post));
    }

    /**
     * Catch the given exception type from the downstream chain and handle it with the
     * given function.
     *
     * @param throwable the exception type to catch
     * @param function  the function to use for handling the exception
     * @return a new Binder
     */
    public Binder catchException(Class<? extends Throwable> throwable, MethodHandle function) {
        return new Binder(this, new Catch(throwable, function));
    }

    /**
     * Apply all transforms to an endpoint that does absolutely nothing. Useful for
     * creating exception handlers in void methods that simply ignore the exception.
     *
     * @return a handle that has all transforms applied and does nothing at its endpoint
     */
    public MethodHandle nop() {
        if (type().returnType() != void.class) {
            throw new InvalidTransformException("must have void return type to nop: " + type());
        }
        return invoke(Binder
                .from(type())
                .drop(0, type().parameterCount())
                .cast(Object.class)
                .constant(null));
    }

    /**
     * Throw the current signature's sole Throwable argument. Return type
     * does not matter, since it will never return.
     *
     * @return a handle that has all transforms applied and which will eventually throw an exception
     */
    public MethodHandle throwException() {
        if (type().parameterCount() != 1 || !Throwable.class.isAssignableFrom(type().parameterType(0))) {
            throw new InvalidTransformException("incoming signature must have one Throwable type as its sole argument: " + type());
        }
        return invoke(MethodHandles.throwException(type().returnType(), (Class<Throwable>)type().parameterType(0)));
    }

    /**
     * Apply the tranforms, binding them to a constant value that will
     * propagate back through the chain. The chain's expected return type
     * at that point must be compatible with the given value's type.
     *
     * @param value the constant value to put at the end of the chain
     * @return a handle that has all transforms applied in sequence up to the constant
     */
    public MethodHandle constant(Object value) {
        return invoke(MethodHandles.constant(type().returnType(), value));
    }

    /**
     * Apply the tranforms, binding them to a handle that will simply return its sole
     * argument as its return value. The endpoint signature must have a single argument
     * of the same type as its return type.
     *
     * @return a handle that has all transforms applied in sequence
     */
    public MethodHandle identity() {
        return invoke(MethodHandles.identity(type().parameterType(0)));
    }

    /**
     * Apply the chain of transforms with the target method handle as the final
     * endpoint. Produces a handle that has the transforms in given sequence.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * @param target the endpoint handle to bind to
     * @return a handle that has all transforms applied in sequence up to endpoint
     */
    public MethodHandle invoke(MethodHandle target) {
        MethodHandle current = target;
        for (Transform t : transforms) {
            current = t.up(current);
        }

        // if resulting handle's type does not match start, attempt one more cast
        current = MethodHandles.explicitCastArguments(current, start);

        return current;
    }

    /**
     * Apply the chain of transforms and bind them to a static method specified
     * using the end signature plus the given class and method. The method will
     * be retrieved using the given Lookup and must match the end signature
     * exactly.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * @param lookup the MethodHandles.Lookup to use to unreflect the method
     * @param method the Method to unreflect
     * @return the full handle chain, bound to the given method
     * @throws java.lang.IllegalAccessException if the method is not accessible
     */
    public MethodHandle invoke(MethodHandles.Lookup lookup, Method method) throws IllegalAccessException {
        return invoke(lookup.unreflect(method));
    }

    /**
     * Apply the chain of transforms and bind them to a static method specified
     * using the end signature plus the given class and method. The method will
     * be retrieved using the given Lookup and must match the end signature
     * exactly.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * This version is "quiet" in that it throws an unchecked InvalidTransformException
     * if the target method does not exist or is inaccessible.
     *
     * @param lookup the MethodHandles.Lookup to use to unreflect the method
     * @param method the Method to unreflect
     * @return the full handle chain, bound to the given method
     */
    public MethodHandle invokeQuiet(MethodHandles.Lookup lookup, Method method) {
        try {
            return invoke(lookup, method);
        } catch (IllegalAccessException iae) {
            throw new InvalidTransformException(iae);
        }
    }

    /**
     * Apply the chain of transforms and bind them to a static method specified
     * using the end signature plus the given class and name. The method will
     * be retrieved using the given Lookup and must match the end signature
     * exactly.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * @param lookup the MethodHandles.Lookup to use to unreflect the method
     * @param target the class in which to find the method
     * @param name   the name of the method to invoke
     * @return the full handle chain, bound to the given method
     * @throws java.lang.NoSuchMethodException if the method does not exist
     * @throws java.lang.IllegalAccessException if the method is not accessible
     */
    public MethodHandle invokeStatic(MethodHandles.Lookup lookup, Class target, String name) throws NoSuchMethodException, IllegalAccessException {
        return invoke(lookup.findStatic(target, name, type()));
    }

    /**
     * Apply the chain of transforms and bind them to a static method specified
     * using the end signature plus the given class and name. The method will
     * be retrieved using the given Lookup and must match the end signature
     * exactly.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * This version is "quiet" in that it throws an unchecked InvalidTransformException
     * if the target method does not exist or is inaccessible.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the method
     * @param target the class in which to find the method
     * @param name   the name of the method to invoke
     * @return the full handle chain, bound to the given method
     */
    public MethodHandle invokeStaticQuiet(MethodHandles.Lookup lookup, Class target, String name) {
        try {
            return invokeStatic(lookup, target, name);
        } catch (IllegalAccessException iae) {
            throw new InvalidTransformException(iae);
        } catch (NoSuchMethodException nsme) {
            throw new InvalidTransformException(nsme);
        }
    }

    /**
     * Apply the chain of transforms and bind them to a virtual method specified
     * using the end signature plus the given class and name. The method will
     * be retrieved using the given Lookup and must match the end signature
     * exactly.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the method
     * @param name   the name of the method to invoke
     * @return the full handle chain, bound to the given method
     * @throws java.lang.NoSuchMethodException if the method does not exist
     * @throws java.lang.IllegalAccessException if the method is not accessible
     */
    public MethodHandle invokeVirtual(MethodHandles.Lookup lookup, String name) throws NoSuchMethodException, IllegalAccessException {
        return invoke(lookup.findVirtual(type().parameterType(0), name, type().dropParameterTypes(0, 1)));
    }

    /**
     * Apply the chain of transforms and bind them to a virtual method specified
     * using the end signature plus the given class and name. The method will
     * be retrieved using the given Lookup and must match the end signature
     * exactly.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * This version is "quiet" in that it throws an unchecked InvalidTransformException
     * if the target method does not exist or is inaccessible.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the method
     * @param name   the name of the method to invoke
     * @return the full handle chain, bound to the given method
     */
    public MethodHandle invokeVirtualQuiet(MethodHandles.Lookup lookup, String name) {
        try {
            return invokeVirtual(lookup, name);
        } catch (IllegalAccessException iae) {
            throw new InvalidTransformException(iae);
        } catch (NoSuchMethodException nsme) {
            throw new InvalidTransformException(nsme);
        }
    }

    /**
     * Apply the chain of transforms and bind them to a special method specified
     * using the end signature plus the given class and name. The method will
     * be retrieved using the given Lookup and must match the end signature
     * exactly.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the method
     * @param name   the name of the method to invoke
     * @param caller the calling class
     * @return the full handle chain, bound to the given method
     * @throws java.lang.NoSuchMethodException if the method does not exist
     * @throws java.lang.IllegalAccessException if the method is not accessible
     */
    public MethodHandle invokeSpecial(MethodHandles.Lookup lookup, String name, Class caller) throws NoSuchMethodException, IllegalAccessException {
        return invoke(lookup.findSpecial(type().parameterType(0), name, type().dropParameterTypes(0, 1), caller));
    }

    /**
     * Apply the chain of transforms and bind them to a special method specified
     * using the end signature plus the given class and name. The method will
     * be retrieved using the given Lookup and must match the end signature
     * exactly.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * This version is "quiet" in that it throws an unchecked InvalidTransformException
     * if the target method does not exist or is inaccessible.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the method
     * @param name   the name of the method to invoke
     * @param caller the calling class
     * @return the full handle chain, bound to the given method
     */
    public MethodHandle invokeSpecialQuiet(MethodHandles.Lookup lookup, String name, Class caller) {
        try {
            return invokeSpecial(lookup, name, caller);
        } catch (IllegalAccessException iae) {
            throw new InvalidTransformException(iae);
        } catch (NoSuchMethodException nsme) {
            throw new InvalidTransformException(nsme);
        }
    }

    /**
     * Apply the chain of transforms and bind them to a constructor specified
     * using the end signature plus the given class. The constructor will
     * be retrieved using the given Lookup and must match the end signature's
     * arguments exactly.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the constructor
     * @param target the constructor's class
     * @return the full handle chain, bound to the given constructor
     * @throws java.lang.NoSuchMethodException if the constructor does not exist
     * @throws java.lang.IllegalAccessException if the constructor is not accessible
     */
    public MethodHandle invokeConstructor(MethodHandles.Lookup lookup, Class target) throws NoSuchMethodException, IllegalAccessException {
        return invoke(lookup.findConstructor(target, type().changeReturnType(void.class)));
    }

    /**
     * Apply the chain of transforms and bind them to a constructor specified
     * using the end signature plus the given class. The constructor will
     * be retrieved using the given Lookup and must match the end signature's
     * arguments exactly.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * This version is "quiet" in that it throws an unchecked InvalidTransformException
     * if the target method does not exist or is inaccessible.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the constructor
     * @param target the constructor's class
     * @return the full handle chain, bound to the given constructor
     */
    public MethodHandle invokeConstructorQuiet(MethodHandles.Lookup lookup, Class target) {
        try {
            return invokeConstructor(lookup, target);
        } catch (IllegalAccessException iae) {
            throw new InvalidTransformException(iae);
        } catch (NoSuchMethodException nsme) {
            throw new InvalidTransformException(nsme);
        }
    }

    /**
     * Apply the chain of transforms and bind them to an object field retrieval specified
     * using the end signature plus the given class and name. The field must
     * match the end signature's return value and the end signature must take
     * the target class or a subclass as its only argument.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the field
     * @param name   the field's name
     * @return the full handle chain, bound to the given field access
     * @throws java.lang.NoSuchFieldException if the field does not exist
     * @throws java.lang.IllegalAccessException if the field is not accessible
     * @throws java.lang.NoSuchFieldException if the field does not exist
     * @throws java.lang.IllegalAccessException if the field is not accessible
     */
    public MethodHandle getField(MethodHandles.Lookup lookup, String name) throws NoSuchFieldException, IllegalAccessException {
        return invoke(lookup.findGetter(type().parameterType(0), name, type().returnType()));
    }

    /**
     * Apply the chain of transforms and bind them to an object field retrieval specified
     * using the end signature plus the given class and name. The field must
     * match the end signature's return value and the end signature must take
     * the target class or a subclass as its only argument.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * This version is "quiet" in that it throws an unchecked InvalidTransformException
     * if the target method does not exist or is inaccessible.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the field
     * @param name   the field's name
     * @return the full handle chain, bound to the given field access
     */
    public MethodHandle getFieldQuiet(MethodHandles.Lookup lookup, String name) {
        try {
            return getField(lookup, name);
        } catch (IllegalAccessException iae) {
            throw new InvalidTransformException(iae);
        } catch (NoSuchFieldException nsfe) {
            throw new InvalidTransformException(nsfe);
        }
    }

    /**
     * Apply the chain of transforms and bind them to a static field retrieval specified
     * using the end signature plus the given class and name. The field must
     * match the end signature's return value and the end signature must take
     * no arguments.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the field
     * @param target the class in which the field is defined
     * @param name   the field's name
     * @return the full handle chain, bound to the given field access
     * @throws java.lang.NoSuchFieldException if the field does not exist
     * @throws java.lang.IllegalAccessException if the field is not accessible or cannot be modified
     */
    public MethodHandle getStatic(MethodHandles.Lookup lookup, Class target, String name) throws NoSuchFieldException, IllegalAccessException {
        return invoke(lookup.findStaticGetter(target, name, type().returnType()));
    }

    /**
     * Apply the chain of transforms and bind them to a static field retrieval specified
     * using the end signature plus the given class and name. The field must
     * match the end signature's return value and the end signature must take
     * no arguments.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * This version is "quiet" in that it throws an unchecked InvalidTransformException
     * if the target method does not exist or is inaccessible.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the field
     * @param target the class in which the field is defined
     * @param name   the field's name
     * @return the full handle chain, bound to the given field access
     */
    public MethodHandle getStaticQuiet(MethodHandles.Lookup lookup, Class target, String name) {
        try {
            return getStatic(lookup, target, name);
        } catch (IllegalAccessException iae) {
            throw new InvalidTransformException(iae);
        } catch (NoSuchFieldException nsfe) {
            throw new InvalidTransformException(nsfe);
        }
    }

    /**
     * Apply the chain of transforms and bind them to an object field assignment specified
     * using the end signature plus the given class and name. The end signature must take
     * the target class or a subclass and the field's type as its arguments, and its return
     * type must be compatible with void.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the field
     * @param name   the field's name
     * @return the full handle chain, bound to the given field assignment
     * @throws java.lang.NoSuchFieldException if the field does not exist
     * @throws java.lang.IllegalAccessException if the field is not accessible or cannot be modified
     */
    public MethodHandle setField(MethodHandles.Lookup lookup, String name) throws NoSuchFieldException, IllegalAccessException {
        return invoke(lookup.findSetter(type().parameterType(0), name, type().parameterType(1)));
    }

    /**
     * Apply the chain of transforms and bind them to an object field assignment specified
     * using the end signature plus the given class and name. The end signature must take
     * the target class or a subclass and the field's type as its arguments, and its return
     * type must be compatible with void.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * This version is "quiet" in that it throws an unchecked InvalidTransformException
     * if the target method does not exist or is inaccessible.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the field
     * @param name   the field's name
     * @return the full handle chain, bound to the given field assignment
     */
    public MethodHandle setFieldQuiet(MethodHandles.Lookup lookup, String name) {
        try {
            return setField(lookup, name);
        } catch (IllegalAccessException iae) {
            throw new InvalidTransformException(iae);
        } catch (NoSuchFieldException nsfe) {
            throw new InvalidTransformException(nsfe);
        }
    }

    /**
     * Apply the chain of transforms and bind them to an object field assignment specified
     * using the end signature plus the given class and name. The end signature must take
     * the target class or a subclass and the field's type as its arguments, and its return
     * type must be compatible with void.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the field
     * @param target the class in which the field is defined
     * @param name   the field's name
     * @return the full handle chain, bound to the given field assignment
     * @throws java.lang.NoSuchFieldException if the field does not exist
     * @throws java.lang.IllegalAccessException if the field is not accessible or cannot be modified
     */
    public MethodHandle setStatic(MethodHandles.Lookup lookup, Class target, String name) throws NoSuchFieldException, IllegalAccessException {
        return invoke(lookup.findStaticSetter(target, name, type().parameterType(0)));
    }

    /**
     * Apply the chain of transforms and bind them to an object field assignment specified
     * using the end signature plus the given class and name. The end signature must take
     * the target class or a subclass and the field's type as its arguments, and its return
     * type must be compatible with void.
     *
     * If the final handle's type does not exactly match the initial type for
     * this Binder, an additional cast will be attempted.
     *
     * This version is "quiet" in that it throws an unchecked InvalidTransformException
     * if the target method does not exist or is inaccessible.
     *
     * @param lookup the MethodHandles.Lookup to use to look up the field
     * @param target the class in which the field is defined
     * @param name   the field's name
     * @return the full handle chain, bound to the given field assignment
     */
    public MethodHandle setStaticQuiet(MethodHandles.Lookup lookup, Class target, String name) {
        try {
            return setStatic(lookup, target, name);
        } catch (IllegalAccessException iae) {
            throw new InvalidTransformException(iae);
        } catch (NoSuchFieldException nsfe) {
            throw new InvalidTransformException(nsfe);
        }
    }


    /**
     * Apply the chain of transforms and bind them to an array element set. The signature
     * at the endpoint must return void and receive the array type, int index, and array
     * element type.
     *
     * @return the full handle chain, bound to an array element set.
     */
    public MethodHandle arraySet() {
        return invoke(MethodHandles.arrayElementSetter(type().parameterType(0)));
    }


    /**
     * Apply the chain of transforms and bind them to an array element get. The signature
     * at the endpoint must return the array element type and receive the array type and
     * int index.
     *
     * @return the full handle chain, bound to an array element get.
     */
    public MethodHandle arrayGet() {
        return invoke(MethodHandles.arrayElementGetter(type().parameterType(0)));
    }

    /**
     * Apply the chain of transforms and bind them to a boolean branch as from
     * java.lang.invoke.MethodHandles.guardWithTest. As with GWT, the current endpoint
     * signature must match the given target and fallback signatures.
     *
     * @param test      the test handle
     * @param truePath  the target handle
     * @param falsePath the fallback handle
     * @return the full handle chain bound to a branch
     */
    public MethodHandle branch(MethodHandle test, MethodHandle truePath, MethodHandle falsePath) {
        return invoke(MethodHandles.guardWithTest(test, truePath, falsePath));
    }

    /**
     * Produce a MethodHandle that invokes its leading MethodHandle argument
     * with the remaining arguments, returning the result.
     *
     * @return a new handle that invokes its leading MethodHandle argument
     */
    public MethodHandle invoker() {
        return invoke(MethodHandles.invoker(start));
    }

}
TOP

Related Classes of com.headius.invokebinder.Binder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.