
org.codehaus.groovy.vmplugin.v7.Selector Maven / Gradle / Ivy
/*
* 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.AdaptingMetaClass;
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.MetaMethod;
import groovy.lang.MetaProperty;
import groovy.lang.MissingMethodException;
import groovy.lang.MetaClassImpl.MetaConstructor;
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 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.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.NullObject;
import org.codehaus.groovy.runtime.GroovyCategorySupport.CategoryMethod;
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 static org.codehaus.groovy.vmplugin.v7.IndyInterface.*;
import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.*;
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;
/**
* 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.");
}
return null;
}
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 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;
MetaProperty res = mci.getEffectiveGetMetaProperty(mci.getTheClass(), 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) {
String msg =
"----------------------------------------------------"+
"\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 - 2025 Weber Informatics LLC | Privacy Policy