blob: aecfba8e6ae5607445866f2692fdd98bd6d806a3 [file] [log] [blame]
/*
* 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.v8;
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 groovy.transform.Internal;
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.VMPluginFactory;
import org.codehaus.groovy.vmplugin.v8.IndyInterface.CallType;
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.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.ARRAYLIST_CONSTRUCTOR;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.BEAN_CONSTRUCTOR_PROPERTY_SETTER;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.BOOLEAN_IDENTITY;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.CLASS_FOR_NAME;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.DTT_CAST_TO_TYPE;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.EQUALS;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.GROOVY_CAST_EXCEPTION;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.GROOVY_OBJECT_GET_PROPERTY;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.GROOVY_OBJECT_INVOKER;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.HASHSET_CONSTRUCTOR;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.HAS_CATEGORY_IN_CURRENT_THREAD_GUARD;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.INTERCEPTABLE_INVOKER;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.IS_NULL;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.META_CLASS_INVOKE_STATIC_METHOD;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.META_METHOD_INVOKER;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.META_PROPERTY_GETTER;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.MOP_GET;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.MOP_INVOKE_CONSTRUCTOR;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.MOP_INVOKE_METHOD;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.NULL_REF;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.SAME_CLASS;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.SAME_MC;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.SAM_CONVERSION;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.UNWRAP_EXCEPTION;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.UNWRAP_METHOD;
import static org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.unwrap;
import static org.codehaus.groovy.vmplugin.v8.IndyInterface.LOG;
import static org.codehaus.groovy.vmplugin.v8.IndyInterface.LOG_ENABLED;
import static org.codehaus.groovy.vmplugin.v8.IndyInterface.LOOKUP;
import static org.codehaus.groovy.vmplugin.v8.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 CallType callType;
/**
* Cache values for read-only access
*/
private static final CallType[] CALL_TYPE_VALUES = CallType.values();
/**
* Returns the Selector
*/
public static Selector getSelector(MutableCallSite callSite, Class<?> sender, String methodName, int callID, boolean safeNavigation, boolean thisCall, boolean spreadCall, Object[] arguments) {
CallType callType = CALL_TYPE_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 final Class<?> staticSourceType, staticTargetType;
public CastSelector(MutableCallSite callSite, Object[] arguments) {
super(callSite, Selector.class, "", CallType.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);
}
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();
}
}
}
private static class PropertySelector extends MethodSelector {
private boolean insertName = false;
public PropertySelector(MutableCallSite callSite, Class<?> sender, String methodName, CallType 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();
try {
Method reflectionMethod = aClass.getMethod("getProperty", String.class);
if (!reflectionMethod.isSynthetic() && !isMarkedInternal(reflectionMethod)) {
handle = MethodHandles.insertArguments(GROOVY_OBJECT_GET_PROPERTY, 1, name);
return;
}
} catch (ReflectiveOperationException ignored) {
}
} 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.getCachedField();
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);
}
}
private boolean isMarkedInternal(Method reflectionMethod) {
return reflectionMethod.getAnnotation(Internal.class) != null;
}
/**
* 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 setMetaClassCallHandleIfNeeded(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, CallType 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 MetaClass getMetaClass() {
Object receiver = args[0];
mc = GroovySystem.getMetaClassRegistry().getMetaClass((Class<?>) receiver);
return mc;
}
/**
* 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().getCachedConstructor();
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 setMetaClassCallHandleIfNeeded(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.
*/
private static class MethodSelector extends Selector {
private static final Object[] SINGLE_NULL_ARRAY = {null};
protected MetaClass mc;
private boolean isCategoryMethod;
public MethodSelector(MutableCallSite callSite, Class<?> sender, String methodName, CallType 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 < arguments.length; i++) {
msg.append("\n\t\t\targument[").append(i).append("] = ");
if (arguments[i] == null) {
msg.append("null");
} else {
msg.append(arguments[i].getClass().getName()).append("@").append(Integer.toHexString(System.identityHashCode(arguments[i])));
}
}
LOG.info(msg.toString());
}
}
/**
* Sets the null constant for safe navigation.
* In case of foo?.bar() and foo being null, we don't call the method,
* instead we simply return null. This produces a handle, which will
* return the constant.
*/
public boolean setNullForSafeNavigation() {
if (!safeNavigation) return false;
handle = MethodHandles.dropArguments(NULL_REF, 0, targetType.parameterArray());
if (LOG_ENABLED) LOG.info("set null returning handle for safe navigation");
return true;
}
/**
* Gives the meta class to an Object.
*/
public MetaClass getMetaClass() {
Object receiver = args[0];
if (receiver == null) {
mc = NullObject.getNullObject().getMetaClass();
} else if (receiver instanceof Class) {
Class<?> c = (Class<?>) receiver;
mc = GroovySystem.getMetaClassRegistry().getMetaClass(c);
cache &= !ClassInfo.getClassInfo(c).hasPerInstanceMetaClasses();
} else if (Proxy.isProxyClass(receiver.getClass())) {
// GROOVY-5410: receiver.getMetaClass() returns meta class for proxied object;
// later when metaClass.getTheClass() is called, the $ProxyN reference is lost
mc = GroovySystem.getMetaClassRegistry().getMetaClass(receiver.getClass());
cache = false;
} else if (receiver instanceof GroovyObject) {
mc = ((GroovyObject) receiver).getMetaClass();
} else {
mc = ((MetaClassRegistryImpl) GroovySystem.getMetaClassRegistry()).getMetaClass(receiver);
cache &= !ClassInfo.getClassInfo(receiver.getClass()).hasPerInstanceMetaClasses();
}
mc.initialize();
return mc;
}
/**
* Uses the meta class to get a meta method for a method call.
* There will be no meta method selected, if the meta class is no MetaClassImpl
* or the meta class is an AdaptingMetaClass.
*/
public void chooseMeta(MetaClassImpl mci) {
if (mci == null) return;
Object receiver = getCorrectedReceiver();
Object[] newArgs = removeRealReceiver(args);
if (receiver instanceof Class) {
if (LOG_ENABLED) LOG.info("receiver is a class");
if (!mci.hasCustomStaticInvokeMethod()) method = mci.retrieveStaticMethod(name, newArgs);
} else {
String changedName = name;
if (receiver instanceof GeneratedClosure && changedName.equals("call")) {
changedName = "doCall";
}
if (!mci.hasCustomInvokeMethod())
method = mci.getMethodWithCaching(selectionBase, changedName, newArgs, false);
}
if (LOG_ENABLED) LOG.info("retrieved method from meta class: " + method);
}
/**
* Creates a MethodHandle using a before selected MetaMethod.
* If the MetaMethod has reflective information available, then
* we will use that information to create the target MethodHandle.
* If that is not the case we will produce a handle, which will use the
* MetaMethod itself for invocation.
*/
public void setHandleForMetaMethod() {
MetaMethod metaMethod = method;
isCategoryMethod = method instanceof CategoryMethod;
if (metaMethod instanceof NumberNumberMetaMethod
|| (method instanceof GeneratedMetaMethod && (name.equals("next") || name.equals("previous")))) {
if (LOG_ENABLED) LOG.info("meta method is number method");
if (IndyMath.chooseMathMethod(this, metaMethod)) {
catchException = false;
if (LOG_ENABLED) LOG.info("indy math successful");
return;
}
}
boolean isCategoryTypeMethod = metaMethod instanceof NewInstanceMetaMethod;
if (LOG_ENABLED) LOG.info("meta method is category type method: " + isCategoryTypeMethod);
boolean isStaticCategoryTypeMethod = metaMethod instanceof NewStaticMetaMethod;
if (LOG_ENABLED) LOG.info("meta method is static category type method: " + isCategoryTypeMethod);
if (metaMethod instanceof ReflectionMetaMethod) {
if (LOG_ENABLED) LOG.info("meta method is reflective method");
ReflectionMetaMethod rmm = (ReflectionMetaMethod) metaMethod;
metaMethod = rmm.getCachedMethod();
}
if (metaMethod instanceof CachedMethod) {
if (LOG_ENABLED) LOG.info("meta method is CachedMethod instance");
CachedMethod cm = (CachedMethod) metaMethod;
cm = (CachedMethod) VMPluginFactory.getPlugin().transformMetaMethod(getMetaClass(), cm);
isVargs = cm.isVargsMethod();
try {
Method m = cm.getCachedMethod();
handle = correctClassForNameAndUnReflectOtherwise(m);
if (LOG_ENABLED) LOG.info("successfully unreflected method");
if (isStaticCategoryTypeMethod) {
handle = MethodHandles.insertArguments(handle, 0, new Object[]{null});
handle = MethodHandles.dropArguments(handle, 0, targetType.parameterType(0));
} else if (!isCategoryTypeMethod && isStatic(m)) {
// we drop the receiver, which might be a Class (invocation on Class)
// or it might be an object (static method invocation on instance)
// Object.class handles both cases at once
handle = MethodHandles.dropArguments(handle, 0, Object.class);
}
} catch (IllegalAccessException e) {
throw new GroovyBugError(e);
}
} else if (method != null) {
if (LOG_ENABLED) LOG.info("meta method is dgm helper");
// generic meta method invocation path
handle = META_METHOD_INVOKER;
handle = handle.bindTo(method);
if (spread) {
args = originalArguments;
skipSpreadCollector = true;
} else {
// wrap arguments from call site in Object[]
handle = handle.asCollector(Object[].class, targetType.parameterCount() - 1);
}
currentType = removeWrapper(targetType);
if (LOG_ENABLED) LOG.info("bound method name to META_METHOD_INVOKER");
}
}
private MethodHandle correctClassForNameAndUnReflectOtherwise(Method m) throws IllegalAccessException {
if (m.getDeclaringClass() == Class.class && m.getName().equals("forName") && m.getParameterTypes().length == 1) {
return MethodHandles.insertArguments(CLASS_FOR_NAME, 1, true, sender.getClassLoader());
} else {
return LOOKUP.unreflect(m);
}
}
/**
* Helper method to manipulate the given type to replace Wrapper with Object.
*/
private MethodType removeWrapper(MethodType targetType) {
Class<?>[] types = targetType.parameterArray();
for (int i = 0; i < types.length; i++) {
if (types[i] == Wrapper.class) {
targetType = targetType.changeParameterType(i, Object.class);
}
}
return targetType;
}
/**
* Creates a MethodHandle, which will use the meta class path.
* This method is called only if no handle has been created before. This
* is usually the case if the method selection failed.
*/
public void setMetaClassCallHandleIfNeeded(boolean standardMetaClass) {
if (handle != null) return;
useMetaClass = true;
if (LOG_ENABLED) LOG.info("set meta class invocation path");
Object receiver = getCorrectedReceiver();
if (receiver instanceof Class) {
handle = META_CLASS_INVOKE_STATIC_METHOD.bindTo(mc);
if (LOG_ENABLED) LOG.info("use invokeStaticMethod with bound meta class");
} else {
handle = MOP_INVOKE_METHOD.bindTo(mc);
if (LOG_ENABLED) LOG.info("use invokeMethod with bound meta class");
if (receiver instanceof GroovyObject) {
// if the meta class call fails we may still want to fall back to call
// GroovyObject#invokeMethod if the receiver is a GroovyObject
if (LOG_ENABLED) LOG.info("add MissingMethod handler for GrooObject#invokeMethod fallback path");
handle = MethodHandles.catchException(handle, MissingMethodException.class, GROOVY_OBJECT_INVOKER);
}
}
handle = MethodHandles.insertArguments(handle, 1, name);
if (!spread) handle = handle.asCollector(Object[].class, targetType.parameterCount() - 1);
if (LOG_ENABLED) LOG.info("bind method name and create collector for arguments");
}
/**
* Corrects method argument wrapping.
* In cases in which we want to force a certain method selection
* we use Wrapper classes to transport the static type information.
* This method will be used to undo the wrapping.
*/
public void correctWrapping() {
if (useMetaClass) return;
Class<?>[] pt = handle.type().parameterArray();
if (currentType != null) pt = currentType.parameterArray();
for (int i = 1; i < args.length; i++) {
if (args[i] instanceof Wrapper) {
Class<?> type = pt[i];
MethodType mt = MethodType.methodType(type, Wrapper.class);
handle = MethodHandles.filterArguments(handle, i, UNWRAP_METHOD.asType(mt));
if (LOG_ENABLED) LOG.info("added filter for Wrapper for argument at pos " + i);
}
}
}
/**
* Handles cases in which we have to correct the length of arguments
* using the parameters. This might be needed for vargs and for one
* parameter calls without arguments (null is used then).
*/
public void correctParameterLength() {
if (handle == null) return;
Class<?>[] params = handle.type().parameterArray();
if (currentType != null) params = currentType.parameterArray();
if (!isVargs) {
if (spread && useMetaClass) return;
if (params.length == 2 && args.length == 1) {
handle = MethodHandles.insertArguments(handle, 1, SINGLE_NULL_ARRAY);
}
return;
}
Class<?> lastParam = params[params.length - 1];
Object lastArg = unwrapIfWrapped(args[args.length - 1]);
if (params.length == args.length) {
// may need rewrap
if (lastArg == null) return;
if (lastParam.isInstance(lastArg)) return;
if (lastArg.getClass().isArray()) return;
// arg is not null and not assignment compatible
// so we really need to rewrap
handle = handle.asCollector(lastParam, 1);
} else if (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
// 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 < args.length; i++) {
if (parameters[i] == Object.class) continue;
Object arg = unwrapIfWrapped(args[i]);
// we have to handle here different cases in which we do no
// transformations. We depend on our method selection to have
// selected only a compatible method, that means for a null
// argument we don't have to do anything. Same of course is if
// the argument is an instance of the parameter type. We also
// exclude boxing, since the MethodHandles will do that part
// already for us. Another case is the conversion of a primitive
// to another primitive or of the wrappers, or a combination of
// these. This is also handled already. What is left is the
// GString conversion and the number conversions.
if (arg == null) continue;
Class<?> got = arg.getClass();
// equal class, nothing to do
if (got == parameters[i]) continue;
Class<?> wrappedPara = TypeHelper.getWrapperClass(parameters[i]);
// equal class with one maybe a primitive, the later explicitCastArguments will solve this case
if (wrappedPara == TypeHelper.getWrapperClass(got)) continue;
// equal in terms of an assignment in Java. That means according to Java widening rules, or
// a subclass, interface, superclass relation, this case then handles also
// primitive to primitive conversion. Those case are also solved by explicitCastArguments.
if (parameters[i].isAssignableFrom(got)) continue;
// to aid explicitCastArguments we convert to the wrapper type to let is only unbox
handle = TypeTransformers.addTransformer(handle, i, arg, wrappedPara);
if (LOG_ENABLED)
LOG.info("added transformer at pos " + i + " for type " + got + " to type " + wrappedPara);
}
}
/**
* Gives a replacement receiver for null.
* In case of the receiver being null we want to do the method
* invocation on NullObject instead.
*/
public void correctNullReceiver() {
if (args[0] != null) return;
handle = handle.bindTo(NullObject.getNullObject());
handle = MethodHandles.dropArguments(handle, 0, targetType.parameterType(0));
if (LOG_ENABLED) LOG.info("binding null object receiver and dropping old receiver");
}
public void correctSpreading() {
if (!spread || useMetaClass || skipSpreadCollector) return;
handle = handle.asSpreader(Object[].class, args.length - 1);
}
/**
* Adds the standard exception handler.
*/
public void addExceptionHandler() {
//TODO: if we would know exactly which paths require the exceptions
// and which paths not, we can sometimes save this guard
if (handle == null || !catchException) return;
Class<?> returnType = handle.type().returnType();
if (returnType != Object.class) {
MethodType mtype = MethodType.methodType(returnType, GroovyRuntimeException.class);
handle = MethodHandles.catchException(handle, GroovyRuntimeException.class, UNWRAP_EXCEPTION.asType(mtype));
} else {
handle = MethodHandles.catchException(handle, GroovyRuntimeException.class, UNWRAP_EXCEPTION);
}
if (LOG_ENABLED) LOG.info("added GroovyRuntimeException unwrapper");
}
/**
* Sets all argument and receiver guards.
*/
public void setGuards(Object receiver) {
if (handle == null) return;
if (!cache) return;
MethodHandle fallback;
if (callSite instanceof CacheableCallSite) {
fallback = ((CacheableCallSite) callSite).getFallbackTarget();
} else {
throw new GroovyBugError("CacheableCallSite is expected, but the actual callsite is: " + callSite);
}
// special guards for receiver
if (receiver instanceof GroovyObject) {
GroovyObject go = (GroovyObject) receiver;
MetaClass mc = go.getMetaClass();
MethodHandle test = SAME_MC.bindTo(mc);
// drop dummy receiver
test = test.asType(MethodType.methodType(boolean.class, targetType.parameterType(0)));
handle = MethodHandles.guardWithTest(test, handle, fallback);
if (LOG_ENABLED) LOG.info("added meta class equality check");
} else if (receiver instanceof Class) {
MethodHandle test = EQUALS.bindTo(receiver);
test = test.asType(MethodType.methodType(boolean.class, targetType.parameterType(0)));
handle = MethodHandles.guardWithTest(test, handle, fallback);
if (LOG_ENABLED) LOG.info("added class equality check");
}
if (!useMetaClass && isCategoryMethod) {
// category method needs Thread check
// cases:
// (1) method is a category method
// We need to check if the category in the current thread is still active.
// Since we invalidate on leaving the category checking for it being
// active directly is good enough.
// (2) method is in use scope, but not from category
// Since entering/leaving a category will invalidate, there is no need for any special check
// (3) method is not in use scope /and not from category
// Since entering/leaving a category will invalidate, there is no need for any special check
if (method instanceof NewInstanceMetaMethod) {
handle = MethodHandles.guardWithTest(HAS_CATEGORY_IN_CURRENT_THREAD_GUARD, handle, fallback);
if (LOG_ENABLED) LOG.info("added category-in-current-thread-guard for category method");
}
}
// handle constant meta class and category changes
handle = switchPoint.guardWithTest(handle, fallback);
if (LOG_ENABLED) LOG.info("added switch point guard");
// guards for receiver and parameter
Class<?>[] pt = handle.type().parameterArray();
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
Class<?> paramType = pt[i];
MethodHandle test;
if (arg == null) {
test = IS_NULL.asType(MethodType.methodType(boolean.class, paramType));
if (LOG_ENABLED) LOG.info("added null argument check at pos " + i);
} else {
Class<?> argClass = arg.getClass();
if (paramType.isPrimitive()) continue;
//if (Modifier.isFinal(argClass.getModifiers()) && TypeHelper.argumentClassIsParameterClass(argClass,pt[i])) continue;
test = SAME_CLASS.
bindTo(argClass).
asType(MethodType.methodType(boolean.class, paramType));
if (LOG_ENABLED) LOG.info("added same class check at pos " + i);
}
Class<?>[] drops = new Class[i];
System.arraycopy(pt, 0, drops, 0, drops.length);
test = MethodHandles.dropArguments(test, 0, drops);
handle = MethodHandles.guardWithTest(test, handle, fallback);
}
}
/**
* do the actual call site target set, if the call is supposed to be cached
*/
public void doCallSiteTargetSet() {
if (LOG_ENABLED) LOG.info("call site stays uncached");
/*
if (!cache) {
if (LOG_ENABLED) LOG.info("call site stays uncached");
} else {
callSite.setTarget(handle);
if (LOG_ENABLED) LOG.info("call site target set, preparing outside invocation");
}
*/
}
/**
* Sets the selection base.
*/
public void setSelectionBase() {
if (thisCall) {
selectionBase = sender;
} else if (args[0] == null) {
selectionBase = NullObject.class;
} else {
selectionBase = mc.getTheClass();
}
if (LOG_ENABLED) LOG.info("selection base set to " + selectionBase);
}
/**
* Sets a handle to call {@link GroovyInterceptable#invokeMethod(String, Object)}
*/
public boolean setInterceptor() {
if (!(this.args[0] instanceof GroovyInterceptable)) return false;
handle = MethodHandles.insertArguments(INTERCEPTABLE_INVOKER, 1, this.name);
handle = handle.asCollector(Object[].class, targetType.parameterCount() - 1);
handle = handle.asType(targetType);
return true;
}
/**
* setting a call site target consists of the following steps:
* # get the meta class
* # select a method/constructor/property from it, if it is a MetaClassImpl
* # make a handle out of the selection
* # if nothing could be selected select a path through the given MetaClass or the GroovyObject
* # apply transformations for vargs, implicit null argument, coercion, wrapping, null receiver and spreading
*/
@Override
public void setCallSiteTarget() {
if (!setNullForSafeNavigation() && !setInterceptor()) {
getMetaClass();
if (LOG_ENABLED) LOG.info("meta class is " + mc);
setSelectionBase();
MetaClassImpl mci = getMetaClassImpl(mc, callType != CallType.GET);
chooseMeta(mci);
setHandleForMetaMethod();
setMetaClassCallHandleIfNeeded(mci != null);
correctParameterLength();
correctCoerce();
correctWrapping();
correctNullReceiver();
correctSpreading();
if (LOG_ENABLED) LOG.info("casting explicit from " + handle.type() + " to " + targetType);
handle = MethodHandles.explicitCastArguments(handle, targetType);
addExceptionHandler();
}
setGuards(args[0]);
doCallSiteTargetSet();
}
}
/**
* Unwraps the given object from a {@link Wrapper}. If not
* wrapped, the given object is returned.
*/
private static Object unwrapIfWrapped(Object object) {
if (object instanceof Wrapper) return unwrap(object);
return object;
}
/**
* Returns {@link NullObject#getNullObject()} if the receiver
* (args[0]) is null. If it is not null, the recevier itself
* is returned.
*/
public Object getCorrectedReceiver() {
Object receiver = args[0];
if (receiver == null) {
if (LOG_ENABLED) LOG.info("receiver is null");
receiver = NullObject.getNullObject();
}
return receiver;
}
/**
* Returns if a method is static
*/
private static boolean isStatic(Method m) {
int mods = m.getModifiers();
return (mods & Modifier.STATIC) != 0;
}
/**
* Returns the MetaClassImpl if the given MetaClass is one of
* MetaClassImpl, AdaptingMetaClass or ClosureMetaClass. If
* none of these cases matches, this method returns null.
*/
private static MetaClassImpl getMetaClassImpl(MetaClass mc, boolean includeEMC) {
Class<?> mcc = mc.getClass();
boolean valid = mcc == MetaClassImpl.class ||
mcc == AdaptingMetaClass.class ||
mcc == ClosureMetaClass.class ||
(includeEMC && mcc == ExpandoMetaClass.class);
if (!valid) {
if (LOG_ENABLED)
LOG.info("meta class is neither MetaClassImpl, nor AdoptingMetaClass, nor ClosureMetaClass, normal method selection path disabled.");
return null;
}
if (LOG_ENABLED) LOG.info("meta class is a recognized MetaClassImpl");
return (MetaClassImpl) mc;
}
/**
* Helper method to remove the receiver from the argument array
* by producing a new array.
*/
private static Object[] removeRealReceiver(Object[] args) {
Object[] ar = new Object[args.length - 1];
System.arraycopy(args, 1, ar, 0, args.length - 1);
return ar;
}
}