blob: 3282cf4720f02dfb8b198a4bf43d2ddcce669fd2 [file] [log] [blame]
/*
* 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 groovy.lang;
import org.codehaus.groovy.runtime.*;
import org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod;
import org.codehaus.groovy.runtime.metaclass.ThreadManagedMetaBeanProperty;
import org.codehaus.groovy.runtime.metaclass.ClosureStaticMetaMethod;
import org.codehaus.groovy.runtime.metaclass.ConcurrentReaderHashMap;
import org.codehaus.groovy.reflection.CachedClass;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* A MetaClass that implements GroovyObject and behaves like an Expando, allowing the addition of new methods on the fly
*
* <code><pre>
* // defines or replaces instance method:
* metaClass.myMethod = { args -> }
*
* // defines a new instance method
* metaClass.myMethod << { args -> }
*
* // creates multiple overloaded methods of the same name
* metaClass.myMethod << { String s -> } << { Integer i -> }
*
* // defines or replaces a static method with the 'static' qualifier
* metaClass.'static'.myMethod = { args -> }
*
* // defines a new static method with the 'static' qualifier
* metaClass.'static'.myMethod << { args -> }
*
* // defines a new contructor
* metaClass.constructor << { String arg -> }
*
* // defines or replaces a constructor
* metaClass.constructor = { String arg -> }
*
* // defines a new property with an initial value of "blah"
* metaClass.myProperty = "blah"
*
* </code></pre>
*
* By default methods are only allowed to be added before initialize() is called. In other words you create a new
* ExpandoMetaClass, add some methods and then call initialize(). If you attempt to add new methods after initialize()
* has been called an error will be thrown.
*
* This is to ensure that the MetaClass can operate appropriately in multi threaded environments as it forces you
* to do all method additions at the beginning, before using the MetaClass.
*
* If you need more fine grained control of how a method is matched you can use DynamicMethodsMetaClass
*
* WARNING: This MetaClass uses a thread-bound ThreadLocal instance to store and retrieve properties.
* In addition properties stored use soft references so they are both bound by the life of the Thread and by the soft
* references. The implication here is you should NEVER use dynamic properties if you want their values to stick around
* for long periods because as soon as the JVM is running low on memory or the thread dies they will be garbage collected.
*
* @author Graeme Rocher
* @since 1.1
*/
public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject {
private static final String META_CLASS = "metaClass";
private static final String CLASS = "class";
private static final String META_METHODS = "metaMethods";
private static final String METHODS = "methods";
private static final String PROPERTIES = "properties";
public static final String STATIC_QUALIFIER = "static";
private static final Class[] ZERO_ARGUMENTS = new Class[0];
private static final String CONSTRUCTOR = "constructor";
private static final String GET_PROPERTY_METHOD = "getProperty";
private static final String SET_PROPERTY_METHOD = "setProperty";
private static final String INVOKE_METHOD_METHOD = "invokeMethod";
private static final String CLASS_PROPERTY = "class";
private static final String META_CLASS_PROPERTY = "metaClass";
private static final String GROOVY_CONSTRUCTOR = "<init>";
// These two properties are used when no ExpandoMetaClassCreationHandle is present
private boolean hasCreationHandle;
private MetaClass myMetaClass;
private boolean allowChangesAfterInit;
private boolean initialized;
private boolean initCalled;
private boolean modified;
private boolean inRegistry;
private final Set inheritedMetaMethods = new HashSet();
private final Map beanPropertyCache = new ConcurrentReaderHashMap();
private final Map staticBeanPropertyCache = new ConcurrentReaderHashMap();
private final Map expandoMethods = new ConcurrentReaderHashMap();
private final Map expandoProperties = new ConcurrentReaderHashMap();
private ClosureMetaMethod getPropertyMethod;
private ClosureMetaMethod invokeMethodMethod;
private ClosureMetaMethod setPropertyMethod;
private ClosureStaticMetaMethod invokeStaticMethodMethod;
/**
* Constructs a new ExpandoMetaClass instance for the given class
*
* @param theClass The class that the MetaClass applies to
*/
public ExpandoMetaClass(Class theClass) {
super(GroovySystem.getMetaClassRegistry(), theClass);
this.myMetaClass = InvokerHelper.getMetaClass(this);
}
/**
* Constructs a new ExpandoMetaClass instance for the given class optionally placing the MetaClass
* in the MetaClassRegistry automatically
*
* @param theClass The class that the MetaClass applies to
* @param register True if the MetaClass should be registered inside the MetaClassRegistry. This defaults to true and ExpandoMetaClass will effect all instances if changed
*/
public ExpandoMetaClass(Class theClass, boolean register) {
this(theClass);
this.inRegistry = register;
}
/**
* Overrides the default missing method behaviour and adds the capability to look up a method from super class
*
* @see MetaClassImpl#invokeMissingMethod(Object, String, Object[])
*/
public Object invokeMissingMethod(Object instance, String methodName, Object[] arguments) {
Class superClass = instance instanceof Class ? (Class)instance : instance.getClass();
while(superClass != Object.class) {
final MetaMethod method = findMethodInClassHeirarchy(methodName, arguments, superClass);
if(method != null) {
addSuperMethodIfNotOverriden(method);
return method.invoke(instance, arguments);
}
superClass = superClass.getSuperclass();
}
// still not method here, so see if there is an invokeMethod method up the heirarchy
final Object[] invokeMethodArgs = {methodName, arguments};
final MetaMethod method = findMethodInClassHeirarchy(INVOKE_METHOD_METHOD, invokeMethodArgs, theClass );
if(method!=null && method instanceof ClosureMetaMethod) {
this.invokeMethodMethod = (ClosureMetaMethod)method;
return method.invoke(instance, invokeMethodArgs);
}
return super.invokeMissingMethod(instance, methodName, arguments);
}
/**
* Overrides the default missing method behaviour and adds the capability to look up a method from the super class in the case
* where it has been overriden
*
* @param instance The instance of the object
* @param propertyName The property name
* @param optionalValue The property value in the case of a setter
* @param isGetter True if it is a getter
* @return The return value if of a getProperty call or a MissingPropertyException is thrown
*/
public Object invokeMissingProperty(Object instance, String propertyName, Object optionalValue, boolean isGetter) {
Class theClass = instance instanceof Class ? (Class)instance : instance.getClass();
Class superClass = theClass;
while(superClass != Object.class) {
final MetaBeanProperty property = findPropertyInClassHierarchy(propertyName, superClass);
if(property != null) {
addMetaBeanProperty(property);
if(!isGetter) {
property.setProperty(instance, optionalValue);
return null;
}
else {
return property.getProperty(instance);
}
}
superClass = superClass.getSuperclass();
}
// got here to property not found, look for getProperty or setProperty overrides
if(isGetter) {
final Object[] getPropertyArgs = {propertyName};
final MetaMethod method = findMethodInClassHeirarchy(GET_PROPERTY_METHOD, getPropertyArgs, theClass);
if(method != null && method instanceof ClosureMetaMethod) {
this.getPropertyMethod = (ClosureMetaMethod)method;
return method.invoke(instance,getPropertyArgs);
}
}
else {
final Object[] setPropertyArgs = {propertyName, optionalValue};
final MetaMethod method = findMethodInClassHeirarchy(SET_PROPERTY_METHOD, setPropertyArgs, theClass);
if(method != null && method instanceof ClosureMetaMethod) {
this.setPropertyMethod = (ClosureMetaMethod)method;
return method.invoke(instance, setPropertyArgs);
}
}
return super.invokeMissingProperty(instance, propertyName, optionalValue, isGetter);
}
private MetaBeanProperty findPropertyInClassHierarchy(String propertyName, Class theClass) {
MetaBeanProperty property= null;
final Class superClass = theClass.getSuperclass();
MetaClass metaClass = this.registry.getMetaClass(superClass);
if(metaClass instanceof MutableMetaClass) {
property = getMetaPropertyFromMutableMetaClass(propertyName,metaClass);
if(property == null) {
if(superClass != Object.class) {
property = findPropertyInClassHierarchy(propertyName, superClass);
}
if(property == null) {
final Class[] interfaces = theClass.getInterfaces();
property = searchInterfacesForMetaProperty(propertyName, interfaces);
}
}
}
return property;
}
private MetaBeanProperty searchInterfacesForMetaProperty(String propertyName, Class[] interfaces) {
MetaBeanProperty property = null;
for (int i = 0; i < interfaces.length; i++) {
Class anInterface = interfaces[i];
MetaClass metaClass = this.registry.getMetaClass(anInterface);
if(metaClass instanceof MutableMetaClass) {
property = getMetaPropertyFromMutableMetaClass(propertyName,metaClass);
if(property != null) break;
}
Class[] superInterfaces = anInterface.getInterfaces();
if(superInterfaces.length > 0) {
property = searchInterfacesForMetaProperty(propertyName, superInterfaces);
if(property!=null) break;
}
}
return property;
}
private MetaBeanProperty getMetaPropertyFromMutableMetaClass(String propertyName, MetaClass metaClass) {
final boolean isModified = ((MutableMetaClass) metaClass).isModified();
final MetaProperty metaProperty = metaClass.getMetaProperty(propertyName);
if(metaProperty instanceof MetaBeanProperty)
return isModified ? (MetaBeanProperty)metaProperty : null;
else
return null;
}
private MetaMethod findMethodInClassHeirarchy(String methodName, Object[] arguments, Class theClass) {
MetaMethod method = null;
final Class superClass = theClass.getSuperclass();
MetaClass metaClass = this.registry.getMetaClass(superClass);
if(metaClass instanceof MutableMetaClass) {
method = getMetaMethodFromMutableMetaClass(methodName, arguments, metaClass);
if(method == null) {
if(superClass != Object.class) {
method = findMethodInClassHeirarchy(methodName, arguments, superClass);
}
if(method == null) {
final Class[] interfaces = theClass.getInterfaces();
method = searchInterfacesForMetaMethod(methodName, arguments, interfaces);
}
}
}
return method;
}
private MetaMethod searchInterfacesForMetaMethod(String methodName, Object[] arguments, Class[] interfaces) {
MetaMethod method = null;
for (int i = 0; i < interfaces.length; i++) {
Class anInterface = interfaces[i];
MetaClass metaClass = this.registry.getMetaClass(anInterface);
if(metaClass instanceof MutableMetaClass) {
method = getMetaMethodFromMutableMetaClass(methodName, arguments, metaClass);
if(method != null) break;
}
Class[] superInterfaces = anInterface.getInterfaces();
if(superInterfaces.length > 0) {
method = searchInterfacesForMetaMethod(methodName,arguments, superInterfaces);
if(method!=null) break;
}
}
return method;
}
private MetaMethod getMetaMethodFromMutableMetaClass(String methodName, Object[] arguments, MetaClass metaClass) {
final boolean isModified = ((MutableMetaClass) metaClass).isModified();
return isModified ? metaClass.getMetaMethod(methodName, arguments) : null;
}
public boolean isModified() {
return this.modified;
}
/**
* For simulating closures in Java
*/
private interface Callable {
void call();
}
/**
* Call to enable global use of global use of ExpandoMetaClass within the registry. This has the advantage that
* inheritance will function correctly, but has a higher memory usage on the JVM than normal Groovy
*/
public static void enableGlobally() {
ExpandoMetaClassCreationHandle.enable();
}
/**
* Call to disable the global use of ExpandoMetaClass
*/
public static void disableGlobally() {
GroovySystem.getMetaClassRegistry().setMetaClassCreationHandle( new MetaClassRegistry.MetaClassCreationHandle() );
}
/**
* @param allowChangesAfterInit the allowChangesAfterInit to set
*/
public void setAllowChangesAfterInit(boolean allowChangesAfterInit) {
this.allowChangesAfterInit = allowChangesAfterInit;
}
/* (non-Javadoc)
* @see groovy.lang.MetaClassImpl#initialize()
*/
public synchronized void initialize() {
if (!this.initialized) {
super.initialize();
this.initialized = true;
this.initCalled = true;
}
}
/* (non-Javadoc)
* @see groovy.lang.MetaClassImpl#isInitialized()
*/
protected boolean isInitialized() {
return this.initialized;
}
private void addSuperMethodIfNotOverriden(final MetaMethod metaMethodFromSuper) {
performOperationOnMetaClass(new Callable() {
public void call() {
MetaMethod existing = null;
try {
existing = pickMethod(metaMethodFromSuper.getName(), metaMethodFromSuper.getNativeParameterTypes());}
catch ( GroovyRuntimeException e) {
// ignore, this happens with overlapping method definitions
}
if(existing == null) {
addMethodWithKey(metaMethodFromSuper);
}
else {
boolean isGroovyMethod = getMetaMethods().contains(existing);
if(isGroovyMethod) {
addMethodWithKey(metaMethodFromSuper);
}
else if(inheritedMetaMethods.contains(existing)) {
inheritedMetaMethods.remove(existing);
addMethodWithKey(metaMethodFromSuper);
}
}
}
private void addMethodWithKey(final MetaMethod metaMethodFromSuper) {
inheritedMetaMethods.add(metaMethodFromSuper);
if(metaMethodFromSuper instanceof ClosureMetaMethod) {
ClosureMetaMethod closureMethod = (ClosureMetaMethod)metaMethodFromSuper;
Closure cloned = (Closure)closureMethod.getClosure().clone();
String name = metaMethodFromSuper.getName();
ClosureMetaMethod localMethod = new ClosureMetaMethod(name, getJavaClass(), cloned);
addMetaMethod(localMethod);
MethodKey key = new DefaultCachedMethodKey(getJavaClass(),name, localMethod.getParameterTypes(),false );
cacheInstanceMethod(key, localMethod);
checkIfGroovyObjectMethod(localMethod, name);
expandoMethods.put(key,localMethod);
}
}
});
}
/**
* Instances of this class are returned when using the << left shift operator.
*
* Example:
*
* metaClass.myMethod << { String args -> }
*
* This allows callbacks to the ExpandoMetaClass for registering appending methods
*
* @author Graeme Rocher
*
*/
protected class ExpandoMetaProperty extends GroovyObjectSupport {
protected String propertyName;
protected boolean isStatic;
protected ExpandoMetaProperty(String name) {
this(name, false);
}
protected ExpandoMetaProperty(String name, boolean isStatic) {
this.propertyName = name;
this.isStatic = isStatic;
}
public String getPropertyName() { return this.propertyName; }
public boolean isStatic() { return this.isStatic; }
public Object leftShift(Object arg) {
registerIfClosure(arg, false);
return this;
}
private void registerIfClosure(Object arg, boolean replace) {
if(arg instanceof Closure) {
Closure callable = (Closure)arg;
Class[] paramTypes = callable.getParameterTypes();
if(paramTypes == null)paramTypes = ZERO_ARGUMENTS;
if(!this.isStatic) {
Method foundMethod = checkIfMethodExists(theClass, propertyName, paramTypes, false);
if(foundMethod != null && !replace) throw new GroovyRuntimeException("Cannot add new method ["+propertyName+"] for arguments ["+DefaultGroovyMethods.inspect(paramTypes)+"]. It already exists!");
registerInstanceMethod(propertyName, callable);
}
else {
Method foundMethod = checkIfMethodExists(theClass, propertyName, paramTypes, true);
if(foundMethod != null && !replace) throw new GroovyRuntimeException("Cannot add new static method ["+propertyName+"] for arguments ["+DefaultGroovyMethods.inspect(paramTypes)+"]. It already exists!");
registerStaticMethod(propertyName, callable);
}
}
}
private Method checkIfMethodExists(Class methodClass, String methodName, Class[] paramTypes, boolean staticMethod) {
Method foundMethod = null;
Method[] methods = methodClass.getMethods();
for (int i = 0; i < methods.length; i++) {
if(methods[i].getName().equals(methodName) && Modifier.isStatic(methods[i].getModifiers()) == staticMethod) {
if(MetaClassHelper.parametersAreCompatible( paramTypes, methods[i].getParameterTypes() )) {
foundMethod = methods[i];
break;
}
}
}
return foundMethod;
}
/* (non-Javadoc)
* @see groovy.lang.GroovyObjectSupport#getProperty(java.lang.String)
*/
public Object getProperty(String property) {
this.propertyName = property;
return this;
}
/* (non-Javadoc)
* @see groovy.lang.GroovyObjectSupport#setProperty(java.lang.String, java.lang.Object)
*/
public void setProperty(String property, Object newValue) {
this.propertyName = property;
registerIfClosure(newValue, true);
}
}
/* (non-Javadoc)
* @see groovy.lang.MetaClassImpl#invokeConstructor(java.lang.Object[])
*/
public Object invokeConstructor(Object[] arguments) {
// TODO This is the only area where this MetaClass needs to do some interception because Groovy's current
// MetaClass uses hard coded references to the java.lang.reflect.Constructor class so you can't simply
// inject Constructor like you can do properties, methods and fields. When Groovy's MetaClassImpl is
// refactored we can fix this
Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments);
MetaMethod method = pickMethod(GROOVY_CONSTRUCTOR, argClasses);
if(method!=null && method.getParameterTypes().length == arguments.length) {
return method.invoke(theClass, arguments);
}
return super.invokeConstructor(arguments);
}
/**
* Retrieves a list of super classes. Taken from MetaClassImpl. Ideally this method should be protected
*
* @return A list of super classes
*/
protected LinkedList getSuperClasses() {
LinkedList superClasses = new LinkedList();
for (Class c = theClass; c!= null; c = c.getSuperclass()) {
superClasses.addFirst(c);
}
if (getJavaClass().isArray() && getJavaClass()!=Object[].class && !getJavaClass().getComponentType().isPrimitive()) {
superClasses.addFirst(Object[].class);
}
return superClasses;
}
/**
* Handles the ability to use the left shift operator to append new constructors
*
* @author Graeme Rocher
*
*/
protected class ExpandoMetaConstructor extends GroovyObjectSupport {
public Object leftShift(Closure c) {
if(c != null) {
Class[] paramTypes = c.getParameterTypes();
if(paramTypes == null)paramTypes = ZERO_ARGUMENTS;
Constructor ctor = retrieveConstructor(paramTypes);
if(ctor != null) throw new GroovyRuntimeException("Cannot add new constructor for arguments ["+DefaultGroovyMethods.inspect(paramTypes)+"]. It already exists!");
registerInstanceMethod(GROOVY_CONSTRUCTOR, c);
}
return this;
}
}
/* (non-Javadoc)
* @see groovy.lang.GroovyObject#getMetaClass()
*/
public MetaClass getMetaClass() {
return myMetaClass;
}
/* (non-Javadoc)
* @see groovy.lang.GroovyObject#getProperty(java.lang.String)
*/
public Object getProperty(String property) {
if(isValidExpandoProperty(property)) {
if(property.equals(STATIC_QUALIFIER)) {
return new ExpandoMetaProperty(property, true);
}
else if(property.equals(CONSTRUCTOR)) {
return new ExpandoMetaConstructor();
}
else {
return new ExpandoMetaProperty(property);
}
}
else {
return myMetaClass.getProperty(this, property);
}
}
private boolean isValidExpandoProperty(String property) {
return !property.equals(META_CLASS) &&
!property.equals(CLASS) &&
!property.equals(META_METHODS) &&
!property.equals(METHODS) &&
!property.equals(PROPERTIES);
}
/* (non-Javadoc)
* @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object)
*/
public Object invokeMethod(String name, Object args) {
return myMetaClass.invokeMethod(this, name, args);
}
/* (non-Javadoc)
* @see groovy.lang.GroovyObject#setMetaClass(groovy.lang.MetaClass)
*/
public void setMetaClass(MetaClass metaClass) {
this.myMetaClass = metaClass;
}
/* (non-Javadoc)
* @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object)
*/
public void setProperty(String property, Object newValue) {
if(newValue instanceof Closure) {
if(property.equals(CONSTRUCTOR)) {
property = GROOVY_CONSTRUCTOR;
}
Closure callable = (Closure)newValue;
// here we don't care if the method exists or not we assume the
// developer is responsible and wants to override methods where necessary
registerInstanceMethod(property, callable);
}
else if(property.equals("allowChangesAfterInit")) {
this.allowChangesAfterInit = ((Boolean)newValue).booleanValue();
}
else {
registerBeanProperty(property, newValue);
}
}
protected void performOperationOnMetaClass(Callable c) {
try {
if(allowChangesAfterInit) {
this.initialized = false;
}
c.call();
}
finally {
if(initCalled)this.initialized = true;
}
}
/**
* Registers a new bean property
*
* @param property The property name
* @param newValue The properties initial value
*/
protected void registerBeanProperty(final String property, final Object newValue) {
performOperationOnMetaClass(new Callable() {
public void call() {
Class type = newValue == null ? Object.class : newValue.getClass();
MetaBeanProperty mbp = newValue instanceof MetaBeanProperty ? (MetaBeanProperty)newValue : new ThreadManagedMetaBeanProperty(theClass,property,type,newValue);
final MetaMethod getter = mbp.getGetter();
final MethodKey getterKey = new DefaultCachedMethodKey(theClass,getter.getName(), new CachedClass[0],false );
final MetaMethod setter = mbp.getSetter();
final MethodKey setterKey = new DefaultCachedMethodKey(theClass,setter.getName(), setter.getParameterTypes(),false );
addMetaMethod(getter);
addMetaMethod(setter);
expandoMethods.put(setterKey,setter);
expandoMethods.put(getterKey,getter);
expandoProperties.put(mbp.getName(),mbp);
addMetaBeanProperty(mbp);
performRegistryCallbacks();
}
});
}
/**
* Registers a new instance method for the given method name and closure on this MetaClass
*
* @param methodName The method name
* @param callable The callable Closure
*/
protected void registerInstanceMethod(final String methodName, final Closure callable) {
final boolean inited = this.initCalled;
performOperationOnMetaClass(new Callable() {
public void call() {
ClosureMetaMethod metaMethod = new ClosureMetaMethod(methodName, theClass,callable);
checkIfGroovyObjectMethod(metaMethod, methodName);
MethodKey key = new DefaultCachedMethodKey(theClass,methodName, metaMethod.getParameterTypes(),false );
addMetaMethod(metaMethod);
expandoMethods.put(key,metaMethod);
if(inited && isGetter(methodName, metaMethod.getParameterTypes())) {
String propertyName = getPropertyForGetter(methodName);
registerBeanPropertyForMethod(metaMethod, propertyName, true, false);
}
else if(inited && isSetter(methodName, metaMethod.getParameterTypes())) {
String propertyName = getPropertyForSetter(methodName);
registerBeanPropertyForMethod(metaMethod, propertyName, false, false);
}
performRegistryCallbacks();
}
});
}
/**
* Overrides the behaviour of parent getMethods() method to make MetaClass aware of added Expando methods
*
* @see MetaObjectProtocol#getMethods()
*
* @return A list of MetaMethods
*/
public List getMethods() {
List methodList = new ArrayList();
methodList.addAll(this.expandoMethods.values());
methodList.addAll(super.getMethods());
return methodList;
}
public List getProperties() {
List propertyList = new ArrayList();
propertyList.addAll(expandoProperties.values());
propertyList.addAll(super.getProperties());
return propertyList;
}
/**
* Checks if the metaMethod is a method from the GroovyObject interface such as setProperty, getProperty and invokeMethod
*
* @param metaMethod The metaMethod instance
* @param methodName The method name
*
* @see groovy.lang.GroovyObject
*/
private void checkIfGroovyObjectMethod(ClosureMetaMethod metaMethod, String methodName) {
if(isGetPropertyMethod(methodName)) {
getPropertyMethod = metaMethod;
}
else if(isInvokeMethod(methodName, metaMethod)) {
invokeMethodMethod = metaMethod;
}
else if(isSetPropertyMethod(methodName, metaMethod)) {
setPropertyMethod = metaMethod;
}
}
private boolean isSetPropertyMethod(String methodName, ClosureMetaMethod metaMethod) {
return SET_PROPERTY_METHOD.equals(methodName) && metaMethod.getParameterTypes().length == 2;
}
private boolean isGetPropertyMethod(String methodName) {
return GET_PROPERTY_METHOD.equals(methodName);
}
private boolean isInvokeMethod(String methodName, ClosureMetaMethod metaMethod) {
return INVOKE_METHOD_METHOD.equals(methodName) && metaMethod.getParameterTypes().length == 2;
}
private void performRegistryCallbacks() {
MetaClassRegistry registry = GroovySystem.getMetaClassRegistry();
if(!modified) {
modified = true;
// Implementation note: By default Groovy uses soft references to store MetaClass
// this insures the registry doesn't grow and get out of hand. By doing this we're
// saying this this EMC will be a hard reference in the registry. As we're only
// going have a small number of classes that have modified EMC this is ok
if(inRegistry) {
MetaClass currMetaClass = registry.getMetaClass(theClass);
if(!(currMetaClass instanceof ExpandoMetaClass) && currMetaClass instanceof AdaptingMetaClass) {
((AdaptingMetaClass)currMetaClass).setAdaptee(this);
} else {
registry.setMetaClass(theClass, this);
}
}
}
// Implementation note: EMC handles most cases by itself except for the case where yuou
// want to call a dynamically injected method registered with a parent on a child class
// For this to work the MetaClassRegistry needs to have an ExpandoMetaClassCreationHandle
// What this does is ensure that EVERY class created in the registry uses an EMC
// Then when an EMC changes it reports back to the EMCCreationHandle which will
// tell child classes of this class to re-inherit their methods
if(registry.getMetaClassCreationHandler() instanceof ExpandoMetaClassCreationHandle) {
ExpandoMetaClassCreationHandle creationHandler = (ExpandoMetaClassCreationHandle)registry.getMetaClassCreationHandler();
hasCreationHandle = true;
if(!creationHandler.hasModifiedMetaClass(this))
creationHandler.registerModifiedMetaClass(this);
}
}
private void registerBeanPropertyForMethod(MetaMethod metaMethod, String propertyName, boolean getter, boolean isStatic) {
Map propertyCache = isStatic ? staticBeanPropertyCache : beanPropertyCache;
MetaBeanProperty beanProperty = (MetaBeanProperty)propertyCache.get(propertyName);
if(beanProperty == null) {
if(getter)
beanProperty = new MetaBeanProperty(propertyName,Object.class,metaMethod,null);
else
beanProperty = new MetaBeanProperty(propertyName,Object.class,null,metaMethod);
propertyCache.put(propertyName, beanProperty);
}
else {
if(getter) {
MetaMethod setterMethod = beanProperty.getSetter();
Class type = setterMethod != null ? setterMethod.getParameterTypes()[0].cachedClass : Object.class;
beanProperty = new MetaBeanProperty(propertyName,type,metaMethod,setterMethod);
propertyCache.put(propertyName, beanProperty);
}else {
MetaMethod getterMethod = beanProperty.getGetter();
beanProperty = new MetaBeanProperty(propertyName, metaMethod.getParameterTypes()[0].cachedClass,getterMethod,metaMethod);
propertyCache .put(propertyName, beanProperty);
}
}
expandoProperties.put(beanProperty.getName(),beanProperty);
addMetaBeanProperty(beanProperty);
}
/**
* Registers a new static method for the given method name and closure on this MetaClass
*
* @param name The method name
* @param callable The callable Closure
*/
protected void registerStaticMethod(final String name, final Closure callable) {
performOperationOnMetaClass(new Callable() {
public void call() {
String methodName;
if(name.equals(METHOD_MISSING))
methodName = STATIC_METHOD_MISSING;
else if(name.equals(PROPERTY_MISSING))
methodName = STATIC_PROPERTY_MISSING;
else
methodName = name;
ClosureStaticMetaMethod metaMethod = new ClosureStaticMetaMethod(methodName, theClass,callable);
if(methodName.equals(INVOKE_METHOD_METHOD) && callable.getParameterTypes().length == 2) {
invokeStaticMethodMethod = metaMethod;
}
else {
if(methodName.equals(METHOD_MISSING)) {
methodName = STATIC_METHOD_MISSING;
}
MethodKey key = new DefaultCachedMethodKey(theClass,methodName, metaMethod.getParameterTypes(), false );
addMetaMethod(metaMethod);
cacheStaticMethod(key,metaMethod);
if(isGetter(methodName, metaMethod.getParameterTypes())) {
String propertyName = getPropertyForGetter(methodName);
registerBeanPropertyForMethod(metaMethod, propertyName, true, true);
}
else if(isSetter(methodName, metaMethod.getParameterTypes())) {
String propertyName = getPropertyForSetter(methodName);
registerBeanPropertyForMethod(metaMethod, propertyName, false, true);
}
performRegistryCallbacks();
expandoMethods.put(key,metaMethod);
}
}
});
}
/**
* @return The Java class enhanced by this MetaClass
*/
public Class getJavaClass() {
return theClass;
}
/**
* Called from ExpandoMetaClassCreationHandle in the registry if it exists to setup inheritance
* handling
*
* @param modifiedSuperExpandos A list of modified super ExpandoMetaClass
*/
public void refreshInheritedMethods(Set modifiedSuperExpandos) {
for (Iterator i = modifiedSuperExpandos.iterator(); i.hasNext();) {
ExpandoMetaClass superExpando = (ExpandoMetaClass) i.next();
if(superExpando != this) {
List metaMethods = superExpando.getExpandoMethods();
for (Iterator j = metaMethods.iterator(); j.hasNext();) {
MetaMethod metaMethod = (MetaMethod) j.next();
if(metaMethod.isStatic()) continue; // don't inherit static methodsw
addSuperMethodIfNotOverriden(metaMethod);
}
Collection metaProperties = superExpando.getExpandoProperties();
for (Iterator j = metaProperties.iterator(); j.hasNext();) {
MetaBeanProperty property = (MetaBeanProperty) j.next();
expandoProperties.put(property.getName(),property);
addMetaBeanProperty(property);
}
}
}
}
/**
* Returns a list of expando MetaMethod instances added to this ExpandoMetaClass
*
* @return the expandoMethods
*/
public List getExpandoMethods() {
return Collections.unmodifiableList(DefaultGroovyMethods.toList(expandoMethods.values()));
}
/**
* Returns a list of MetaBeanProperty instances added to this ExpandoMetaClass
*
* @return the expandoProperties
*/
public Collection getExpandoProperties() {
return Collections.unmodifiableCollection(expandoProperties.values());
}
/**
* Overrides default implementation just in case invokeMethod has been overriden by ExpandoMetaClass
*
* @see groovy.lang.MetaClassImpl#invokeMethod(Class, Object, String, Object[], boolean, boolean)
*/
public Object invokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) {
if(invokeMethodMethod!=null) {
return invokeMethodMethod.invoke(object, new Object[]{methodName, originalArguments});
}
return super.invokeMethod(sender, object, methodName, originalArguments, isCallToSuper, fromInsideClass);
}
/**
* Overrides default implementation just in case a static invoke method has been set on ExpandoMetaClass
* @see MetaClassImpl#invokeStaticMethod(Object, String, Object[])
*/
public Object invokeStaticMethod(Object object, String methodName, Object[] arguments) {
if(invokeStaticMethodMethod != null) {
return invokeStaticMethodMethod.invoke(object, new Object[]{methodName, arguments});
}
return super.invokeStaticMethod(object, methodName, arguments);
}
/**
* Overrides default implementation just in case getProperty method has been overriden by ExpandoMetaClass
*
* @see MetaClassImpl#getProperty(Class, Object, String, boolean, boolean)
*/
public Object getProperty(Class sender, Object object, String name, boolean useSuper, boolean fromInsideClass) {
if(hasOverrideGetProperty(name) && getJavaClass().isInstance(object)) {
return getPropertyMethod.invoke(object, new Object[]{name});
}
return super.getProperty(sender, object, name, useSuper, fromInsideClass);
}
/**
* Overrides default implementation just in case getProperty method has been overriden by ExpandoMetaClass
*
* @see MetaClassImpl#getProperty(Object, String)
*/
public Object getProperty(Object object, String name) {
if(hasOverrideGetProperty(name) && getJavaClass().isInstance(object)) {
return getPropertyMethod.invoke(object, new Object[]{name});
}
return super.getProperty(object,name);
}
private boolean hasOverrideGetProperty(String name) {
return getPropertyMethod != null && !name.equals(META_CLASS_PROPERTY)&& !name.equals(CLASS_PROPERTY);
}
/**
* Overrides default implementation just in case setProperty method has been overriden by ExpandoMetaClass
*
* @see MetaClassImpl#setProperty(Class, Object, String, Object, boolean, boolean)
*/
public void setProperty(Class sender, Object object, String name, Object newValue, boolean useSuper, boolean fromInsideClass) {
if(setPropertyMethod!=null && !name.equals(META_CLASS_PROPERTY) && getJavaClass().isInstance(object)) {
setPropertyMethod.invoke(object, new Object[]{name, newValue});
return;
}
super.setProperty(sender, object, name, newValue, useSuper, fromInsideClass);
}
/**
* Looks up an existing MetaProperty by name
*
* @param name The name of the MetaProperty
* @return The MetaProperty or null if it doesn't exist
*/
public MetaProperty getMetaProperty(String name) {
MetaProperty mp = (MetaProperty) this.expandoProperties.get(name);
if (mp != null) return mp;
return super.getMetaProperty(name);
}
/**
* Returns true if the MetaClass has the given property
*
* @param name The name of the MetaProperty
* @return True it exists as a MetaProperty
*/
public boolean hasMetaProperty(String name) {
return getMetaProperty(name) != null;
}
/**
* Checks whether a MetaMethod for the given name and arguments exists
*
* @param name The name of the MetaMethod
* @param args The arguments to the meta method
* @return True if the method exists otherwise null
*/
public boolean hasMetaMethod(String name, Class[] args) {
return super.pickMethod(name, args) != null;
}
/**
* Returns true if the name of the method specified and the number of arguments make it a javabean property
*
* @param name True if its a Javabean property
* @param args The arguments
* @return True if it is a javabean property method
*/
private boolean isGetter(String name, CachedClass[] args) {
if(name == null || name.length() == 0 || args == null)return false;
if(args.length != 0)return false;
if(name.startsWith("get")) {
name = name.substring(3);
if(name.length() > 0 && Character.isUpperCase(name.charAt(0))) return true;
}
else if(name.startsWith("is")) {
name = name.substring(2);
if(name.length() > 0 && Character.isUpperCase(name.charAt(0))) return true;
}
return false;
}
/**
* Returns a property name equivalent for the given getter name or null if it is not a getter
*
* @param getterName The getter name
* @return The property name equivalent
*/
private String getPropertyForGetter(String getterName) {
if(getterName == null || getterName.length() == 0)return null;
if(getterName.startsWith("get")) {
String prop = getterName.substring(3);
return convertPropertyName(prop);
}
else if(getterName.startsWith("is")) {
String prop = getterName.substring(2);
return convertPropertyName(prop);
}
return null;
}
private String convertPropertyName(String prop) {
if(Character.isUpperCase(prop.charAt(0)) && (prop.length() > 1 && Character.isUpperCase(prop.charAt(1)))) {
return prop;
}
else if(Character.isDigit(prop.charAt(0))) {
return prop;
}
else {
return Character.toLowerCase(prop.charAt(0)) + (prop.length() > 1 ? prop.substring(1) : "");
}
}
/**
* Returns a property name equivalent for the given setter name or null if it is not a getter
*
* @param setterName The setter name
* @return The property name equivalent
*/
public String getPropertyForSetter(String setterName) {
if(setterName == null || setterName.length() == 0)return null;
if(setterName.startsWith("set")) {
String prop = setterName.substring(3);
return convertPropertyName(prop);
}
return null;
}
public boolean isSetter(String name, CachedClass[] args) {
if(name == null || name.length() == 0 || args == null)return false;
if(name.startsWith("set")) {
if(args.length != 1) return false;
name = name.substring(3);
if(name.length() > 0 && Character.isUpperCase(name.charAt(0))) return true;
}
return false;
}
}