/* | |
* Copyright 2003-2007 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.runtime.metaclass; | |
import groovy.lang.*; | |
import org.codehaus.groovy.reflection.CachedMethod; | |
import org.codehaus.groovy.reflection.ReflectionCache; | |
import org.codehaus.groovy.reflection.CachedClass; | |
import org.codehaus.groovy.runtime.InvokerHelper; | |
import org.codehaus.groovy.runtime.MetaClassHelper; | |
import org.codehaus.groovy.runtime.Reflector; | |
import org.codehaus.groovy.runtime.wrappers.Wrapper; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Method; | |
import java.security.AccessController; | |
import java.security.PrivilegedAction; | |
import java.util.*; | |
import java.util.logging.Level; | |
/** | |
* 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 isntances of this class will share that MetaClass. The Class MetaClass | |
* is initialized lazy, because most operations do not need this MetaClass. | |
* <p/> | |
* The Closure and Class MetaClasses are not replaceable. | |
* <p/> | |
* This MetaClass is for internal usage only! | |
* | |
* @author Jochen Theodorou | |
* @since 1.1 | |
*/ | |
public final class ClosureMetaClass extends MetaClassImpl { | |
private boolean initialized; | |
private Reflector reflector; | |
private final List closureMethods = new ArrayList(3); | |
private Map attributes = new HashMap(); | |
private MethodChooser chooser; | |
private volatile boolean attributeInitDone = false; | |
private static final MetaClassImpl CLOSURE_METACLASS; | |
private static MetaClassImpl classMetaClass; | |
private static final Object[] EMPTY_ARGUMENTS = {}; | |
private static final String CLOSURE_CALL_METHOD = "call"; | |
private static final String CLOSURE_DO_CALL_METHOD = "doCall"; | |
private static final String CLOSURE_CURRY_METHOD = "curry"; | |
static { | |
CLOSURE_METACLASS = new MetaClassImpl(Closure.class); | |
CLOSURE_METACLASS.initialize(); | |
} | |
private static synchronized MetaClass getStaticMetaClass() { | |
if (classMetaClass == null) { | |
classMetaClass = new MetaClassImpl(Class.class); | |
classMetaClass.initialize(); | |
} | |
return classMetaClass; | |
} | |
private static interface MethodChooser { | |
Object chooseMethod(Class[] arguments, boolean coerce); | |
} | |
private static class StandardClosureChooser implements MethodChooser { | |
private final MetaMethod doCall0; | |
private final MetaMethod doCall1; | |
StandardClosureChooser(MetaMethod m0, MetaMethod m1) { | |
doCall0 = m0; | |
doCall1 = m1; | |
} | |
public Object chooseMethod(Class[] arguments, boolean coerce) { | |
if (arguments.length == 0) return doCall0; | |
if (arguments.length == 1) return doCall1; | |
return null; | |
} | |
} | |
private static class NormalMethodChooser implements MethodChooser { | |
private final List methods; | |
final Class theClass; | |
NormalMethodChooser(Class theClass, List methods) { | |
this.theClass = theClass; | |
this.methods = methods; | |
} | |
public Object chooseMethod(Class[] arguments, 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(); | |
for (Iterator iter = methods.iterator(); iter.hasNext();) { | |
Object method = iter.next(); | |
// making this false helps find matches | |
if (MetaClassHelper.isValidMethod(method, arguments, coerce)) { | |
matchingMethods.add(method); | |
} | |
} | |
if (matchingMethods.isEmpty()) { | |
return null; | |
} else if (matchingMethods.size() == 1) { | |
return matchingMethods.get(0); | |
} | |
return chooseMostSpecificParams(CLOSURE_DO_CALL_METHOD, matchingMethods, arguments); | |
} | |
} | |
private Object chooseMostSpecificParams(String name, List matchingMethods, Class[] arguments) { | |
long matchesDistance = -1; | |
LinkedList matches = new LinkedList(); | |
for (Iterator iter = matchingMethods.iterator(); iter.hasNext();) { | |
Object method = iter.next(); | |
Class[] paramTypes = MetaClassHelper.getParameterTypes(method).getNativeParameterTypes(); | |
if (!MetaClassHelper.parametersAreCompatible(arguments, paramTypes)) continue; | |
long dist = MetaClassHelper.calculateParameterDistance(arguments, paramTypes); | |
if (dist == 0) return method; | |
if (matches.isEmpty()) { | |
matches.add(method); | |
matchesDistance = dist; | |
} else if (dist < matchesDistance) { | |
matchesDistance = dist; | |
matches.clear(); | |
matches.add(method); | |
} else if (dist == matchesDistance) { | |
matches.add(method); | |
} | |
} | |
if (matches.size() == 1) { | |
return matches.getFirst(); | |
} | |
if (matches.isEmpty()) { | |
return null; | |
} | |
//more than one matching method found --> ambigous! | |
String msg = "Ambiguous method overloading for method "; | |
msg += theClass.getName() + "#" + name; | |
msg += ".\nCannot resolve which method to invoke for "; | |
msg += InvokerHelper.toString(arguments); | |
msg += " due to overlapping prototypes between:"; | |
for (Iterator iter = matches.iterator(); iter.hasNext();) { | |
CachedClass[] types = MetaClassHelper.getParameterTypes(iter.next()).getParameterTypes(); | |
msg += "\n\t" + InvokerHelper.toString(types); | |
} | |
throw new GroovyRuntimeException(msg); | |
} | |
} | |
public ClosureMetaClass(MetaClassRegistry registry, Class theClass) { | |
super(registry, theClass); | |
} | |
public MetaProperty getMetaProperty(String name) { | |
return CLOSURE_METACLASS.getMetaProperty(name); | |
} | |
private void unwrap(Object[] arguments) { | |
for (int i = 0; i != arguments.length; i++) { | |
if (arguments[i] instanceof Wrapper) { | |
arguments[i] = ((Wrapper) arguments[i]).unwrap(); | |
} | |
} | |
} | |
private MetaMethod pickClosureMethod(Class[] argClasses) { | |
Object answer = chooser.chooseMethod(argClasses, false); | |
return (MetaMethod) answer; | |
} | |
private MetaMethod getDelegateMethod(Closure closure, Object delegate, String methodName, Class[] argClasses) { | |
if (delegate == closure || delegate == null) return null; | |
MetaClass delegateMetaClass = lookupObjectMetaClass(delegate); | |
return delegateMetaClass.pickMethod(methodName, argClasses); | |
} | |
public Object invokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) { | |
checkInitalised(); | |
if (object == null) { | |
throw new NullPointerException("Cannot invoke method: " + methodName + " on null object"); | |
} | |
if (LOG.isLoggable(Level.FINER)) { | |
MetaClassHelper.logMethodCall(object, methodName, originalArguments); | |
} | |
final Object[] arguments = originalArguments == null ? EMPTY_ARGUMENTS : originalArguments; | |
final Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); | |
unwrap(arguments); | |
MetaMethod method; | |
final Closure closure = (Closure) object; | |
if (CLOSURE_DO_CALL_METHOD.equals(methodName) || CLOSURE_CALL_METHOD.equals(methodName)) { | |
method = pickClosureMethod(argClasses); | |
if (method != null) return MetaClassHelper.doMethodInvoke(object, method, arguments); | |
} else if (CLOSURE_CURRY_METHOD.equals(methodName)) { | |
return closure.curry(arguments); | |
} else { | |
method = CLOSURE_METACLASS.pickMethod(methodName, argClasses); | |
} | |
MissingMethodException last = null; | |
Object callObject = object; | |
if (method == null) { | |
final Object owner = closure.getOwner(); | |
final Object delegate = closure.getDelegate(); | |
final int resolveStrategy = closure.getResolveStrategy(); | |
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) { | |
method = getDelegateMethod(closure, owner, methodName, argClasses); | |
callObject = owner; | |
} | |
if (method == null) { | |
invokeOnDelegate = delegate != closure && (delegate instanceof GroovyObject); | |
invokeOnOwner = owner != closure && (owner instanceof GroovyObject); | |
ownerFirst = false; | |
} | |
break; | |
default: // owner first | |
method = getDelegateMethod(closure, owner, methodName, argClasses); | |
callObject = owner; | |
if (method == null) { | |
method = getDelegateMethod(closure, delegate, methodName, argClasses); | |
callObject = delegate; | |
} | |
if (method == null) { | |
invokeOnDelegate = delegate != closure && (delegate instanceof GroovyObject); | |
invokeOnOwner = owner != closure && (owner instanceof GroovyObject); | |
} | |
} | |
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) { | |
return MetaClassHelper.doMethodInvoke(callObject, method, arguments); | |
} else { | |
// if no method was found, try to find a closure defined as a field of the class and run it | |
Object value = null; | |
try { | |
value = this.getProperty(object, methodName); | |
} 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); | |
} | |
} | |
if (last != null) throw last; | |
throw new MissingMethodException(methodName, theClass, arguments, false); | |
} | |
private Object invokeOnDelegationObjects( | |
boolean invoke1, Object o1, | |
boolean invoke2, Object o2, | |
String methodName, Object[] args) { | |
MissingMethodException first = null; | |
if (invoke1) { | |
GroovyObject go = (GroovyObject) o1; | |
try { | |
return go.invokeMethod(methodName, args); | |
} catch (MissingMethodException mme) { | |
first = mme; | |
} | |
} | |
if (invoke2) { | |
GroovyObject go = (GroovyObject) o2; | |
try { | |
return go.invokeMethod(methodName, args); | |
} catch (MissingMethodException mme) { | |
if (first == null) first = mme; | |
} | |
} | |
throw first; | |
} | |
private synchronized void initAttributes() { | |
if (!attributes.isEmpty()) return; | |
attributes.put("!", null); // just a dummy for later | |
Field[] fieldArray = (Field[]) AccessController.doPrivileged(new PrivilegedAction() { | |
public Object run() { | |
return theClass.getDeclaredFields(); | |
} | |
}); | |
for (int i = 0; i < fieldArray.length; i++) { | |
MetaFieldProperty mfp = MetaFieldProperty.create(fieldArray[i]); | |
attributes.put(fieldArray[i].getName(), mfp); | |
} | |
attributeInitDone = !attributes.isEmpty(); | |
} | |
public synchronized void initialize() { | |
if (!isInitialized()) { | |
CachedMethod[] methodArray = ReflectionCache.getDeclaredMethodsCached(theClass); | |
for (int i = 0; i < methodArray.length; i++) { | |
final CachedMethod cachedMethod = methodArray[i]; | |
Method reflectionMethod = cachedMethod.cachedMethod; | |
if (!reflectionMethod.getName().equals(CLOSURE_DO_CALL_METHOD)) continue; | |
MetaMethod method = createMetaMethod(cachedMethod); | |
closureMethods.add(method); | |
} | |
assignMethodChooser(); | |
initialized = true; | |
} | |
if (reflector == null) { | |
generateReflector(); | |
} | |
} | |
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 = new MethodChooser() { | |
public Object chooseMethod(Class[] arguments, boolean coerce) { | |
if (arguments.length == 0) return doCall; | |
return null; | |
} | |
}; | |
} else { | |
if (length == 1 && c[0].cachedClass == Object.class) { | |
// Object fits all, so simple dispatch rule here | |
chooser = new MethodChooser() { | |
public Object chooseMethod(Class[] arguments, boolean 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].cachedClass != Object.class) { | |
allObject = false; | |
break; | |
} | |
} | |
if (allObject && c[c.length - 1].cachedClass == Object.class) { | |
// all arguments are object, so test only if argument number is correct | |
chooser = new MethodChooser() { | |
public Object chooseMethod(Class[] arguments, boolean coerce) { | |
if (arguments.length == c.length) return doCall; | |
return null; | |
} | |
}; | |
} else { | |
if (allObject && c[c.length - 1].cachedClass == Object[].class) { | |
// all arguments are Object but last, which is a vargs argument, that | |
// will fit all, so jsut test if the number of argument is equal or | |
// more than the parameters we have. | |
final int minimumLength = c.length - 2; | |
chooser = new MethodChooser() { | |
public Object chooseMethod(Class[] arguments, boolean coerce) { | |
if (arguments.length > minimumLength) return doCall; | |
return null; | |
} | |
}; | |
} else { | |
// general case for single method | |
chooser = new MethodChooser() { | |
public Object chooseMethod(Class[] arguments, boolean coerce) { | |
if (MetaClassHelper.isValidMethod(doCall, arguments, coerce)) { | |
return doCall; | |
} | |
return null; | |
} | |
}; | |
} | |
} | |
} | |
} | |
} else if (closureMethods.size() == 2) { | |
MetaMethod m0 = null, m1 = null; | |
for (Iterator iterator = closureMethods.iterator(); iterator.hasNext();) { | |
MetaMethod m = (MetaMethod) iterator.next(); | |
CachedClass[] c = m.getParameterTypes(); | |
if (c.length == 0) { | |
m0 = m; | |
} else { | |
if (c.length == 1 && c[0].cachedClass == 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 MetaMethod createMetaMethod(final CachedMethod method) { | |
if (((MetaClassRegistryImpl) registry).useAccessible()) { | |
AccessController.doPrivileged(new PrivilegedAction() { | |
public Object run() { | |
method.cachedMethod.setAccessible(true); | |
return null; | |
} | |
}); | |
} | |
if (GroovySystem.isUseReflection()) return new ReflectionMetaMethod(method); | |
return new MetaMethod(method.cachedMethod, method.getParameterTypes()); | |
} | |
private void generateReflector() { | |
reflector = ((MetaClassRegistryImpl) registry).loadReflector(theClass, closureMethods); | |
if (reflector == null) { | |
throw new RuntimeException("Should have a reflector for " + theClass.getName()); | |
} | |
// lets set the reflector on all the methods | |
for (Iterator iter = closureMethods.iterator(); iter.hasNext();) { | |
MetaMethod metaMethod = (MetaMethod) iter.next(); | |
metaMethod.setReflector(reflector); | |
} | |
} | |
private MetaClass lookupObjectMetaClass(Object object) { | |
if (object instanceof GroovyObject) { | |
GroovyObject go = (GroovyObject) object; | |
return go.getMetaClass(); | |
} | |
Class ownerClass = object.getClass(); | |
if (ownerClass == Class.class) ownerClass = (Class) object; | |
MetaClass metaClass = registry.getMetaClass(ownerClass); | |
return metaClass; | |
} | |
public List getMethods() { | |
List answer = CLOSURE_METACLASS.getMetaMethods(); | |
answer.addAll(closureMethods); | |
return answer; | |
} | |
public List getMetaMethods() { | |
return CLOSURE_METACLASS.getMetaMethods(); | |
} | |
public List getProperties() { | |
return CLOSURE_METACLASS.getProperties(); | |
} | |
public MetaMethod pickMethod(String name, Class[] argTypes) { | |
if (argTypes == null) argTypes = new Class[0]; | |
if (name.equals(CLOSURE_CALL_METHOD) || name.equals(CLOSURE_DO_CALL_METHOD)) { | |
return pickClosureMethod(argTypes); | |
} | |
return CLOSURE_METACLASS.getMetaMethod(name, argTypes); | |
} | |
public MetaMethod retrieveStaticMethod(String methodName, Class[] arguments) { | |
return null; | |
} | |
protected boolean isInitialized() { | |
return initialized; | |
} | |
public MetaMethod getStaticMetaMethod(String name, Object[] args) { | |
return CLOSURE_METACLASS.getStaticMetaMethod(name, args); | |
} | |
public MetaMethod getStaticMetaMethod(String name, Class[] argTypes) { | |
return CLOSURE_METACLASS.getStaticMetaMethod(name, argTypes); | |
} | |
public Object getProperty(Class sender, Object object, String name, boolean useSuper, boolean fromInsideClass) { | |
if (object instanceof Class) { | |
return getStaticMetaClass().getProperty(sender, object, name, useSuper, fromInsideClass); | |
} else { | |
return CLOSURE_METACLASS.getProperty(sender, object, name, useSuper, fromInsideClass); | |
} | |
} | |
public Object getAttribute(Class sender, Object object, String attribute, boolean useSuper, boolean fromInsideClass) { | |
if (object instanceof Class) { | |
return getStaticMetaClass().getAttribute(sender, object, attribute, useSuper); | |
} else { | |
if (!attributeInitDone) initAttributes(); | |
MetaFieldProperty mfp = (MetaFieldProperty) attributes.get(attribute); | |
if (mfp == null) { | |
return CLOSURE_METACLASS.getAttribute(sender, object, attribute, useSuper); | |
} else { | |
return mfp.getProperty(object); | |
} | |
} | |
} | |
public void setAttribute(Class sender, Object object, String attribute, | |
Object newValue, boolean useSuper, boolean fromInsideClass) { | |
if (object instanceof Class) { | |
getStaticMetaClass().setAttribute(sender, object, attribute, newValue, useSuper, fromInsideClass); | |
} else { | |
if (!attributeInitDone) initAttributes(); | |
MetaFieldProperty mfp = (MetaFieldProperty) attributes.get(attribute); | |
if (mfp == null) { | |
CLOSURE_METACLASS.setAttribute(sender, object, attribute, newValue, useSuper, fromInsideClass); | |
} else { | |
mfp.setProperty(object, newValue); | |
} | |
} | |
} | |
public Object invokeStaticMethod(Object object, String methodName, Object[] arguments) { | |
return getStaticMetaClass().invokeMethod(Class.class, object, methodName, arguments, false, false); | |
} | |
public void setProperty(Class sender, Object object, String name, Object newValue, boolean useSuper, 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(Class sender, String methodName, Class[] arguments, boolean isCallToSuper) { | |
throw new UnsupportedOperationException(); | |
} | |
public void setProperties(Object bean, Map map) { | |
throw new UnsupportedOperationException(); | |
} | |
private Object invokeConstructor(Class at, Object[] arguments) { | |
throw new UnsupportedOperationException(); | |
} | |
public void addMetaBeanProperty(MetaBeanProperty mp) { | |
throw new UnsupportedOperationException(); | |
} | |
public void addMetaMethod(MetaMethod method) { | |
throw new UnsupportedOperationException(); | |
} | |
public void addNewInstanceMethod(Method method) { | |
throw new UnsupportedOperationException(); | |
} | |
public void addNewStaticMethod(Method method) { | |
throw new UnsupportedOperationException(); | |
} | |
public Constructor retrieveConstructor(Class[] arguments) { | |
throw new UnsupportedOperationException(); | |
} | |
} |