Package org.codehaus.groovy.vmplugin.v7

Source Code of org.codehaus.groovy.vmplugin.v7.TypeTransformers

/*
* Copyright 2003-2012 the original author or authors.
*
* 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 org.codehaus.groovy.vmplugin.v7;

import groovy.lang.Closure;
import groovy.lang.GString;
import groovy.lang.GroovyObject;
import groovy.util.ProxyGenerator;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.reflection.stdclasses.CachedSAMClass;
import org.codehaus.groovy.runtime.ConvertedClosure;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
import org.codehaus.groovy.transform.trait.Traits;

/**
* This class contains several transformers for used during method invocation.
* @author <a href="mailto:blackdrag@gmx.org">Jochen "blackdrag" Theodorou</a>
*/
public class TypeTransformers {
  private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
    private static final MethodHandle
        TO_STRING, TO_BYTE,   TO_INT,     TO_LONG,    TO_SHORT,
        TO_FLOAT,  TO_DOUBLE, TO_BIG_INT, TO_BIG_DEC, AS_ARRAY,
        TO_REFLECTIVE_PROXY, TO_GENERATED_PROXY, TO_SAMTRAIT_PROXY;
    static {
        try {
            TO_STRING   = LOOKUP.findVirtual(Object.class, "toString",      MethodType.methodType(String.class));
            TO_BYTE     = LOOKUP.findVirtual(Number.class, "byteValue",     MethodType.methodType(Byte.TYPE));
            TO_SHORT    = LOOKUP.findVirtual(Number.class, "shortValue",    MethodType.methodType(Short.TYPE));
            TO_INT      = LOOKUP.findVirtual(Number.class, "intValue",      MethodType.methodType(Integer.TYPE));
            TO_LONG     = LOOKUP.findVirtual(Number.class, "longValue",     MethodType.methodType(Long.TYPE));
            TO_FLOAT    = LOOKUP.findVirtual(Number.class, "floatValue",    MethodType.methodType(Float.TYPE));
            TO_DOUBLE   = LOOKUP.findVirtual(Number.class, "doubleValue",   MethodType.methodType(Double.TYPE));

            // BigDecimal conversion is done by using the double value
            // if the given number.
            MethodHandle tmp = LOOKUP.findConstructor(BigDecimal.class, MethodType.methodType(Void.TYPE, Double.TYPE));
            TO_BIG_DEC  = MethodHandles.filterReturnValue(TO_DOUBLE, tmp);

            // BigInteger conversion is done by using the string representation
            // if the given number
            tmp = LOOKUP.findConstructor(BigInteger.class, MethodType.methodType(Void.TYPE, String.class));
            TO_BIG_INT  = MethodHandles.filterReturnValue(TO_STRING, tmp);

            // generic array to array conversion
            AS_ARRAY = LOOKUP.findStatic(DefaultTypeTransformation.class, "asArray", MethodType.methodType(Object.class, Object.class, Class.class));

            // reflective proxy generation, since we need a ConvertedClosure but have only a normal Closure, we need to create that wrapper object as well
            MethodHandle newProxyInstance = LOOKUP.findStatic(Proxy.class, "newProxyInstance",
                    MethodType.methodType(Object.class, ClassLoader.class, Class[].class, InvocationHandler.class));
            MethodHandle newConvertedClosure = LOOKUP.findConstructor(ConvertedClosure.class, MethodType.methodType(Void.TYPE, Closure.class, String.class));
            // prepare target newProxyInstance for fold to drop additional arguments needed by newConvertedClosure
            MethodType newOrder = newProxyInstance.type().dropParameterTypes(2, 3);
            newOrder = newOrder.insertParameterTypes(0, InvocationHandler.class, Closure.class, String.class);
            tmp = MethodHandles.permuteArguments(newProxyInstance, newOrder, 3, 4, 0);
            // execute fold:
            TO_REFLECTIVE_PROXY = MethodHandles.foldArguments(tmp, newConvertedClosure.asType(newConvertedClosure.type().changeReturnType(InvocationHandler.class)));

            {
                // generated proxy using a map to store the closure
                MethodHandle map = LOOKUP.findStatic(Collections.class, "singletonMap",
                        MethodType.methodType(Map.class, Object.class, Object.class));
                newProxyInstance = LOOKUP.findVirtual(ProxyGenerator.class, "instantiateAggregateFromBaseClass",
                        MethodType.methodType(GroovyObject.class, Map.class, Class.class));
                newOrder = newProxyInstance.type().dropParameterTypes(1, 2);
                newOrder = newOrder.insertParameterTypes(0, Map.class, Object.class, Object.class);
                tmp = MethodHandles.permuteArguments(newProxyInstance, newOrder, 3, 0, 4);
                tmp = MethodHandles.foldArguments(tmp, map);
                TO_GENERATED_PROXY = tmp;
            }
            {
                // Trait SAM coercion generated proxy using a map to store the closure
                MethodHandle map = LOOKUP.findStatic(Collections.class, "singletonMap",
                        MethodType.methodType(Map.class, Object.class, Object.class));
                newProxyInstance = LOOKUP.findVirtual(ProxyGenerator.class, "instantiateAggregate",
                        MethodType.methodType(GroovyObject.class,Map.class, List.class));
                newOrder = newProxyInstance.type().dropParameterTypes(1, 2);
                newOrder = newOrder.insertParameterTypes(0, Map.class, Object.class, Object.class);
                tmp = MethodHandles.permuteArguments(newProxyInstance, newOrder, 3, 0, 4);
                tmp = MethodHandles.foldArguments(tmp, map);
                TO_SAMTRAIT_PROXY = tmp;
            }
        } catch (Exception e) {
            throw new GroovyBugError(e);
        }
    }

