org.codehaus.groovy.vmplugin.v7.TypeTransformers Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of groovy Show documentation
Show all versions of groovy Show documentation
Groovy: A powerful, dynamic language for the JVM
/*
* 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 Jochen "blackdrag" Theodorou
*/
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 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 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;
}
}
}