org.codehaus.groovy.vmplugin.v7.Selector Maven / Gradle / Ivy
The newest version!
/*
* 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.AdaptingMetaClass;
import groovy.lang.Closure;
import groovy.lang.ExpandoMetaClass;
import groovy.lang.GroovyInterceptable;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.GroovySystem;
import groovy.lang.MetaClass;
import groovy.lang.MetaClassImpl;
import groovy.lang.MetaClassImpl.MetaConstructor;
import groovy.lang.MetaMethod;
import groovy.lang.MetaProperty;
import groovy.lang.MissingMethodException;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.reflection.CachedField;
import org.codehaus.groovy.reflection.CachedMethod;
import org.codehaus.groovy.reflection.ClassInfo;
import org.codehaus.groovy.reflection.GeneratedMetaMethod;
import org.codehaus.groovy.reflection.stdclasses.CachedSAMClass;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.GroovyCategorySupport;
import org.codehaus.groovy.runtime.GroovyCategorySupport.CategoryMethod;
import org.codehaus.groovy.runtime.NullObject;
import org.codehaus.groovy.runtime.dgmimpl.NumberNumberMetaMethod;
import org.codehaus.groovy.runtime.metaclass.ClosureMetaClass;
import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl;
import org.codehaus.groovy.runtime.metaclass.MethodMetaProperty;
import org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod;
import org.codehaus.groovy.runtime.metaclass.NewStaticMetaMethod;
import org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod;
import org.codehaus.groovy.runtime.wrappers.Wrapper;
import org.codehaus.groovy.vmplugin.v7.IndyInterface.CALL_TYPES;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.MutableCallSite;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.ARRAYLIST_CONSTRUCTOR;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.BEAN_CONSTRUCTOR_PROPERTY_SETTER;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.BOOLEAN_IDENTITY;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.CLASS_FOR_NAME;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.DTT_CAST_TO_TYPE;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.EQUALS;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.GROOVY_CAST_EXCEPTION;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.GROOVY_OBJECT_GET_PROPERTY;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.GROOVY_OBJECT_INVOKER;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.HASHSET_CONSTRUCTOR;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.HAS_CATEGORY_IN_CURRENT_THREAD_GUARD;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.INTERCEPTABLE_INVOKER;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.IS_NULL;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.META_CLASS_INVOKE_STATIC_METHOD;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.META_METHOD_INVOKER;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.META_PROPERTY_GETTER;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.MOP_GET;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.MOP_INVOKE_CONSTRUCTOR;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.MOP_INVOKE_METHOD;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.NULL_REF;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.SAME_CLASS;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.SAME_MC;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.SAM_CONVERSION;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.UNWRAP_EXCEPTION;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.UNWRAP_METHOD;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.unwrap;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.LOG;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.LOG_ENABLED;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.LOOKUP;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.makeFallBack;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.switchPoint;
public abstract class Selector {
public Object[] args, originalArguments;
public MetaMethod method;
public MethodType targetType,currentType;
public String name;
public MethodHandle handle;
public boolean useMetaClass = false, cache = true;
public MutableCallSite callSite;
public Class sender;
public boolean isVargs;
public boolean safeNavigation, safeNavigationOrig, spread;
public boolean skipSpreadCollector;
public boolean thisCall;
public Class selectionBase;
public boolean catchException = true;
public CALL_TYPES callType;
/** Cache values for read-only access */
private static final CALL_TYPES[] CALL_TYPES_VALUES = CALL_TYPES.values();
/**
* Returns the Selector
*/
public static Selector getSelector(MutableCallSite callSite, Class sender, String methodName, int callID, boolean safeNavigation, boolean thisCall, boolean spreadCall, Object[] arguments) {
CALL_TYPES callType = CALL_TYPES_VALUES[callID];
switch (callType) {
case INIT: return new InitSelector(callSite, sender, methodName, callType, safeNavigation, thisCall, spreadCall, arguments);
case METHOD: return new MethodSelector(callSite, sender, methodName, callType, safeNavigation, thisCall, spreadCall, arguments);
case GET:
return new PropertySelector(callSite, sender, methodName, callType, safeNavigation, thisCall, spreadCall, arguments);
case SET:
throw new GroovyBugError("your call tried to do a property set, which is not supported.");
case CAST: return new CastSelector(callSite, arguments);
default: throw new GroovyBugError("unexpected call type");
}
}
abstract void setCallSiteTarget();
/**
* Helper method to transform the given arguments, consisting of the receiver
* and the actual arguments in an Object[], into a new Object[] consisting
* of the receiver and the arguments directly. Before the size of args was
* always 2, the returned Object[] will have a size of 1+n, where n is the
* number arguments.
*/
private static Object[] spread(Object[] args, boolean spreadCall) {
if (!spreadCall) return args;
Object[] normalArguments = (Object[]) args[1];
Object[] ret = new Object[normalArguments.length+1];
ret[0] = args[0];
System.arraycopy(normalArguments, 0, ret, 1, ret.length-1);
return ret;
}
private static class CastSelector extends MethodSelector {
private Class> staticSourceType, staticTargetType;
public CastSelector(MutableCallSite callSite, Object[] arguments) {
super(callSite, Selector.class, "", CALL_TYPES.CAST, false, false, false, arguments);
this.staticSourceType = callSite.type().parameterType(0);
this.staticTargetType = callSite.type().returnType();
}
@Override
public void setCallSiteTarget() {
// targetTypes String, Enum and Class are handled
// by the compiler already
// Boolean / boolean
handleBoolean();
handleNullWithoutBoolean();
// !! from here on args[0] is always not null !!
handleInstanceCase();
// targetType is abstract Collection fitting for HashSet or ArrayList
// and object is Collection or array
handleCollections();
handleSAM();
// will handle :
// * collection case where argument is an array
// * array transformation (staticTargetType.isArray())
// * constructor invocation
// * final GroovyCastException
castToTypeFallBack();
if (!handle.type().equals(callSite.type())) castAndSetGuards();
}
private void castAndSetGuards() {
handle = MethodHandles.explicitCastArguments(handle,targetType);
setGuards(args[0]);
doCallSiteTargetSet();
}
private void handleNullWithoutBoolean() {
if (handle!=null || args[0]!=null) return;
if (staticTargetType.isPrimitive()) {
handle = MethodHandles.insertArguments(GROOVY_CAST_EXCEPTION,1,staticTargetType);
// need to call here here because we used the static target type
// it won't be done otherwise because handle.type() == callSite.type()
castAndSetGuards();
} else {
handle = MethodHandles.identity(staticSourceType);
}
}
private void handleInstanceCase() {
if (handle!=null) return;
if (staticTargetType.isAssignableFrom(args[0].getClass())) {
handle = MethodHandles.identity(staticSourceType);
}
}
private static boolean isAbstractClassOf(Class toTest, Class givenOnCallSite) {
if (!toTest.isAssignableFrom(givenOnCallSite)) return false;
if (givenOnCallSite.isInterface()) return true;
return Modifier.isAbstract(givenOnCallSite.getModifiers());
}
private void handleCollections() {
if (handle!=null) return;
if (!(args[0] instanceof Collection)) return;
if (isAbstractClassOf(HashSet.class, staticTargetType)) {
handle = HASHSET_CONSTRUCTOR;
} else if (isAbstractClassOf(ArrayList.class, staticTargetType)) {
handle = ARRAYLIST_CONSTRUCTOR;
}
}
private void handleSAM() {
if (handle!=null) return;
if (!(args[0] instanceof Closure)) return;
Method m = CachedSAMClass.getSAMMethod(staticTargetType);
if (m==null) return;
//TODO: optimize: add guard based on type Closure
handle = MethodHandles.insertArguments(SAM_CONVERSION, 1, m, staticTargetType, staticTargetType.isInterface());
}
private void castToTypeFallBack() {
if (handle!=null) return;
// generic fallback to castToType
handle = MethodHandles.insertArguments(DTT_CAST_TO_TYPE, 1, staticTargetType);
}
private void handleBoolean() {
if (handle!=null) return;
// boolean->boolean, Boolean->boolean, boolean->Boolean
// is handled by compiler
// that leaves (T)Z and (T)Boolean, where T is the static type
// but runtime type of T might be Boolean
boolean primitive = staticTargetType==boolean.class;
if (!primitive && staticTargetType!=Boolean.class) return;
if (args[0]==null) {
if (primitive) {
handle = MethodHandles.constant(boolean.class, false);
handle = MethodHandles.dropArguments(handle, 0, staticSourceType);
} else {
handle = BOOLEAN_IDENTITY;
}
} else if (args[0] instanceof Boolean) {
// give value through or unbox
handle = BOOLEAN_IDENTITY;
} else {
//call asBoolean
name = "asBoolean";
super.setCallSiteTarget();
return;
}
}
}
private static class PropertySelector extends MethodSelector {
private boolean insertName = false;
public PropertySelector(MutableCallSite callSite, Class sender, String methodName, CALL_TYPES callType, boolean safeNavigation, boolean thisCall, boolean spreadCall, Object[] arguments) {
super(callSite, sender, methodName, callType, safeNavigation, thisCall, spreadCall, arguments);
}
/**
* We never got the interceptor path with a property get
*/
@Override
public boolean setInterceptor() {
return false;
}
/**
* this method chooses a property from the meta class.
*/
@Override
public void chooseMeta(MetaClassImpl mci) {
Object receiver = getCorrectedReceiver();
if (receiver instanceof GroovyObject) {
Class aClass = receiver.getClass();
Method reflectionMethod = null;
try {
reflectionMethod = aClass.getMethod("getProperty", String.class);
if (!reflectionMethod.isSynthetic()) {
handle = MethodHandles.insertArguments(GROOVY_OBJECT_GET_PROPERTY, 1, name);
return;
}
} catch (ReflectiveOperationException e) {}
} else if (receiver instanceof Class) {
handle = MOP_GET;
handle = MethodHandles.insertArguments(handle, 2, name);
handle = MethodHandles.insertArguments(handle, 0, this.mc);
return;
}
if (method!=null || mci==null) return;
Class chosenSender = this.sender;
if (mci.getTheClass()!= chosenSender && GroovyCategorySupport.hasCategoryInCurrentThread()) {
chosenSender = mci.getTheClass();
}
MetaProperty res = mci.getEffectiveGetMetaProperty(chosenSender, receiver, name, false);
if (res instanceof MethodMetaProperty) {
MethodMetaProperty mmp = (MethodMetaProperty) res;
method = mmp.getMetaMethod();
insertName = true;
} else if (res instanceof CachedField) {
CachedField cf = (CachedField) res;
Field f = cf.field;
try {
handle = LOOKUP.unreflectGetter(f);
if (Modifier.isStatic(f.getModifiers())) {
// normally we would do the following
// handle = MethodHandles.dropArguments(handle,0,Class.class);
// but because there is a bug in invokedynamic in all jdk7 versions
// maybe use Unsafe.ensureClassInitialized
handle = META_PROPERTY_GETTER.bindTo(res);
}
} catch (IllegalAccessException iae) {
throw new GroovyBugError(iae);
}
} else {
handle = META_PROPERTY_GETTER.bindTo(res);
}
}
/**
* Additionally to the normal {@link MethodSelector#setHandleForMetaMethod()}
* task we have to also take care of generic getter methods, that depend
* one the name.
*/
@Override
public void setHandleForMetaMethod() {
if (handle!=null) return;
super.setHandleForMetaMethod();
if (handle != null && insertName && handle.type().parameterCount()==2) {
handle = MethodHandles.insertArguments(handle, 1, name);
}
}
/**
* The MOP requires all get property operations to go through
* {@link GroovyObject#getProperty(String)}. We do this in case
* no property was found before.
*/
@Override
public void setMetaClassCallHandleIfNedded(boolean standardMetaClass) {
if (handle!=null) return;
useMetaClass = true;
if (LOG_ENABLED) LOG.info("set meta class invocation path for property get.");
handle = MethodHandles.insertArguments(MOP_GET, 2, this.name);
handle = MethodHandles.insertArguments(handle, 0, mc);
}
}
private static class InitSelector extends MethodSelector {
private boolean beanConstructor;
public InitSelector(MutableCallSite callSite, Class sender, String methodName, CALL_TYPES callType, boolean safeNavigation, boolean thisCall, boolean spreadCall, Object[] arguments) {
super(callSite, sender, methodName, callType, safeNavigation, thisCall, spreadCall, arguments);
}
/**
* Constructor calls are not intercepted, thus always returns false.
*/
@Override
public boolean setInterceptor() {
return false;
}
/**
* For a constructor call we always use the static meta class from the registry
*/
@Override
public void getMetaClass() {
Object receiver = args[0];
mc = GroovySystem.getMetaClassRegistry().getMetaClass((Class) receiver);
}
/**
* This method chooses a constructor from the meta class.
*/
@Override
public void chooseMeta(MetaClassImpl mci) {
if (mci==null) return;
if (LOG_ENABLED) LOG.info("getting constructor");
Object[] newArgs = removeRealReceiver(args);
method = mci.retrieveConstructor(newArgs);
if (method instanceof MetaConstructor) {
MetaConstructor mcon = (MetaConstructor) method;
if (mcon.isBeanConstructor()) {
if (LOG_ENABLED) LOG.info("do beans constructor");
beanConstructor = true;
}
}
}
/**
* Adds {@link MetaConstructor} handling.
*/
@Override
public void setHandleForMetaMethod() {
if (method==null) return;
if (method instanceof MetaConstructor) {
if (LOG_ENABLED) LOG.info("meta method is MetaConstructor instance");
MetaConstructor mc = (MetaConstructor) method;
isVargs = mc.isVargsMethod();
Constructor con = mc.getCachedConstrcutor().cachedConstructor;
try {
handle = LOOKUP.unreflectConstructor(con);
if (LOG_ENABLED) LOG.info("successfully unreflected constructor");
} catch (IllegalAccessException e) {
throw new GroovyBugError(e);
}
} else {
super.setHandleForMetaMethod();
}
if (beanConstructor) {
// we have handle that takes no arguments to create the bean,
// we have to use its return value to call #setBeanProperties with it
// and the meta class.
// to do this we first bind the values to #setBeanProperties
MethodHandle con = BEAN_CONSTRUCTOR_PROPERTY_SETTER.bindTo(mc);
// inner class case
MethodType foldTargetType = MethodType.methodType(Object.class);
if (args.length==3) {
con = MethodHandles.dropArguments(con, 1, targetType.parameterType(1));
foldTargetType = foldTargetType.insertParameterTypes(0, targetType.parameterType(1));
}
handle = MethodHandles.foldArguments(con, handle.asType(foldTargetType));
}
if (method instanceof MetaConstructor) {
handle = MethodHandles.dropArguments(handle, 0, Class.class);
}
}
/**
* In case of a bean constructor we don't do any varags or implicit null argument
* transformations. Otherwise we do the same as for {@link MethodSelector#correctParameterLength()}
*/
@Override
public void correctParameterLength() {
if (beanConstructor) return;
super.correctParameterLength();
}
/**
* In case of a bean constructor we don't do any coercion, otherwise
* we do the same as for {@link MethodSelector#correctCoerce()}
*/
@Override
public void correctCoerce() {
if (beanConstructor) return;
super.correctCoerce();
}
/**
* Set MOP based constructor invocation path.
*/
@Override
public void setMetaClassCallHandleIfNedded(boolean standardMetaClass) {
if (handle!=null) return;
useMetaClass = true;
if (LOG_ENABLED) LOG.info("set meta class invocation path");
handle = MOP_INVOKE_CONSTRUCTOR.bindTo(mc);
handle = handle.asCollector(Object[].class, targetType.parameterCount()-1);
handle = MethodHandles.dropArguments(handle, 0, Class.class);
if (LOG_ENABLED) LOG.info("create collector for arguments");
}
}
/**
* Method invocation based {@link Selector}.
* This Selector is called for method invocations and is base for cosntructor
* calls as well as getProperty calls.
* @author Jochen "blackdrag" Theodorou
*/
private static class MethodSelector extends Selector {
protected MetaClass mc;
private boolean isCategoryMethod;
public MethodSelector(MutableCallSite callSite, Class sender, String methodName, CALL_TYPES callType, Boolean safeNavigation, Boolean thisCall, Boolean spreadCall, Object[] arguments) {
this.callType = callType;
this.targetType = callSite.type();
this.name = methodName;
this.originalArguments = arguments;
this.args = spread(arguments, spreadCall);
this.callSite = callSite;
this.sender = sender;
this.safeNavigationOrig = safeNavigation;
this.safeNavigation = safeNavigation && arguments[0]==null;
this.thisCall = thisCall;
this.spread = spreadCall;
this.cache = !spread;
if (LOG_ENABLED) {
StringBuilder msg =
new StringBuilder("----------------------------------------------------" +
"\n\t\tinvocation of method '" + methodName + "'" +
"\n\t\tinvocation type: " + callType +
"\n\t\tsender: " + sender +
"\n\t\ttargetType: " + targetType +
"\n\t\tsafe navigation: " + safeNavigation +
"\n\t\tthisCall: " + thisCall +
"\n\t\tspreadCall: " + spreadCall +
"\n\t\twith " + arguments.length + " arguments");
for (int i=0; i args.length) {
// we depend on the method selection having done a good
// job before already, so the only case for this here is, that
// we have no argument for the array, meaning params.length is
// args.length+1. In that case we have to fill in an empty array
handle = MethodHandles.insertArguments(handle, params.length-1, Array.newInstance(lastParam.getComponentType(), 0));
if (LOG_ENABLED) LOG.info("added empty array for missing vargs part");
} else { //params.length < args.length
// we depend on the method selection having done a good
// job before already, so the only case for this here is, that
// all trailing arguments belong into the vargs array
handle = handle.asCollector(
lastParam,
args.length - params.length + 1);
if (LOG_ENABLED) LOG.info("changed surplus arguments to be collected for vargs call");
}
}
/**
* There are some conversions we have to do explicitly.
* These are GString to String, Number to Byte and Number to BigInteger
* conversions.
*/
public void correctCoerce() {
if (useMetaClass) return;
Class[] parameters = handle.type().parameterArray();
if (currentType!=null) parameters = currentType.parameterArray();
if (args.length != parameters.length) {
throw new GroovyBugError("At this point argument array length and parameter array length should be the same");
}
for (int i=0; i
© 2015 - 2024 Weber Informatics LLC | Privacy Policy