    /**
     * Adds a type transformer applied at runtime.
     * This method handles transformations to String from GString,
     * array transformations and number based transformations
     */
    protected static MethodHandle addTransformer(MethodHandle handle, int pos, Object arg, Class parameter) {
        MethodHandle transformer=null;
        if (arg instanceof GString) {
            transformer = TO_STRING;
        } else if (arg instanceof Closure) {
            transformer = createSAMTransform(arg, parameter);
        } else if (Number.class.isAssignableFrom(parameter)) {
            transformer = selectNumberTransformer(parameter, arg);
        } else if (parameter.isArray()) {
            transformer =  MethodHandles.insertArguments(AS_ARRAY, 1, parameter);
        }
        if (transformer==null) throw new GroovyBugError("Unknown transformation for argument "+arg+" at position "+pos+" with "+arg.getClass()+" for parameter of type "+parameter);
        return applyUnsharpFilter(handle, pos, transformer);
    }

    /**
     * creates a method handle able to transform the given Closure into a SAM type
     * if the given parameter is a SAM type
     */
    private static MethodHandle createSAMTransform(Object arg, Class parameter) {
        Method method = CachedSAMClass.getSAMMethod(parameter);
        if (method == null) return null;
        // TODO: have to think about how to optimize this!
        if (parameter.isInterface()) {
            if (Traits.isTrait(parameter)) {
                // the following code will basically do this:
                // Map<String,Closure> impl = Collections.singletonMap(method.getName(),arg);
                // return ProxyGenerator.INSTANCE.instantiateAggregate(impl,Collections.singletonList(clazz));
                // TO_SAMTRAIT_PROXY is a handle (Object,Object,ProxyGenerator,Class)GroovyObject
                // where the second object is the input closure, everything else
                // needs to be provide and is in remaining order: method name,
                // ProxyGenerator.INSTANCE and singletonList(parameter)
                MethodHandle ret = TO_SAMTRAIT_PROXY;
                ret = MethodHandles.insertArguments(ret, 2, ProxyGenerator.INSTANCE, Collections.singletonList(parameter));
                ret = MethodHandles.insertArguments(ret, 0, method.getName());
                return ret;
            }
            // the following code will basically do this:
            // return Proxy.newProxyInstance(
            //        arg.getClass().getClassLoader(),
            //        new Class[]{parameter},
            //        new ConvertedClosure((Closure) arg));
            // TO_REFLECTIVE_PROXY will do that for us, though
            // input is the closure, the method name, the class loader and the
            // class[]. All of that but the closure must be provided here 
            MethodHandle ret = TO_REFLECTIVE_PROXY;
            ret = MethodHandles.insertArguments(ret, 1,
                        method.getName(),
                        arg.getClass().getClassLoader(),
                        new Class[]{parameter});
            return ret;
        } else {
            // the following code will basically do this:
            //Map<String, Object> m = Collections.singletonMap(method.getName(), arg);
            //return ProxyGenerator.INSTANCE.
            //            instantiateAggregateFromBaseClass(m, parameter);
            // TO_GENERATED_PROXY is a handle (Object,Object,ProxyGenerator,Class)GroovyObject
            // where the second object is the input closure, everything else
            // needs to be provide and is in remaining order: method name,
            // ProxyGenerator.INSTANCE and parameter
            MethodHandle ret = TO_GENERATED_PROXY;
            ret = MethodHandles.insertArguments(ret, 2, ProxyGenerator.INSTANCE, parameter);
            ret = MethodHandles.insertArguments(ret, 0, method.getName());
            return ret;
        }
    }

    /**
     * Apply a transformer as filter.
     * The filter may not match exactly in the types. In this case needed
     * additional type transformations are done by {@link MethodHandle#asType(MethodType)}
     */
    public static MethodHandle applyUnsharpFilter(MethodHandle handle, int pos, MethodHandle transformer) {
        MethodType type = transformer.type();
        Class given = handle.type().parameterType(pos);
        if (type.returnType() != given || type.parameterType(0) != given) {
            transformer = transformer.asType(MethodType.methodType(given, type.parameterType(0)));
        }
        return MethodHandles.filterArguments(handle, pos, transformer);
    }

    /**
     * returns a transformer later applied as filter to transform one
     * number into another
     */
    private static MethodHandle selectNumberTransformer(Class param, Object arg) {
        param = TypeHelper.getWrapperClass(param);

        if (param == Byte.class) {
            return TO_BYTE;
        } else if (param == Character.class || param == Integer.class) {
            return TO_INT;
        } else  if (param == Long.class) {
            return TO_LONG;
        } else if (param == Float.class) {
            return TO_FLOAT;
        } else if (param == Double.class) {
            return TO_DOUBLE;
        } else if (param == BigInteger.class) {
            return TO_BIG_INT;
        } else if (param == BigDecimal.class) {
            return TO_BIG_DEC;
        } else if (param == Short.class) {
            return TO_SHORT;
        } else {
             return null;
        }
    }
}
TOP

Related Classes of org.codehaus.groovy.vmplugin.v7.TypeTransformers

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.