org.codehaus.groovy.runtime.metaclass.ClosureMetaClass Maven / Gradle / Ivy
Show all versions of groovy Show documentation
/*
* 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.runtime.metaclass;
import groovy.lang.Closure;
import groovy.lang.ExpandoMetaClass;
import groovy.lang.GroovyInterceptable;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.MetaBeanProperty;
import groovy.lang.MetaClass;
import groovy.lang.MetaClassImpl;
import groovy.lang.MetaClassRegistry;
import groovy.lang.MetaMethod;
import groovy.lang.MetaProperty;
import groovy.lang.MissingMethodException;
import groovy.lang.MissingPropertyException;
import groovy.lang.ProxyMetaClass;
import org.codehaus.groovy.reflection.CachedClass;
import org.codehaus.groovy.reflection.CachedField;
import org.codehaus.groovy.reflection.CachedMethod;
import org.codehaus.groovy.reflection.ParameterTypes;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.MetaClassHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.codehaus.groovy.runtime.callsite.PogoMetaClassSite;
import org.codehaus.groovy.runtime.wrappers.Wrapper;
import org.codehaus.groovy.util.FastArray;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A metaclass for closures generated by the Groovy compiler. These classes
* have special characteristics this MetaClass uses. One of these is that a
* generated Closure has only additional doCall methods, all other methods
* are in the Closure class as well. To use this fact this MetaClass uses
* a MetaClass for Closure as static field And delegates calls to this
* MetaClass if needed. This allows a lean implementation for this MetaClass.
* Multiple generated closures will then use the same MetaClass for Closure.
* For static dispatching this class uses the MetaClass of Class, again
* all instances of this class will share that MetaClass. The Class MetaClass
* is initialized lazy, because most operations do not need this MetaClass.
*
* The Closure and Class MetaClasses are not replaceable.
*
* This MetaClass is for internal usage only!
*
* @since 1.5
*/
public final class ClosureMetaClass extends MetaClassImpl {
private volatile boolean initialized, attributeInitDone;
private final FastArray closureMethods = new FastArray(3);
private final Map attributes = new HashMap<>();
private MethodChooser chooser;
private static MetaClassImpl classMetaClass;
private static MetaClassImpl CLOSURE_METACLASS;
private static final Object[] EMPTY_ARGUMENTS = {};
private static final String CLOSURE_CALL_METHOD = "call";
private static final String CLOSURE_DO_CALL_METHOD = "doCall";
static {
resetCachedMetaClasses();
}
public static void resetCachedMetaClasses() {
MetaClassImpl temp = new MetaClassImpl(Closure.class);
temp.initialize();
synchronized (ClosureMetaClass.class) {
CLOSURE_METACLASS = temp;
}
if (classMetaClass != null) {
temp = new MetaClassImpl(Class.class);
temp.initialize();
synchronized (ClosureMetaClass.class) {
classMetaClass = temp;
}
}
}
private static synchronized MetaClass getStaticMetaClass() {
if (classMetaClass == null) {
classMetaClass = new MetaClassImpl(Class.class);
classMetaClass.initialize();
}
return classMetaClass;
}
private interface MethodChooser {
Object chooseMethod(Class[] arguments, boolean coerce);
}
private static class StandardClosureChooser implements MethodChooser {
private final MetaMethod doCall0;
private final MetaMethod doCall1;
StandardClosureChooser(final MetaMethod m0, final MetaMethod m1) {
doCall0 = m0;
doCall1 = m1;
}
@Override
public Object chooseMethod(final Class[] arguments, final boolean coerce) {
if (arguments.length == 0) return doCall0;
if (arguments.length == 1) return doCall1;
return null;
}
}
private static class NormalMethodChooser implements MethodChooser {
private final FastArray methods;
final Class theClass;
NormalMethodChooser(final Class theClass, final FastArray methods) {
this.theClass = theClass;
this.methods = methods;
}
@Override
public Object chooseMethod(final Class[] arguments, final boolean coerce) {
if (arguments.length == 0) {
return MetaClassHelper.chooseEmptyMethodParams(methods);
} else if (arguments.length == 1 && arguments[0] == null) {
return MetaClassHelper.chooseMostGeneralMethodWith1NullParam(methods);
} else {
List matchingMethods = new ArrayList();
final Object[] data = methods.getArray();
for (int i = 0, n = methods.size(); i < n; i += 1) {
Object method = data[i];
// making this false helps find matches
if (((ParameterTypes) method).isValidMethod(arguments)) {
matchingMethods.add(method);
}
}
int size = matchingMethods.size();
if (0 == size) {
return null;
} else if (1 == size) {
return matchingMethods.get(0);
}
return chooseMostSpecificParams(CLOSURE_DO_CALL_METHOD, matchingMethods, arguments);
}
}
private Object chooseMostSpecificParams(final String name, final List matchingMethods, final Class[] arguments) {
return doChooseMostSpecificParams(theClass.getName(), name, matchingMethods, arguments, true);
}
}
//--------------------------------------------------------------------------
public ClosureMetaClass(final MetaClassRegistry registry, final Class theClass) {
super(registry, theClass);
}
@Override
public MetaProperty getMetaProperty(final String name) {
return CLOSURE_METACLASS.getMetaProperty(name);
}
private static void unwrap(final Object[] arguments) {
for (int i = 0; i != arguments.length; i++) {
if (arguments[i] instanceof Wrapper) {
arguments[i] = ((Wrapper) arguments[i]).unwrap();
}
}
}
private MetaMethod pickClosureMethod(final Class[] argClasses) {
Object answer = chooser.chooseMethod(argClasses, false);
return (MetaMethod) answer;
}
private MetaMethod getDelegateMethod(final Closure closure, final Object delegate, final String methodName, final Class[] argClasses) {
if (delegate == closure || delegate == null) return null;
if (delegate instanceof Class) {
for (Class superClass = (Class) delegate;
superClass != Object.class && superClass != null;
superClass = superClass.getSuperclass())
{
MetaClass mc = registry.getMetaClass(superClass);
MetaMethod method = mc.getStaticMetaMethod(methodName, argClasses);
if (method != null) return method;
}
return null;
} else if (delegate instanceof GroovyInterceptable) {
MetaClass delegateMetaClass = lookupObjectMetaClass(delegate);
// GROOVY-3015: must route calls through GroovyObject#invokeMethod(String,Object)
MetaMethod interceptMethod = delegateMetaClass.pickMethod("invokeMethod", new Class[]{String.class, Object.class});
return new TransformMetaMethod(interceptMethod) {
@Override
public Object invoke(final Object object, final Object[] arguments) {
return super.invoke(object, new Object[]{methodName, arguments});
}
};
} else {
MetaClass delegateMetaClass = lookupObjectMetaClass(delegate);
MetaMethod method = delegateMetaClass.pickMethod(methodName, argClasses);
if (method != null) {
return method;
}
if (delegateMetaClass instanceof ExpandoMetaClass) {
method = ((ExpandoMetaClass) delegateMetaClass).findMixinMethod(methodName, argClasses);
if (method != null) {
onMixinMethodFound(method);
return method;
}
}
if (delegateMetaClass instanceof MetaClassImpl) {
method = MetaClassImpl.findMethodInClassHierarchy(getTheClass(), methodName, argClasses, this);
if (method != null) {
onSuperMethodFoundInHierarchy(method);
return method;
}
}
return method;
}
}
@Override
public Object invokeMethod(final Class sender, final Object object, final String methodName, final Object[] originalArguments, final boolean isCallToSuper, final boolean fromInsideClass) {
checkInitalised();
if (object == null) {
throw new NullPointerException("Cannot invoke method: " + methodName + " on null object");
}
final Object[] arguments = makeArguments(originalArguments, methodName);
final Class>[] argClasses = MetaClassHelper.convertToTypeArray(arguments);
unwrap(arguments);
MetaMethod method = null;
final Closure> closure = (Closure>) object;
final int resolveStrategy = closure.getResolveStrategy();
if (CLOSURE_DO_CALL_METHOD.equals(methodName) || CLOSURE_CALL_METHOD.equals(methodName)) {
method = pickClosureMethod(argClasses);
if (method == null && arguments.length == 1 && arguments[0] instanceof List) {
Object[] newArguments = ((List>) arguments[0]).toArray();
Class>[] newArgClasses = MetaClassHelper.convertToTypeArray(newArguments);
method = createTransformMetaMethod(pickClosureMethod(newArgClasses));
}
if (method == null) throw new MissingMethodException(methodName, theClass, arguments, false);
}
boolean shouldDefer = resolveStrategy == Closure.DELEGATE_ONLY && isInternalMethod(methodName);
if (method == null && !shouldDefer) {
method = CLOSURE_METACLASS.pickMethod(methodName, argClasses);
}
if (method != null) return method.doMethodInvoke(object, arguments);
MissingMethodException last = null;
Object callObject = object;
final Object owner = closure.getOwner();
final Object delegate = closure.getDelegate();
boolean invokeOnDelegate = false;
boolean invokeOnOwner = false;
boolean ownerFirst = true;
switch (resolveStrategy) {
case Closure.TO_SELF:
break;
case Closure.DELEGATE_ONLY:
method = getDelegateMethod(closure, delegate, methodName, argClasses);
callObject = delegate;
if (method == null) {
invokeOnDelegate = delegate != closure && (delegate instanceof GroovyObject);
}
break;
case Closure.OWNER_ONLY:
method = getDelegateMethod(closure, owner, methodName, argClasses);
callObject = owner;
if (method == null) {
invokeOnOwner = owner != closure && (owner instanceof GroovyObject);
}
break;
case Closure.DELEGATE_FIRST:
method = getDelegateMethod(closure, delegate, methodName, argClasses);
callObject = delegate;
if (method == null) {
invokeOnDelegate = delegate != closure;
invokeOnOwner = owner != closure;
ownerFirst = false;
}
break;
default: // Closure.OWNER_FIRST:
method = getDelegateMethod(closure, owner, methodName, argClasses);
callObject = owner;
if (method == null) {
invokeOnDelegate = delegate != closure;
invokeOnOwner = owner != closure;
ownerFirst = true;
}
break;
}
if (method == null && (invokeOnOwner || invokeOnDelegate)) {
try {
if (ownerFirst) {
return invokeOnDelegationObjects(invokeOnOwner, owner, invokeOnDelegate, delegate, methodName, arguments);
} else {
return invokeOnDelegationObjects(invokeOnDelegate, delegate, invokeOnOwner, owner, methodName, arguments);
}
} catch (MissingMethodException mme) {
last = mme;
}
}
if (method != null) {
MetaClass metaClass = registry.getMetaClass(callObject.getClass());
if (metaClass instanceof ProxyMetaClass) {
return metaClass.invokeMethod(callObject, methodName, arguments);
} else {
return method.doMethodInvoke(callObject, arguments);
}
} else {
// no method was found; try to find a closure defined as a field of the class and run it
Object value = null;
try {
value = getProperty(sender, object, methodName, isCallToSuper, fromInsideClass);
} catch (MissingPropertyException mpe) {
// ignore
}
if (value instanceof Closure) { // This test ensures that value != this If you ever change this ensure that value != this
Closure> cl = (Closure>) value;
MetaClass delegateMetaClass = cl.getMetaClass();
return delegateMetaClass.invokeMethod(cl.getClass(), closure, CLOSURE_DO_CALL_METHOD, originalArguments, false, fromInsideClass);
}
}
throw last != null ? last : new MissingMethodException(methodName, theClass, arguments, false);
}
private static boolean isInternalMethod(final String methodName) {
switch (methodName) {
case "curry":
case "ncurry":
case "rcurry":
case "leftShift":
case "rightShift":
return true;
default:
return false;
}
}
private static Object[] makeArguments(final Object[] arguments, final String methodName) {
if (arguments == null) return EMPTY_ARGUMENTS;
return arguments;
}
private static Throwable unwrap(final GroovyRuntimeException gre) {
Throwable th = gre;
if (th.getCause() != null && th.getCause() != gre) th = th.getCause();
if (th != gre && (th instanceof GroovyRuntimeException)) return unwrap((GroovyRuntimeException) th);
return th;
}
private static Object invokeOnDelegationObjects(final boolean invoke1, final Object o1, final boolean invoke2, final Object o2, final String methodName, final Object[] args) {
MissingMethodException first = null;
if (invoke1) {
try {
return InvokerHelper.invokeMethod(o1, methodName, args);
} catch (MissingMethodException mme) {
first = mme;
} catch (GroovyRuntimeException gre) {
Throwable t = unwrap(gre);
if (t instanceof MissingMethodException && methodName.equals(((MissingMethodException) t).getMethod())) {
first = (MissingMethodException) t;
} else {
throw gre;
}
}
}
if (invoke2 && (!invoke1 || o1 != o2)) {
try {
return InvokerHelper.invokeMethod(o2, methodName, args);
} catch (MissingMethodException mme) {
// patch needed here too, but we need a test case to trip it first
if (first == null) first = mme;
} catch (GroovyRuntimeException gre) {
Throwable t = unwrap(gre);
if (t instanceof MissingMethodException) {
first = (MissingMethodException) t;
} else {
throw gre;
}
}
}
throw first;
}
private synchronized void initAttributes() {
if (!attributes.isEmpty()) return;
attributes.put("!", null); // just a dummy for later
CachedField[] fieldArray = theCachedClass.getFields();
for (CachedField aFieldArray : fieldArray) {
attributes.put(aFieldArray.getName(), aFieldArray);
}
attributeInitDone = !attributes.isEmpty();
}
@Override
public synchronized void initialize() {
if (!isInitialized()) {
CachedMethod[] methodArray = theCachedClass.getMethods();
synchronized (theCachedClass) {
for (final CachedMethod cachedMethod : methodArray) {
if (!cachedMethod.getName().equals(CLOSURE_DO_CALL_METHOD)) continue;
closureMethods.add(cachedMethod);
}
}
assignMethodChooser();
setInitialized(true);
}
}
private void assignMethodChooser() {
if (closureMethods.size() == 1) {
final MetaMethod doCall = (MetaMethod) closureMethods.get(0);
final CachedClass[] c = doCall.getParameterTypes();
int length = c.length;
if (length == 0) {
// no arg method
chooser = (arguments, coerce) -> {
if (arguments.length == 0) return doCall;
return null;
};
} else {
if (length == 1 && c[0].getTheClass() == Object.class) {
// Object fits all, so simple dispatch rule here
chooser = (arguments, coerce) -> {
// <2, because foo() is same as foo(null)
if (arguments.length < 2) return doCall;
return null;
};
} else {
boolean allObject = true;
for (int i = 0; i < c.length - 1; i++) {
if (c[i].getTheClass() != Object.class) {
allObject = false;
break;
}
}
if (allObject && c[c.length - 1].getTheClass() == Object.class) {
// all arguments are object, so test only if argument number is correct
chooser = (arguments, coerce) -> {
if (arguments.length == c.length) return doCall;
return null;
};
} else {
if (allObject && c[c.length - 1].getTheClass() == Object[].class) {
// all arguments are Object but last, which is a vargs argument, that
// will fit all, so just test if the number of argument is equal or
// more than the parameters we have.
final int minimumLength = c.length - 2;
chooser = (arguments, coerce) -> {
if (arguments.length > minimumLength) return doCall;
return null;
};
} else {
// general case for single method
chooser = (arguments, coerce) -> {
if (doCall.isValidMethod(arguments)) {
return doCall;
}
return null;
};
}
}
}
}
} else if (closureMethods.size() == 2) {
MetaMethod m0 = null, m1 = null;
for (int i = 0; i != closureMethods.size(); ++i) {
MetaMethod m = (MetaMethod) closureMethods.get(i);
CachedClass[] c = m.getParameterTypes();
if (c.length == 0) {
m0 = m;
} else {
if (c.length == 1 && c[0].getTheClass() == Object.class) {
m1 = m;
}
}
}
if (m0 != null && m1 != null) {
// standard closure (2 methods because "it" is with default null)
chooser = new StandardClosureChooser(m0, m1);
}
}
if (chooser == null) {
// standard chooser for cases if it is not a single method and if it is
// not the standard closure.
chooser = new NormalMethodChooser(theClass, closureMethods);
}
}
private MetaClass lookupObjectMetaClass(final Object object) {
if (object instanceof GroovyObject) {
GroovyObject go = (GroovyObject) object;
return go.getMetaClass();
}
Class ownerClass = object.getClass();
if (ownerClass == Class.class) {
ownerClass = (Class) object;
return registry.getMetaClass(ownerClass);
}
MetaClass metaClass = InvokerHelper.getMetaClass(object);
return metaClass;
}
@Override
public List getMethods() {
List answer = CLOSURE_METACLASS.getMetaMethods();
answer.addAll(closureMethods.toList());
return answer;
}
@Override
public List getMetaMethods() {
return CLOSURE_METACLASS.getMetaMethods();
}
@Override
public List getProperties() {
return CLOSURE_METACLASS.getProperties();
}
@Override
public MetaMethod pickMethod(final String methodName, final Class[] argumentTypes) {
if (CLOSURE_CALL_METHOD.equals(methodName) || CLOSURE_DO_CALL_METHOD.equals(methodName)) {
return pickClosureMethod(argumentTypes != null ? argumentTypes : MetaClassHelper.EMPTY_CLASS_ARRAY);
}
return CLOSURE_METACLASS.getMetaMethod(methodName, argumentTypes);
}
public MetaMethod retrieveStaticMethod(final String methodName, final Class[] arguments) {
return null;
}
@Override
protected boolean isInitialized() {
return initialized;
}
@Override
protected void setInitialized(final boolean initialized) {
this.initialized = initialized;
}
@Override
public MetaMethod getStaticMetaMethod(final String name, final Object[] args) {
return CLOSURE_METACLASS.getStaticMetaMethod(name, args);
}
public MetaMethod getStaticMetaMethod(final String name, final Class[] argTypes) {
return CLOSURE_METACLASS.getStaticMetaMethod(name, argTypes);
}
@Override
public Object getProperty(final Class sender, final Object object, final String name, final boolean useSuper, final boolean fromInsideClass) {
if (object instanceof Class) {
return getStaticMetaClass().getProperty(sender, object, name, useSuper, fromInsideClass);
} else {
return CLOSURE_METACLASS.getProperty(sender, object, name, useSuper, fromInsideClass);
}
}
@Override
public Object getAttribute(final Class sender, final Object object, final String attribute, final boolean useSuper, final boolean fromInsideClass) {
if (object instanceof Class) {
return getStaticMetaClass().getAttribute(sender, object, attribute, useSuper);
} else {
if (!attributeInitDone) initAttributes();
CachedField mfp = attributes.get(attribute);
if (mfp == null) {
return CLOSURE_METACLASS.getAttribute(sender, object, attribute, useSuper);
} else {
return mfp.getProperty(object);
}
}
}
@Override
public void setAttribute(final Class sender, final Object object, final String attribute, final Object newValue, final boolean useSuper, final boolean fromInsideClass) {
if (object instanceof Class) {
getStaticMetaClass().setAttribute(sender, object, attribute, newValue, useSuper, fromInsideClass);
} else {
if (!attributeInitDone) initAttributes();
CachedField mfp = attributes.get(attribute);
if (mfp == null) {
CLOSURE_METACLASS.setAttribute(sender, object, attribute, newValue, useSuper, fromInsideClass);
} else {
mfp.setProperty(object, newValue);
}
}
}
@Override
public Object invokeStaticMethod(final Object object, final String methodName, final Object[] arguments) {
return getStaticMetaClass().invokeMethod(Class.class, object, methodName, arguments, false, false);
}
@Override
public void setProperty(final Class sender, final Object object, final String name, final Object newValue, final boolean useSuper, final boolean fromInsideClass) {
if (object instanceof Class) {
getStaticMetaClass().setProperty(sender, object, name, newValue, useSuper, fromInsideClass);
} else {
CLOSURE_METACLASS.setProperty(sender, object, name, newValue, useSuper, fromInsideClass);
}
}
public MetaMethod getMethodWithoutCaching(final int index, final Class sender, final String methodName, final Class[] arguments, final boolean isCallToSuper) {
throw new UnsupportedOperationException();
}
@Override
public void setProperties(final Object bean, final Map map) {
throw new UnsupportedOperationException();
}
@Override
public void addMetaBeanProperty(final MetaBeanProperty mp) {
throw new UnsupportedOperationException();
}
@Override
public void addMetaMethod(final MetaMethod method) {
throw new UnsupportedOperationException();
}
@Override
public void addNewInstanceMethod(final Method method) {
throw new UnsupportedOperationException();
}
@Override
public void addNewStaticMethod(final Method method) {
throw new UnsupportedOperationException();
}
@Override
public Constructor retrieveConstructor(final Class[] arguments) {
throw new UnsupportedOperationException();
}
@Override
public CallSite createPojoCallSite(final CallSite site, final Object receiver, final Object[] args) {
throw new UnsupportedOperationException();
}
@Override
public CallSite createPogoCallSite(final CallSite site, final Object[] args) {
return new PogoMetaClassSite(site, this);
}
@Override
public CallSite createPogoCallCurrentSite(final CallSite site, final Class sender, final Object[] args) {
return new PogoMetaClassSite(site, this);
}
@Override
public List respondsTo(final Object obj, final String name, final Object[] argTypes) {
loadMetaInfo();
return super.respondsTo(obj, name, argTypes);
}
@Override
public List respondsTo(final Object obj, final String name) {
loadMetaInfo();
return super.respondsTo(obj, name);
}
private synchronized void loadMetaInfo() {
if (metaMethodIndex.isEmpty()) {
reinitialize();
}
}
@Override
protected void applyPropertyDescriptors(final PropertyDescriptor[] propertyDescriptors) {
// do nothing
}
}