/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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, AS_ARRAY, TO_REFLECTIVE_PROXY, TO_GENERATED_PROXY, TO_SAMTRAIT_PROXY, DOUBLE_TO_BIG_DEC, DOUBLE_TO_BIG_DEC_WITH_CONVERSION, LONG_TO_BIG_DEC, BIG_INT_TO_BIG_DEC; 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 DOUBLE_TO_BIG_DEC = LOOKUP.findConstructor(BigDecimal.class, MethodType.methodType(Void.TYPE, Double.TYPE)); DOUBLE_TO_BIG_DEC_WITH_CONVERSION = MethodHandles.filterReturnValue(TO_DOUBLE, DOUBLE_TO_BIG_DEC); // BigDecimal conversion is done by using the double value LONG_TO_BIG_DEC = LOOKUP.findConstructor(BigDecimal.class, MethodType.methodType(Void.TYPE, Long.TYPE)); // BigDecimal conversion is done by using the double value BIG_INT_TO_BIG_DEC = LOOKUP.findConstructor(BigDecimal.class, MethodType.methodType(Void.TYPE, BigInteger.class)); // BigInteger conversion is done by using the string representation MethodHandle 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) { if (arg instanceof Double) { return DOUBLE_TO_BIG_DEC; } else if (arg instanceof Long) { return LONG_TO_BIG_DEC; } else if (arg instanceof BigInteger) { return BIG_INT_TO_BIG_DEC; } else { return DOUBLE_TO_BIG_DEC_WITH_CONVERSION; } } else if (param == Short.class) { return TO_SHORT; } else { return null; } } }