/* * Copyright 2003-2009 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.GroovyBugError; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.classgen.asm.BytecodeHelper; import org.codehaus.groovy.control.CompilationUnit; import org.codehaus.groovy.control.Phases; import org.codehaus.groovy.reflection.CachedClass; import org.codehaus.groovy.reflection.CachedConstructor; import org.codehaus.groovy.reflection.CachedField; import org.codehaus.groovy.reflection.CachedMethod; import org.codehaus.groovy.reflection.ClassInfo; import org.codehaus.groovy.reflection.GeneratedMetaMethod; import org.codehaus.groovy.reflection.ParameterTypes; import org.codehaus.groovy.reflection.ReflectionCache; import org.codehaus.groovy.runtime.ConvertedClosure; import org.codehaus.groovy.runtime.CurriedClosure; import org.codehaus.groovy.runtime.DefaultGroovyMethods; import org.codehaus.groovy.runtime.GroovyCategorySupport; import org.codehaus.groovy.runtime.InvokerHelper; import org.codehaus.groovy.runtime.InvokerInvocationException; import org.codehaus.groovy.runtime.MetaClassHelper; import org.codehaus.groovy.runtime.MethodClosure; import org.codehaus.groovy.runtime.callsite.CallSite; import org.codehaus.groovy.runtime.callsite.ConstructorSite; import org.codehaus.groovy.runtime.callsite.MetaClassConstructorSite; import org.codehaus.groovy.runtime.callsite.PogoMetaClassSite; import org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite; import org.codehaus.groovy.runtime.callsite.PojoMetaClassSite; import org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite; import org.codehaus.groovy.runtime.callsite.StaticMetaClassSite; import org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite; import org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod; import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl; import org.codehaus.groovy.runtime.metaclass.MetaMethodIndex; import org.codehaus.groovy.runtime.metaclass.MethodSelectionException; import org.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack; import org.codehaus.groovy.runtime.metaclass.MissingMethodExecutionFailed; import org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack; import org.codehaus.groovy.runtime.metaclass.MixinInstanceMetaMethod; import org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod; import org.codehaus.groovy.runtime.metaclass.NewMetaMethod; import org.codehaus.groovy.runtime.metaclass.NewStaticMetaMethod; import org.codehaus.groovy.runtime.metaclass.TransformMetaMethod; import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; import org.codehaus.groovy.runtime.typehandling.NumberMathModificationInfo; import org.codehaus.groovy.runtime.wrappers.Wrapper; import org.codehaus.groovy.util.ComplexKeyHashMap; import org.codehaus.groovy.util.FastArray; import org.codehaus.groovy.util.SingleKeyHashMap; import org.objectweb.asm.ClassVisitor; import java.beans.BeanInfo; import java.beans.EventSetDescriptor; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; /** * Allows methods to be dynamically added to existing classes at runtime * * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> * @author Guillaume Laforge * @author Jochen Theodorou * @author Graeme Rocher * @author Alex Tkachman * @version $Revision$ * @see groovy.lang.MetaClass */ public class MetaClassImpl implements MetaClass, MutableMetaClass { private static final String CLOSURE_CALL_METHOD = "call"; private static final String CLOSURE_DO_CALL_METHOD = "doCall"; protected static final String STATIC_METHOD_MISSING = "$static_methodMissing"; protected static final String STATIC_PROPERTY_MISSING = "$static_propertyMissing"; protected static final String METHOD_MISSING = "methodMissing"; protected static final String PROPERTY_MISSING = "propertyMissing"; private static final String GET_PROPERTY_METHOD = "getProperty"; private static final String SET_PROPERTY_METHOD = "setProperty"; protected static final String INVOKE_METHOD_METHOD = "invokeMethod"; private static final Class[] METHOD_MISSING_ARGS = new Class[]{String.class, Object.class}; private static final Class[] GETTER_MISSING_ARGS = new Class[]{String.class}; private static final Class[] SETTER_MISSING_ARGS = METHOD_MISSING_ARGS; protected final Class theClass; protected final CachedClass theCachedClass; private static final MetaMethod[] EMPTY = new MetaMethod[0]; protected MetaMethod getPropertyMethod; protected MetaMethod invokeMethodMethod; protected MetaMethod setPropertyMethod; public final CachedClass getTheCachedClass() { return theCachedClass; } protected MetaClassRegistry registry; protected final boolean isGroovyObject; protected final boolean isMap; private ClassNode classNode; private final Index classPropertyIndex = new MethodIndex(); private Index classPropertyIndexForSuper = new MethodIndex(); private final SingleKeyHashMap staticPropertyIndex = new SingleKeyHashMap(); private final Map<String, MetaMethod> listeners = new HashMap<String, MetaMethod>(); private FastArray constructors; private final List<MetaMethod> allMethods = new ArrayList<MetaMethod>(); private boolean initialized; // we only need one of these that can be reused over and over. private final MetaProperty arrayLengthProperty = new MetaArrayLengthProperty(); private static final MetaMethod AMBIGUOUS_LISTENER_METHOD = new DummyMetaMethod(); private static final Object[] EMPTY_ARGUMENTS = {}; private final Set<MetaMethod> newGroovyMethodsSet = new HashSet<MetaMethod>(); private MetaMethod genericGetMethod; private MetaMethod genericSetMethod; private MetaMethod propertyMissingGet; private MetaMethod propertyMissingSet; private MetaMethod methodMissing; private MetaMethodIndex.Header mainClassMethodHeader; protected final MetaMethodIndex metaMethodIndex; private final MetaMethod [] myNewMetaMethods; private final MetaMethod [] additionalMetaMethods; public MetaClassImpl(final Class theClass, MetaMethod [] add) { this.theClass = theClass; theCachedClass = ReflectionCache.getCachedClass(theClass); this.isGroovyObject = GroovyObject.class.isAssignableFrom(theClass); this.isMap = Map.class.isAssignableFrom(theClass); this.registry = GroovySystem.getMetaClassRegistry(); metaMethodIndex = new MetaMethodIndex(theCachedClass); final MetaMethod[] metaMethods = theCachedClass.getNewMetaMethods(); if (add != null && !(add.length == 0)) { ArrayList<MetaMethod> arr = new ArrayList<MetaMethod>(); arr.addAll(Arrays.asList(metaMethods)); arr.addAll(Arrays.asList(add)); myNewMetaMethods = arr.toArray(new MetaMethod[arr.size()]); additionalMetaMethods = metaMethods; } else { myNewMetaMethods = metaMethods; additionalMetaMethods = EMPTY; } } public MetaClassImpl(final Class theClass) { this(theClass, null); } public MetaClassImpl(MetaClassRegistry registry, final Class theClass, MetaMethod add []) { this(theClass, add); this.registry = registry; this.constructors = new FastArray(theCachedClass.getConstructors()); } public MetaClassImpl(MetaClassRegistry registry, final Class theClass) { this(registry, theClass, null); } /** * @see MetaObjectProtocol#respondsTo(Object, String, Object[]) */ public List respondsTo(Object obj, String name, Object[] argTypes) { Class[] classes = MetaClassHelper.castArgumentsToClassArray(argTypes); MetaMethod m = getMetaMethod(name, classes); List<MetaMethod> methods = new ArrayList<MetaMethod>(); if (m != null) { methods.add(m); } return methods; } /** * @see MetaObjectProtocol#respondsTo(Object, String) */ public List respondsTo(final Object obj, final String name) { final Object o = getMethods(getTheClass(), name, false); if (o instanceof FastArray) return ((FastArray) o).toList(); else return Collections.singletonList(o); } /** * @see MetaObjectProtocol#hasProperty(Object,String) */ public MetaProperty hasProperty(Object obj, String name) { return getMetaProperty(name); } /** * @see MetaObjectProtocol#getMetaProperty(String) */ public MetaProperty getMetaProperty(String name) { SingleKeyHashMap propertyMap = classPropertyIndex.getNotNull(theCachedClass); if (propertyMap.containsKey(name)) { return (MetaProperty) propertyMap.get(name); } else if (staticPropertyIndex.containsKey(name)) { return (MetaProperty) staticPropertyIndex.get(name); } else { propertyMap = classPropertyIndexForSuper.getNotNull(theCachedClass); if (propertyMap.containsKey(name)) return (MetaProperty) propertyMap.get(name); else { CachedClass superClass = theCachedClass; while (superClass != null && superClass != ReflectionCache.OBJECT_CLASS) { final MetaBeanProperty property = findPropertyInClassHierarchy(name, superClass); if (property != null) { onSuperPropertyFoundInHierarchy(property); return property; } superClass = superClass.getCachedSuperClass(); } return null; } } } /** * @see MetaObjectProtocol#getStaticMetaMethod(String, Object[]) */ public MetaMethod getStaticMetaMethod(String name, Object[] argTypes) { Class[] classes = MetaClassHelper.castArgumentsToClassArray(argTypes); return pickStaticMethod(name, classes); } /** * @see MetaObjectProtocol#getMetaMethod(String, Object[]) */ public MetaMethod getMetaMethod(String name, Object[] argTypes) { Class[] classes = MetaClassHelper.castArgumentsToClassArray(argTypes); return pickMethod(name, classes); } public Class getTheClass() { return this.theClass; } public boolean isGroovyObject() { return isGroovyObject; } private void fillMethodIndex() { mainClassMethodHeader = metaMethodIndex.getHeader(theClass); LinkedList superClasses = getSuperClasses(); CachedClass firstGroovySuper = calcFirstGroovySuperClass(superClasses); Set<CachedClass> interfaces = theCachedClass.getInterfaces(); addInterfaceMethods(interfaces); populateMethods(superClasses, firstGroovySuper); inheritInterfaceNewMetaMethods(interfaces); if (isGroovyObject) { metaMethodIndex.copyMethodsToSuper(); connectMultimethods(superClasses, firstGroovySuper); removeMultimethodsOverloadedWithPrivateMethods(); replaceWithMOPCalls(theCachedClass.mopMethods); } } private void populateMethods(LinkedList superClasses, CachedClass firstGroovySuper) { Iterator iter = superClasses.iterator(); MetaMethodIndex.Header header = metaMethodIndex.getHeader(firstGroovySuper.getTheClass()); CachedClass c; for (; iter.hasNext();) { c = (CachedClass) iter.next(); CachedMethod[] cachedMethods = c.getMethods(); for (CachedMethod metaMethod : cachedMethods) { addToAllMethodsIfPublic(metaMethod); if (!metaMethod.isPrivate() || c == firstGroovySuper) addMetaMethodToIndex(metaMethod, header); } MetaMethod[] cachedMethods1 = getNewMetaMethods(c); for (final MetaMethod method : cachedMethods1) { if (!newGroovyMethodsSet.contains(method)) { newGroovyMethodsSet.add(method); addMetaMethodToIndex(method, header); } } if (c == firstGroovySuper) break; } MetaMethodIndex.Header last = header; for (;iter.hasNext();) { c = (CachedClass) iter.next(); header = metaMethodIndex.getHeader(c.getTheClass()); if (last != null) { metaMethodIndex.copyNonPrivateMethods(last, header); } last = header; for (CachedMethod metaMethod : c.getMethods()) { addToAllMethodsIfPublic(metaMethod); addMetaMethodToIndex(metaMethod, header); } for (final MetaMethod method : getNewMetaMethods(c)) { if (!newGroovyMethodsSet.contains(method)) { newGroovyMethodsSet.add(method); addMetaMethodToIndex(method, header); } } } } private MetaMethod[] getNewMetaMethods(CachedClass c) { if (theCachedClass != c) return c.getNewMetaMethods(); return myNewMetaMethods; } private void addInterfaceMethods(Set<CachedClass> interfaces) { MetaMethodIndex.Header header = metaMethodIndex.getHeader(theClass); for (CachedClass c : interfaces) { final CachedMethod[] m = c.getMethods(); for (int i = 0; i != m.length; ++i) { MetaMethod method = m[i]; addMetaMethodToIndex(method, header); } } } protected LinkedList<CachedClass> getSuperClasses() { LinkedList<CachedClass> superClasses = new LinkedList<CachedClass>(); if (theClass.isInterface()) { superClasses.addFirst(ReflectionCache.OBJECT_CLASS); } else { for (CachedClass c = theCachedClass; c != null; c = c.getCachedSuperClass()) { superClasses.addFirst(c); } if (theCachedClass.isArray && theClass != Object[].class && !theClass.getComponentType().isPrimitive()) { superClasses.addFirst(ReflectionCache.OBJECT_ARRAY_CLASS); } } return superClasses; } private void removeMultimethodsOverloadedWithPrivateMethods() { MethodIndexAction mia = new MethodIndexAction() { public boolean skipClass(Class clazz) { return clazz == theClass; } public void methodNameAction(Class clazz, MetaMethodIndex.Entry e) { if (e.methods == null) return; boolean hasPrivate = false; if (e.methods instanceof FastArray) { FastArray methods = (FastArray) e.methods; final int len = methods.size(); final Object[] data = methods.getArray(); for (int i = 0; i != len; ++i) { MetaMethod method = (MetaMethod) data[i]; if (method.isPrivate() && clazz == method.getDeclaringClass().getTheClass()) { hasPrivate = true; break; } } } else { MetaMethod method = (MetaMethod) e.methods; if (method.isPrivate() && clazz == method.getDeclaringClass().getTheClass()) { hasPrivate = true; } } if (!hasPrivate) return; // We have private methods for that name, so remove the // multimethods. That is the same as in our index for // super, so just copy the list from there. It is not // possible to use a pointer here, because the methods // in the index for super are replaced later by MOP // methods like super$5$foo final Object o = e.methodsForSuper; if (o instanceof FastArray) e.methods = ((FastArray) o).copy(); else e.methods = o; } }; mia.iterate(); } private void replaceWithMOPCalls(final CachedMethod[] mopMethods) { // no MOP methods if not a child of GroovyObject if (!isGroovyObject) return; class MOPIter extends MethodIndexAction { boolean useThis; public boolean skipClass(CachedClass clazz) { return !useThis && clazz == theCachedClass; } public void methodNameAction(Class clazz, MetaMethodIndex.Entry e) { if (useThis) { if (e.methods == null) return; if (e.methods instanceof FastArray) { FastArray methods = (FastArray) e.methods; processFastArray(methods); } else { MetaMethod method = (MetaMethod) e.methods; if (method instanceof NewMetaMethod) return; if (useThis ^ (method.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0) return; String mopName = method.getMopName(); int index = Arrays.binarySearch(mopMethods, mopName, CachedClass.CachedMethodComparatorWithString.INSTANCE); if (index >= 0) { int from = index; while (from > 0 && mopMethods[from-1].getName().equals(mopName)) from--; int to = index; while (to < mopMethods.length-1 && mopMethods[to+1].getName().equals(mopName)) to++; int matchingMethod = findMatchingMethod(mopMethods, from, to, method); if (matchingMethod != -1) { e.methods = mopMethods[matchingMethod]; } } } } else { if (e.methodsForSuper == null) return; if (e.methodsForSuper instanceof FastArray) { FastArray methods = (FastArray) e.methodsForSuper; processFastArray(methods); } else { MetaMethod method = (MetaMethod) e.methodsForSuper; if (method instanceof NewMetaMethod) return; if (useThis ^ (method.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0) return; String mopName = method.getMopName(); int index = Arrays.binarySearch(mopMethods, mopName, CachedClass.CachedMethodComparatorWithString.INSTANCE); if (index >= 0) { int from = index; while (from > 0 && mopMethods[from-1].getName().equals(mopName)) from--; int to = index; while (to < mopMethods.length-1 && mopMethods[to+1].getName().equals(mopName)) to++; int matchingMethod = findMatchingMethod(mopMethods, from, to, method); if (matchingMethod != -1) { e.methodsForSuper = mopMethods[matchingMethod]; } } } } } private void processFastArray(FastArray methods) { final int len = methods.size(); final Object[] data = methods.getArray(); for (int i = 0; i != len; ++i) { MetaMethod method = (MetaMethod) data[i]; if (method instanceof NewMetaMethod) continue; if (useThis ^ (method.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0) continue; String mopName = method.getMopName(); int index = Arrays.binarySearch(mopMethods, mopName, CachedClass.CachedMethodComparatorWithString.INSTANCE); if (index >= 0) { int from = index; while (from > 0 && mopMethods[from-1].getName().equals(mopName)) from--; int to = index; while (to < mopMethods.length-1 && mopMethods[to+1].getName().equals(mopName)) to++; int matchingMethod = findMatchingMethod(mopMethods, from, to, method); if (matchingMethod != -1) { methods.set(i, mopMethods[matchingMethod]); } } } } } MOPIter iter = new MOPIter(); // replace all calls for super with the correct MOP method iter.useThis = false; iter.iterate(); // replace all calls for this with the correct MOP method iter.useThis = true; iter.iterate(); } private void inheritInterfaceNewMetaMethods(Set<CachedClass> interfaces) { // add methods declared by DGM for interfaces for (CachedClass cls : interfaces) { MetaMethod methods[] = getNewMetaMethods(cls); for (MetaMethod method : methods) { if (!newGroovyMethodsSet.contains(method)) { newGroovyMethodsSet.add(method); } addMetaMethodToIndex(method, mainClassMethodHeader); } } } private void connectMultimethods(List superClasses, CachedClass firstGroovyClass) { superClasses = DefaultGroovyMethods.reverse(superClasses); MetaMethodIndex.Header last = null; for (Iterator iter = superClasses.iterator(); iter.hasNext();) { CachedClass c = (CachedClass) iter.next(); MetaMethodIndex.Header methodIndex = metaMethodIndex.getHeader(c.getTheClass()); // We don't copy DGM methods to superclasses' indexes // The reason we can do that is particular set of DGM methods in use, // if at some point we will define DGM method for some Groovy class or // for a class derived from such, we will need to revise this condition. // It saves us a lot of space and some noticeable time if (last != null) metaMethodIndex.copyNonPrivateNonNewMetaMethods(last, methodIndex); last = methodIndex; if (c == firstGroovyClass) break; } } private CachedClass calcFirstGroovySuperClass(Collection superClasses) { if (theCachedClass.isInterface) return ReflectionCache.OBJECT_CLASS; CachedClass firstGroovy = null; Iterator iter = superClasses.iterator(); for (; iter.hasNext();) { CachedClass c = (CachedClass) iter.next(); if (GroovyObject.class.isAssignableFrom(c.getTheClass())) { firstGroovy = c; break; } } if (firstGroovy == null) firstGroovy = theCachedClass; else { if (firstGroovy.getTheClass() == GroovyObjectSupport.class && iter.hasNext()) { firstGroovy = (CachedClass) iter.next(); if (firstGroovy.getTheClass() == Closure.class && iter.hasNext()) { firstGroovy = (CachedClass) iter.next(); } } } return GroovyObject.class.isAssignableFrom(firstGroovy.getTheClass()) ? firstGroovy.getCachedSuperClass() : firstGroovy; } /** * @return all the normal instance methods available on this class for the * given name */ private Object getMethods(Class sender, String name, boolean isCallToSuper) { Object answer; final MetaMethodIndex.Entry entry = metaMethodIndex.getMethods(sender, name); if (entry == null) answer = FastArray.EMPTY_LIST; else if (isCallToSuper) { answer = entry.methodsForSuper; } else { answer = entry.methods; } if (answer == null) answer = FastArray.EMPTY_LIST; if (!isCallToSuper) { List used = GroovyCategorySupport.getCategoryMethods(name); if (used != null) { FastArray arr; if (answer instanceof MetaMethod) { arr = new FastArray(); arr.add(answer); } else arr = ((FastArray) answer).copy(); for (Iterator iter = used.iterator(); iter.hasNext();) { MetaMethod element = (MetaMethod) iter.next(); if (!element.getDeclaringClass().getTheClass().isAssignableFrom(sender)) continue; filterMatchingMethodForCategory(arr, element); } answer = arr; } } return answer; } /** * @return all the normal static methods available on this class for the * given name */ private Object getStaticMethods(Class sender, String name) { final MetaMethodIndex.Entry entry = metaMethodIndex.getMethods(sender, name); if (entry == null) return FastArray.EMPTY_LIST; Object answer = entry.staticMethods; if (answer == null) return FastArray.EMPTY_LIST; return answer; } public boolean isModified() { return false; // MetaClassImpl not designed for modification, just return false } public void addNewInstanceMethod(Method method) { final CachedMethod cachedMethod = CachedMethod.find(method); NewInstanceMetaMethod newMethod = new NewInstanceMetaMethod(cachedMethod); final CachedClass declaringClass = newMethod.getDeclaringClass(); addNewInstanceMethodToIndex(newMethod, metaMethodIndex.getHeader(declaringClass.getTheClass())); } private void addNewInstanceMethodToIndex(MetaMethod newMethod, MetaMethodIndex.Header header) { if (!newGroovyMethodsSet.contains(newMethod)) { newGroovyMethodsSet.add(newMethod); addMetaMethodToIndex(newMethod, header); } } public void addNewStaticMethod(Method method) { final CachedMethod cachedMethod = CachedMethod.find(method); NewStaticMetaMethod newMethod = new NewStaticMetaMethod(cachedMethod); final CachedClass declaringClass = newMethod.getDeclaringClass(); addNewStaticMethodToIndex(newMethod, metaMethodIndex.getHeader(declaringClass.getTheClass())); } private void addNewStaticMethodToIndex(MetaMethod newMethod, MetaMethodIndex.Header header) { if (!newGroovyMethodsSet.contains(newMethod)) { newGroovyMethodsSet.add(newMethod); addMetaMethodToIndex(newMethod, header); } } public Object invokeMethod(Object object, String methodName, Object arguments) { if (arguments == null) { return invokeMethod(object, methodName, MetaClassHelper.EMPTY_ARRAY); } if (arguments instanceof Tuple) { Tuple tuple = (Tuple) arguments; return invokeMethod(object, methodName, tuple.toArray()); } if (arguments instanceof Object[]) { return invokeMethod(object, methodName, (Object[]) arguments); } else { return invokeMethod(object, methodName, new Object[]{arguments}); } } public Object invokeMissingMethod(Object instance, String methodName, Object[] arguments) { return invokeMissingMethod(instance, methodName, arguments, null, false); } public Object invokeMissingProperty(Object instance, String propertyName, Object optionalValue, boolean isGetter) { Class theClass = instance instanceof Class ? (Class)instance : instance.getClass(); CachedClass superClass = theCachedClass; while(superClass != null && superClass != ReflectionCache.OBJECT_CLASS) { final MetaBeanProperty property = findPropertyInClassHierarchy(propertyName, superClass); if(property != null) { onSuperPropertyFoundInHierarchy(property); if(!isGetter) { property.setProperty(instance, optionalValue); return null; } else { return property.getProperty(instance); } } superClass = superClass.getCachedSuperClass(); } // got here to property not found, look for getProperty or setProperty overrides if(isGetter) { final Class[] getPropertyArgs = {String.class}; final MetaMethod method = findMethodInClassHierarchy(instance.getClass(), GET_PROPERTY_METHOD, getPropertyArgs, this); if(method != null && method instanceof ClosureMetaMethod) { onGetPropertyFoundInHierarchy(method); return method.invoke(instance,new Object[]{propertyName}); } } else { final Class[] setPropertyArgs = {String.class, Object.class}; final MetaMethod method = findMethodInClassHierarchy(instance.getClass(), SET_PROPERTY_METHOD, setPropertyArgs, this); if(method != null && method instanceof ClosureMetaMethod) { onSetPropertyFoundInHierarchy(method); return method.invoke(instance, new Object[]{propertyName, optionalValue}); } } try { if (!(instance instanceof Class)) { if (isGetter && propertyMissingGet != null) { return propertyMissingGet.invoke(instance, new Object[]{propertyName}); } else { if (propertyMissingSet != null) return propertyMissingSet.invoke(instance, new Object[]{propertyName, optionalValue}); } } } catch (InvokerInvocationException iie) { boolean shouldHandle = isGetter && propertyMissingGet != null; if (!shouldHandle) shouldHandle = !isGetter && propertyMissingSet != null; if (shouldHandle && iie.getCause() instanceof MissingPropertyException) { throw (MissingPropertyException) iie.getCause(); } throw iie; } if (instance instanceof Class && theClass != Class.class) { final MetaProperty metaProperty = InvokerHelper.getMetaClass(Class.class).hasProperty(instance, propertyName); if (metaProperty != null) if (isGetter) return metaProperty.getProperty(instance); else { metaProperty.setProperty(instance, optionalValue); return null; } } throw new MissingPropertyExceptionNoStack(propertyName, theClass); } private Object invokeMissingMethod(Object instance, String methodName, Object[] arguments, RuntimeException original, boolean isCallToSuper) { if (!isCallToSuper) { Class instanceKlazz = instance.getClass(); if (theClass != instanceKlazz && theClass.isAssignableFrom(instanceKlazz)) instanceKlazz = theClass; Class[] argClasses = MetaClassHelper.castArgumentsToClassArray(arguments); MetaMethod method = findMixinMethod(methodName, argClasses); if(method != null) { onMixinMethodFound(method); return method.invoke(instance, arguments); } method = findMethodInClassHierarchy(instanceKlazz, methodName, argClasses, this); if(method != null) { onSuperMethodFoundInHierarchy(method); return method.invoke(instance, arguments); } // still not method here, so see if there is an invokeMethod method up the hierarchy final Class[] invokeMethodArgs = {String.class, Object[].class}; method = findMethodInClassHierarchy(instanceKlazz, INVOKE_METHOD_METHOD, invokeMethodArgs, this ); if(method != null && method instanceof ClosureMetaMethod) { onInvokeMethodFoundInHierarchy(method); return method.invoke(instance, invokeMethodArgs); } } if (methodMissing != null) { try { return methodMissing.invoke(instance, new Object[]{methodName, arguments}); } catch (InvokerInvocationException iie) { if (methodMissing instanceof ClosureMetaMethod && iie.getCause() instanceof MissingMethodException) { MissingMethodException mme = (MissingMethodException) iie.getCause(); throw new MissingMethodExecutionFailed (mme.getMethod(), mme.getClass(), mme.getArguments(),mme.isStatic(),mme); } throw iie; } catch (MissingMethodException mme) { if (methodMissing instanceof ClosureMetaMethod) throw new MissingMethodExecutionFailed (mme.getMethod(), mme.getClass(), mme.getArguments(),mme.isStatic(),mme); else throw mme; } } else if (original != null) throw original; else throw new MissingMethodExceptionNoStack(methodName, theClass, arguments, false); } protected void onSuperPropertyFoundInHierarchy(MetaBeanProperty property) { } protected void onMixinMethodFound(MetaMethod method) { } protected void onSuperMethodFoundInHierarchy(MetaMethod method) { } protected void onInvokeMethodFoundInHierarchy(MetaMethod method) { } protected void onSetPropertyFoundInHierarchy(MetaMethod method) { } protected void onGetPropertyFoundInHierarchy(MetaMethod method) { } /** * Hook to deal with the case of MissingProperty for static properties. The method will look attempt to look up * "propertyMissing" handlers and invoke them otherwise thrown a MissingPropertyException * * @param instance The instance * @param propertyName The name of the property * @param optionalValue The value in the case of a setter * @param isGetter True if its a getter * @return The value in the case of a getter or a MissingPropertyException */ protected Object invokeStaticMissingProperty(Object instance, String propertyName, Object optionalValue, boolean isGetter) { MetaClass mc = instance instanceof Class ? registry.getMetaClass((Class) instance) : this; if (isGetter) { MetaMethod propertyMissing = mc.getMetaMethod(STATIC_PROPERTY_MISSING, GETTER_MISSING_ARGS); if (propertyMissing != null) { return propertyMissing.invoke(instance, new Object[]{propertyName}); } } else { MetaMethod propertyMissing = mc.getMetaMethod(STATIC_PROPERTY_MISSING, SETTER_MISSING_ARGS); if (propertyMissing != null) { return propertyMissing.invoke(instance, new Object[]{propertyName, optionalValue}); } } if (instance instanceof Class) { throw new MissingPropertyException(propertyName, (Class) instance); } throw new MissingPropertyException(propertyName, theClass); } /** * Invokes the given method on the object. * TODO: should this be deprecated? If so, we have to propogate to many places. */ public Object invokeMethod(Object object, String methodName, Object[] originalArguments) { return invokeMethod(theClass, object, methodName, originalArguments, false, false); } /** * Invokes the given method on the object. */ 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"); } final Object[] arguments = originalArguments == null ? EMPTY_ARGUMENTS : originalArguments; // final Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); // // unwrap(arguments); MetaMethod method = getMethodWithCaching(sender, methodName, arguments, isCallToSuper); MetaClassHelper.unwrap(arguments); if (method == null) method = tryListParamMetaMethod(sender, methodName, isCallToSuper, arguments); final boolean isClosure = object instanceof Closure; if (isClosure) { final Closure closure = (Closure) object; final Object owner = closure.getOwner(); if (CLOSURE_CALL_METHOD.equals(methodName) || CLOSURE_DO_CALL_METHOD.equals(methodName)) { final Class objectClass = object.getClass(); if (objectClass == MethodClosure.class) { final MethodClosure mc = (MethodClosure) object; methodName = mc.getMethod(); final Class ownerClass = owner instanceof Class ? (Class) owner : owner.getClass(); final MetaClass ownerMetaClass = registry.getMetaClass(ownerClass); return ownerMetaClass.invokeMethod(ownerClass, owner, methodName, arguments, false, false); } else if (objectClass == CurriedClosure.class) { final CurriedClosure cc = (CurriedClosure) object; // change the arguments for an uncurried call final Object[] curriedArguments = cc.getUncurriedArguments(arguments); final Class ownerClass = owner instanceof Class ? (Class) owner : owner.getClass(); final MetaClass ownerMetaClass = registry.getMetaClass(ownerClass); return ownerMetaClass.invokeMethod(owner, methodName, curriedArguments); } if (method==null) invokeMissingMethod(object,methodName,arguments); } final Object delegate = closure.getDelegate(); final boolean isClosureNotOwner = owner != closure; final int resolveStrategy = closure.getResolveStrategy(); final Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); switch (resolveStrategy) { case Closure.TO_SELF: method = closure.getMetaClass().pickMethod(methodName, argClasses); if (method != null) return method.invoke(closure, arguments); break; case Closure.DELEGATE_ONLY: if (method == null && delegate != closure && delegate != null) { MetaClass delegateMetaClass = lookupObjectMetaClass(delegate); method = delegateMetaClass.pickMethod(methodName, argClasses); if (method != null) return delegateMetaClass.invokeMethod(delegate, methodName, originalArguments); else if (delegate != closure && (delegate instanceof GroovyObject)) { return invokeMethodOnGroovyObject(methodName, originalArguments, delegate); } } break; case Closure.OWNER_ONLY: if (method == null && owner != closure) { MetaClass ownerMetaClass = lookupObjectMetaClass(owner); return ownerMetaClass.invokeMethod(owner, methodName, originalArguments); } break; case Closure.DELEGATE_FIRST: if (method == null && delegate != closure && delegate != null) { MetaClass delegateMetaClass = lookupObjectMetaClass(delegate); method = delegateMetaClass.pickMethod(methodName, argClasses); if (method != null) return delegateMetaClass.invokeMethod(delegate, methodName, originalArguments); } if (method == null && owner != closure) { MetaClass ownerMetaClass = lookupObjectMetaClass(owner); method = ownerMetaClass.pickMethod(methodName, argClasses); if (method != null) return ownerMetaClass.invokeMethod(owner, methodName, originalArguments); } if (method == null && resolveStrategy != Closure.TO_SELF) { // still no methods found, test if delegate or owner are GroovyObjects // and invoke the method on them if so. MissingMethodException last = null; if (delegate != closure && (delegate instanceof GroovyObject)) { try { return invokeMethodOnGroovyObject(methodName, originalArguments, delegate); } catch (MissingMethodException mme) { if (last == null) last = mme; } } if (isClosureNotOwner && (owner instanceof GroovyObject)) { try { return invokeMethodOnGroovyObject(methodName, originalArguments, owner); } catch (MissingMethodException mme) { last = mme; } } if (last != null) return invokeMissingMethod(object, methodName, originalArguments, last, isCallToSuper); } break; default: if (method == null && owner != closure) { MetaClass ownerMetaClass = lookupObjectMetaClass(owner); method = ownerMetaClass.pickMethod(methodName, argClasses); if (method != null) return ownerMetaClass.invokeMethod(owner, methodName, originalArguments); } if (method == null && delegate != closure && delegate != null) { MetaClass delegateMetaClass = lookupObjectMetaClass(delegate); method = delegateMetaClass.pickMethod(methodName, argClasses); if (method != null) return delegateMetaClass.invokeMethod(delegate, methodName, originalArguments); } if (method == null && resolveStrategy != Closure.TO_SELF) { // still no methods found, test if delegate or owner are GroovyObjects // and invoke the method on them if so. MissingMethodException last = null; if (isClosureNotOwner && (owner instanceof GroovyObject)) { try { return invokeMethodOnGroovyObject(methodName, originalArguments, owner); } catch (MissingMethodException mme) { if (methodName.equals(mme.getMethod())) { if (last == null) last = mme; } else { throw mme; } } catch (InvokerInvocationException iie) { if (iie.getCause() instanceof MissingMethodException) { MissingMethodException mme = (MissingMethodException) iie.getCause(); if (methodName.equals(mme.getMethod())) { if (last == null) last = mme; } else { throw iie; } } else throw iie; } } if (delegate != closure && (delegate instanceof GroovyObject)) { try { return invokeMethodOnGroovyObject(methodName, originalArguments, delegate); } catch (MissingMethodException mme) { last = mme; } catch (InvokerInvocationException iie) { if (iie.getCause() instanceof MissingMethodException) { last = (MissingMethodException) iie.getCause(); } else throw iie; } } if (last != null) return invokeMissingMethod(object, methodName, originalArguments, last, isCallToSuper); } } } if (method != null) { return method.doMethodInvoke(object, arguments); } else { return invokePropertyOrMissing(object, methodName, originalArguments, fromInsideClass, isCallToSuper); } } private MetaMethod tryListParamMetaMethod(Class sender, String methodName, boolean isCallToSuper, Object[] arguments) { MetaMethod method = null; if (arguments.length == 1 && arguments[0] instanceof List) { Object[] newArguments = ((List) arguments[0]).toArray(); method = getMethodWithCaching(sender, methodName, newArguments, isCallToSuper); if (method != null) { method = new TransformMetaMethod(method) { public Object invoke(Object object, Object[] arguments) { Object firstArgument = arguments[0]; List list = (List) firstArgument; arguments = list.toArray(); return super.invoke(object, arguments); } }; } } return method; } private Object invokePropertyOrMissing(Object object, String methodName, Object[] originalArguments, boolean fromInsideClass, boolean isCallToSuper) { // if no method was found, try to find a closure defined as a field of the class and run it Object value = null; final MetaProperty metaProperty = this.getMetaProperty(methodName, false); if (metaProperty != null) value = metaProperty.getProperty(object); else { if (object instanceof Map) value = ((Map)object).get(methodName); } if (value instanceof Closure) { // This test ensures that value != this If you ever change this ensure that value != this Closure closure = (Closure) value; MetaClass delegateMetaClass = closure.getMetaClass(); return delegateMetaClass.invokeMethod(closure.getClass(), closure, CLOSURE_DO_CALL_METHOD, originalArguments, false, fromInsideClass); } if (object instanceof Script) { Object bindingVar = ((Script) object).getBinding().getVariables().get(methodName); if (bindingVar != null) { MetaClass bindingVarMC = ((MetaClassRegistryImpl) registry).getMetaClass(bindingVar); return bindingVarMC.invokeMethod(bindingVar, CLOSURE_CALL_METHOD, originalArguments); } } return invokeMissingMethod(object, methodName, originalArguments, null, isCallToSuper); } 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; } private Object invokeMethodOnGroovyObject(String methodName, Object[] originalArguments, Object owner) { GroovyObject go = (GroovyObject) owner; return go.invokeMethod(methodName, originalArguments); } public MetaMethod getMethodWithCaching(Class sender, String methodName, Object[] arguments, boolean isCallToSuper) { // let's try use the cache to find the method if (!isCallToSuper && GroovyCategorySupport.hasCategoryInCurrentThread()) { return getMethodWithoutCaching(sender, methodName, MetaClassHelper.convertToTypeArray(arguments), isCallToSuper); } else { final MetaMethodIndex.Entry e = metaMethodIndex.getMethods(sender, methodName); if (e == null) return null; return isCallToSuper ? getSuperMethodWithCaching(arguments, e) : getNormalMethodWithCaching(arguments, e); } } private static boolean sameClasses(Class[] params, Class[] arguments, boolean weakNullCheck) { // we do here a null check because the params field might not have been set yet if (params == null) return false; if (params.length != arguments.length) return false; for (int i = params.length - 1; i >= 0; i--) { Object arg = arguments[i]; if (arg != null) { if (params[i] != arguments[i]) return false; } else if (!weakNullCheck) return false; } return true; } // This method should be called by CallSite only private MetaMethod getMethodWithCachingInternal (Class sender, CallSite site, Class [] params) { if (GroovyCategorySupport.hasCategoryInCurrentThread()) return getMethodWithoutCaching(sender, site.getName (), params, false); final MetaMethodIndex.Entry e = metaMethodIndex.getMethods(sender, site.getName()); if (e == null) { return null; } MetaMethodIndex.CacheEntry cacheEntry; final Object methods = e.methods; if (methods == null) return null; cacheEntry = e.cachedMethod; if (cacheEntry != null && (sameClasses(cacheEntry.params, params, methods instanceof MetaMethod))) { return cacheEntry.method; } cacheEntry = new MetaMethodIndex.CacheEntry (); cacheEntry.params = params; cacheEntry.method = (MetaMethod) chooseMethod(e.name, methods, params); e.cachedMethod = cacheEntry; return cacheEntry.method; } private MetaMethod getSuperMethodWithCaching(Object[] arguments, MetaMethodIndex.Entry e) { MetaMethodIndex.CacheEntry cacheEntry; if (e.methodsForSuper == null) return null; cacheEntry = e.cachedMethodForSuper; if (cacheEntry != null && MetaClassHelper.sameClasses(cacheEntry.params, arguments, e.methodsForSuper instanceof MetaMethod)) { MetaMethod method = cacheEntry.method; if (method!=null) return method; } cacheEntry = new MetaMethodIndex.CacheEntry (); final Class[] classes = MetaClassHelper.convertToTypeArray(arguments); cacheEntry.params = classes; cacheEntry.method = (MetaMethod) chooseMethod(e.name, e.methodsForSuper, classes); if (cacheEntry.method.isAbstract()) cacheEntry.method = null; e.cachedMethodForSuper = cacheEntry; return cacheEntry.method; } private MetaMethod getNormalMethodWithCaching(Object[] arguments, MetaMethodIndex.Entry e) { MetaMethodIndex.CacheEntry cacheEntry; final Object methods = e.methods; if (methods == null) return null; cacheEntry = e.cachedMethod; if (cacheEntry != null && MetaClassHelper.sameClasses(cacheEntry.params, arguments, methods instanceof MetaMethod)) { MetaMethod method = cacheEntry.method; if (method!=null) return method; } cacheEntry = new MetaMethodIndex.CacheEntry (); final Class[] classes = MetaClassHelper.convertToTypeArray(arguments); cacheEntry.params = classes; cacheEntry.method = (MetaMethod) chooseMethod(e.name, methods, classes); e.cachedMethod = cacheEntry; return cacheEntry.method; } public Constructor retrieveConstructor(Class[] arguments) { CachedConstructor constructor = (CachedConstructor) chooseMethod("<init>", constructors, arguments); if (constructor != null) { return constructor.cachedConstructor; } constructor = (CachedConstructor) chooseMethod("<init>", constructors, arguments); if (constructor != null) { return constructor.cachedConstructor; } return null; } public MetaMethod retrieveStaticMethod(String methodName, Object[] arguments) { final MetaMethodIndex.Entry e = metaMethodIndex.getMethods(theClass, methodName); MetaMethodIndex.CacheEntry cacheEntry; if (e != null) { cacheEntry = e.cachedStaticMethod; if (cacheEntry != null && MetaClassHelper.sameClasses(cacheEntry.params, arguments, e.staticMethods instanceof MetaMethod)) { return cacheEntry.method; } cacheEntry = new MetaMethodIndex.CacheEntry (); final Class[] classes = MetaClassHelper.convertToTypeArray(arguments); cacheEntry.params = classes; cacheEntry.method = pickStaticMethod(methodName, classes); e.cachedStaticMethod = cacheEntry; return cacheEntry.method; } else return pickStaticMethod(methodName, MetaClassHelper.convertToTypeArray(arguments)); } public MetaMethod getMethodWithoutCaching(Class sender, String methodName, Class[] arguments, boolean isCallToSuper) { MetaMethod method = null; Object methods = getMethods(sender, methodName, isCallToSuper); if (methods != null) { method = (MetaMethod) chooseMethod(methodName, methods, arguments); } return method; } public Object invokeStaticMethod(Object object, String methodName, Object[] arguments) { checkInitalised(); final Class sender = object instanceof Class ? (Class) object : object.getClass(); if (sender != theClass) { MetaClass mc = registry.getMetaClass(sender); return mc.invokeStaticMethod(sender, methodName, arguments); } if (sender == Class.class) { return invokeMethod(object, methodName, arguments); } if (arguments == null) arguments = EMPTY_ARGUMENTS; // Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); MetaMethod method = retrieveStaticMethod(methodName, arguments); // let's try use the cache to find the method if (method != null) { MetaClassHelper.unwrap(arguments); return method.doMethodInvoke(object, arguments); } Object prop = null; try { prop = getProperty(theClass, theClass, methodName, false, false); } catch (MissingPropertyException mpe) { // ignore } if (prop instanceof Closure) { return invokeStaticClosureProperty(arguments, prop); } Object[] originalArguments = (Object[]) arguments.clone(); MetaClassHelper.unwrap(arguments); Class superClass = sender.getSuperclass(); Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); while (superClass != Object.class && superClass != null) { MetaClass mc = registry.getMetaClass(superClass); method = mc.getStaticMetaMethod(methodName, argClasses); if (method != null) return method.doMethodInvoke(object, arguments); try { prop = mc.getProperty(superClass, superClass, methodName, false, false); } catch (MissingPropertyException mpe) { // ignore } if (prop instanceof Closure) { return invokeStaticClosureProperty(originalArguments, prop); } superClass = superClass.getSuperclass(); } if (prop != null) { MetaClass propMC = registry.getMetaClass(prop.getClass()); return propMC.invokeMethod(prop, CLOSURE_CALL_METHOD, arguments); } return invokeStaticMissingMethod(sender, methodName, arguments); } private Object invokeStaticClosureProperty(Object[] originalArguments, Object prop) { Closure closure = (Closure) prop; MetaClass delegateMetaClass = closure.getMetaClass(); return delegateMetaClass.invokeMethod(closure.getClass(), closure, CLOSURE_DO_CALL_METHOD, originalArguments, false, false); } private Object invokeStaticMissingMethod(Class sender, String methodName, Object[] arguments) { MetaMethod metaMethod = getStaticMetaMethod(STATIC_METHOD_MISSING, METHOD_MISSING_ARGS); if (metaMethod != null) { return metaMethod.invoke(sender, new Object[]{methodName, arguments}); } throw new MissingMethodException(methodName, sender, arguments, true); } private MetaMethod pickStaticMethod(String methodName, Class[] arguments) { MetaMethod method = null; MethodSelectionException mse = null; Object methods = getStaticMethods(theClass, methodName); if (!(methods instanceof FastArray) || !((FastArray)methods).isEmpty()) { try { method = (MetaMethod) chooseMethod(methodName, methods, arguments); } catch(MethodSelectionException msex) { mse = msex; } } if (method == null && theClass != Class.class) { MetaClass classMetaClass = registry.getMetaClass(Class.class); method = classMetaClass.pickMethod(methodName, arguments); } if (method == null) { method = (MetaMethod) chooseMethod(methodName, methods, MetaClassHelper.convertToTypeArray(arguments)); } if (method == null && mse != null) { throw mse; } else { return method; } } /** * Warning, this method will be removed * * @deprecated use invokeConstructor instead */ public Object invokeConstructorAt(Class at, Object[] arguments) { return invokeConstructor(arguments); } public Object invokeConstructor(Object[] arguments) { return invokeConstructor(theClass, arguments); } public int selectConstructorAndTransformArguments(int numberOfConstructors, Object[] arguments) { //TODO: that is just a quick prototype, not the real thing! if (numberOfConstructors != constructors.size()) { throw new IncompatibleClassChangeError("the number of constructors during runtime and compile time for " + this.theClass.getName() + " do not match. Expected " + numberOfConstructors + " but got " + constructors.size()); } if (arguments == null) arguments = EMPTY_ARGUMENTS; Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); MetaClassHelper.unwrap(arguments); CachedConstructor constructor = (CachedConstructor) chooseMethod("<init>", constructors, argClasses); if (constructor == null) { constructor = (CachedConstructor) chooseMethod("<init>", constructors, argClasses); } if (constructor == null) { throw new GroovyRuntimeException( "Could not find matching constructor for: " + theClass.getName() + "(" + InvokerHelper.toTypeString(arguments) + ")"); } List l = new ArrayList(constructors.toList()); Comparator comp = new Comparator() { public int compare(Object arg0, Object arg1) { CachedConstructor c0 = (CachedConstructor) arg0; CachedConstructor c1 = (CachedConstructor) arg1; String descriptor0 = BytecodeHelper.getMethodDescriptor(Void.TYPE, c0.getNativeParameterTypes()); String descriptor1 = BytecodeHelper.getMethodDescriptor(Void.TYPE, c1.getNativeParameterTypes()); return descriptor0.compareTo(descriptor1); } }; Collections.sort(l, comp); int found = -1; for (int i = 0; i < l.size(); i++) { if (l.get(i) != constructor) continue; found = i; break; } // NOTE: must be changed to "1 |" if constructor was vargs return 0 | (found << 8); } /** * checks if the initialisation of the class id complete. * This method should be called as a form of assert, it is no * way to test if there is still initialisation work to be done. * Such logic must be implemented in a different way. * * @throws IllegalStateException if the initialisation is incomplete yet */ protected void checkInitalised() { if (!isInitialized()) throw new IllegalStateException( "initialize must be called for meta " + "class of " + theClass + "(" + this.getClass() + ") " + "to complete initialisation process " + "before any invocation or field/property " + "access can be done"); } private Object invokeConstructor(Class at, Object[] arguments) { checkInitalised(); if (arguments == null) arguments = EMPTY_ARGUMENTS; Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); MetaClassHelper.unwrap(arguments); CachedConstructor constructor = (CachedConstructor) chooseMethod("<init>", constructors, argClasses); if (constructor != null) { return constructor.doConstructorInvoke(arguments); } if (arguments.length == 1) { Object firstArgument = arguments[0]; if (firstArgument instanceof Map) { constructor = (CachedConstructor) chooseMethod("<init>", constructors, MetaClassHelper.EMPTY_TYPE_ARRAY); if (constructor != null) { Object bean = constructor.doConstructorInvoke(MetaClassHelper.EMPTY_ARRAY); setProperties(bean, ((Map) firstArgument)); return bean; } } } throw new GroovyRuntimeException( "Could not find matching constructor for: " + theClass.getName() + "(" + InvokerHelper.toTypeString(arguments) + ")"); } /** * Sets a number of bean properties from the given Map where the keys are * the String names of properties and the values are the values of the * properties to set */ public void setProperties(Object bean, Map map) { checkInitalised(); for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) { Map.Entry entry = (Map.Entry) iter.next(); String key = entry.getKey().toString(); Object value = entry.getValue(); setProperty(bean, key, value); } } /** * @return the given property's value on the object */ public Object getProperty(Class sender, Object object, String name, boolean useSuper, boolean fromInsideClass) { //---------------------------------------------------------------------- // handling of static //---------------------------------------------------------------------- boolean isStatic = theClass != Class.class && object instanceof Class; if (isStatic && object != theClass) { MetaClass mc = registry.getMetaClass((Class) object); return mc.getProperty(sender, object, name, useSuper, false); } checkInitalised(); //---------------------------------------------------------------------- // turn getProperty on a Map to get on the Map itself //---------------------------------------------------------------------- if (!isStatic && this.isMap) { return ((Map) object).get(name); } MetaMethod method = null; Object[] arguments = EMPTY_ARGUMENTS; //---------------------------------------------------------------------- // getter //---------------------------------------------------------------------- MetaProperty mp = getMetaProperty(sender, name, useSuper, isStatic); if (mp != null) { if (mp instanceof MetaBeanProperty) { MetaBeanProperty mbp = (MetaBeanProperty) mp; method = mbp.getGetter(); mp = mbp.getField(); } } // check for a category method named like a getter if (!useSuper && !isStatic && GroovyCategorySupport.hasCategoryInCurrentThread()) { String getterName = GroovyCategorySupport.getPropertyCategoryGetterName(name); if (getterName != null) { MetaMethod categoryMethod = getCategoryMethodGetter(sender, getterName, false); if (categoryMethod != null) method = categoryMethod; } } //---------------------------------------------------------------------- // field //---------------------------------------------------------------------- if (method == null && mp != null) { try { return mp.getProperty(object); } catch (IllegalArgumentException e) { // can't access the field directly but there may be a getter mp = null; } } //---------------------------------------------------------------------- // generic get method //---------------------------------------------------------------------- // check for a generic get method provided through a category if (method == null && !useSuper && !isStatic && GroovyCategorySupport.hasCategoryInCurrentThread()) { method = getCategoryMethodGetter(sender, "get", true); if (method != null) arguments = new Object[]{name}; } // the generic method is valid, if available (!=null), if static or // if it is not static and we do no static access if (method == null && genericGetMethod != null && !(!genericGetMethod.isStatic() && isStatic)) { arguments = new Object[]{name}; method = genericGetMethod; } //---------------------------------------------------------------------- // special cases //---------------------------------------------------------------------- if (method == null) { /** todo these special cases should be special MetaClasses maybe */ if (theClass != Class.class && object instanceof Class) { MetaClass mc = registry.getMetaClass(Class.class); return mc.getProperty(Class.class, object, name, useSuper, false); } else if (object instanceof Collection) { return DefaultGroovyMethods.getAt((Collection) object, name); } else if (object instanceof Object[]) { return DefaultGroovyMethods.getAt(Arrays.asList((Object[]) object), name); } else { MetaMethod addListenerMethod = (MetaMethod) listeners.get(name); if (addListenerMethod != null) { //TODO: one day we could try return the previously registered Closure listener for easy removal return null; } } } else { //---------------------------------------------------------------------- // executing the getter method //---------------------------------------------------------------------- return method.doMethodInvoke(object, arguments); } //---------------------------------------------------------------------- // error due to missing method/field //---------------------------------------------------------------------- if (isStatic || object instanceof Class) return invokeStaticMissingProperty(object, name, null, true); else return invokeMissingProperty(object, name, null, true); } public MetaProperty getEffectiveGetMetaProperty(final Class sender, final Object object, String name, final boolean useSuper) { //---------------------------------------------------------------------- // handling of static //---------------------------------------------------------------------- boolean isStatic = theClass != Class.class && object instanceof Class; if (isStatic && object != theClass) { return new MetaProperty(name, Object.class) { final MetaClass mc = registry.getMetaClass((Class) object); public Object getProperty(Object object) { return mc.getProperty(sender, object, name, useSuper,false); } public void setProperty(Object object, Object newValue) { throw new UnsupportedOperationException(); } }; } checkInitalised(); //---------------------------------------------------------------------- // turn getProperty on a Map to get on the Map itself //---------------------------------------------------------------------- if (!isStatic && this.isMap) { return new MetaProperty(name, Object.class) { public Object getProperty(Object object) { return ((Map) object).get(name); } public void setProperty(Object object, Object newValue) { throw new UnsupportedOperationException(); } }; } MetaMethod method = null; //---------------------------------------------------------------------- // getter //---------------------------------------------------------------------- MetaProperty mp = getMetaProperty(sender, name, useSuper, isStatic); if (mp != null) { if (mp instanceof MetaBeanProperty) { MetaBeanProperty mbp = (MetaBeanProperty) mp; method = mbp.getGetter(); mp = mbp.getField(); } } // check for a category method named like a getter if (!useSuper && !isStatic && GroovyCategorySupport.hasCategoryInCurrentThread()) { String getterName = GroovyCategorySupport.getPropertyCategoryGetterName(name); if (getterName != null) { MetaMethod categoryMethod = getCategoryMethodGetter(sender, getterName, false); if (categoryMethod != null) method = categoryMethod; } } //---------------------------------------------------------------------- // field //---------------------------------------------------------------------- if (method != null) return new GetBeanMethodMetaProperty(name, method); if (mp != null) { return mp; // try { // return mp.getProperty(object); // } catch (IllegalArgumentException e) { // // can't access the field directly but there may be a getter // mp = null; // } } //---------------------------------------------------------------------- // generic get method //---------------------------------------------------------------------- // check for a generic get method provided through a category if (!useSuper && !isStatic && GroovyCategorySupport.hasCategoryInCurrentThread()) { method = getCategoryMethodGetter(sender, "get", true); if (method != null) return new GetMethodMetaProperty(name, method); } // the generic method is valid, if available (!=null), if static or // if it is not static and we do no static access if (genericGetMethod != null && !(!genericGetMethod.isStatic() && isStatic)) { method = genericGetMethod; if (method != null) return new GetMethodMetaProperty(name, method); } //---------------------------------------------------------------------- // special cases //---------------------------------------------------------------------- /** todo these special cases should be special MetaClasses maybe */ if (theClass != Class.class && object instanceof Class) { return new MetaProperty(name, Object.class) { public Object getProperty(Object object) { MetaClass mc = registry.getMetaClass(Class.class); return mc.getProperty(Class.class, object, name, useSuper, false); } public void setProperty(Object object, Object newValue) { throw new UnsupportedOperationException(); } }; } else if (object instanceof Collection) { return new MetaProperty(name, Object.class) { public Object getProperty(Object object) { return DefaultGroovyMethods.getAt((Collection) object, name); } public void setProperty(Object object, Object newValue) { throw new UnsupportedOperationException(); } }; } else if (object instanceof Object[]) { return new MetaProperty(name, Object.class) { public Object getProperty(Object object) { return DefaultGroovyMethods.getAt(Arrays.asList((Object[]) object), name); } public void setProperty(Object object, Object newValue) { throw new UnsupportedOperationException(); } }; } else { MetaMethod addListenerMethod = (MetaMethod) listeners.get(name); if (addListenerMethod != null) { //TODO: one day we could try return the previously registered Closure listener for easy removal return new MetaProperty(name, Object.class) { public Object getProperty(Object object) { return null; } public void setProperty(Object object, Object newValue) { throw new UnsupportedOperationException(); } }; } } //---------------------------------------------------------------------- // error due to missing method/field //---------------------------------------------------------------------- if (isStatic || object instanceof Class) return new MetaProperty(name, Object.class) { public Object getProperty(Object object) { return invokeStaticMissingProperty(object, name, null, true); } public void setProperty(Object object, Object newValue) { throw new UnsupportedOperationException(); } }; else return new MetaProperty(name, Object.class) { public Object getProperty(Object object) { return invokeMissingProperty(object, name, null, true); } public void setProperty(Object object, Object newValue) { throw new UnsupportedOperationException(); } }; } private MetaMethod getCategoryMethodGetter(Class sender, String name, boolean useLongVersion) { List possibleGenericMethods = GroovyCategorySupport.getCategoryMethods(name); if (possibleGenericMethods != null) { for (Iterator iter = possibleGenericMethods.iterator(); iter.hasNext();) { MetaMethod mmethod = (MetaMethod) iter.next(); if (!mmethod.getDeclaringClass().getTheClass().isAssignableFrom(sender)) continue; CachedClass[] paramTypes = mmethod.getParameterTypes(); if (useLongVersion) { if (paramTypes.length == 1 && paramTypes[0].getTheClass() == String.class) { return mmethod; } } else { if (paramTypes.length == 0) return mmethod; } } } return null; } private MetaMethod getCategoryMethodSetter(Class sender, String name, boolean useLongVersion) { List possibleGenericMethods = GroovyCategorySupport.getCategoryMethods(name); if (possibleGenericMethods != null) { for (Iterator iter = possibleGenericMethods.iterator(); iter.hasNext();) { MetaMethod mmethod = (MetaMethod) iter.next(); if (!mmethod.getDeclaringClass().getTheClass().isAssignableFrom(sender)) continue; CachedClass[] paramTypes = mmethod.getParameterTypes(); if (useLongVersion) { if (paramTypes.length == 2 && paramTypes[0].getTheClass() == String.class) { return mmethod; } } else { if (paramTypes.length == 1) return mmethod; } } } return null; } /** * Get all the properties defined for this type * * @return a list of MetaProperty objects */ public List<MetaProperty> getProperties() { checkInitalised(); SingleKeyHashMap propertyMap = classPropertyIndex.getNullable(theCachedClass); // simply return the values of the metaproperty map as a List List ret = new ArrayList(propertyMap.size()); for (ComplexKeyHashMap.EntryIterator iter = propertyMap.getEntrySetIterator(); iter.hasNext();) { MetaProperty element = (MetaProperty) ((SingleKeyHashMap.Entry) iter.next()).value; if (element instanceof CachedField) continue; // filter out DGM beans if (element instanceof MetaBeanProperty) { MetaBeanProperty mp = (MetaBeanProperty) element; boolean setter = true; boolean getter = true; if (mp.getGetter() == null || mp.getGetter() instanceof GeneratedMetaMethod || mp.getGetter() instanceof NewInstanceMetaMethod) { getter = false; } if (mp.getSetter() == null || mp.getSetter() instanceof GeneratedMetaMethod || mp.getSetter() instanceof NewInstanceMetaMethod) { setter = false; } if (!setter && !getter) continue; // TODO: I (ait) don't know why these strange tricks needed and comment following as it effects some Grails tests // if (!setter && mp.getSetter() != null) { // element = new MetaBeanProperty(mp.getName(), mp.getType(), mp.getGetter(), null); // } // if (!getter && mp.getGetter() != null) { // element = new MetaBeanProperty(mp.getName(), mp.getType(), null, mp.getSetter()); // } } ret.add(element); } return ret; } private MetaMethod findPropertyMethod(Object methodOrList, boolean isGetter, boolean booleanGetter) { if (methodOrList == null) return null; Object ret = null; if (methodOrList instanceof MetaMethod) { MetaMethod element = (MetaMethod)methodOrList; if (!isGetter && //(element.getReturnType() == Void.class || element.getReturnType() == Void.TYPE) && element.getParameterTypes().length == 1) { ret = addElementToList(ret, element); } if (isGetter && !(element.getReturnType() == Void.class || element.getReturnType() == Void.TYPE) && (!booleanGetter || element.getReturnType() == Boolean.class || element.getReturnType() == Boolean.TYPE) && element.getParameterTypes().length == 0) { ret = addElementToList(ret, element); } } else { FastArray methods = (FastArray) methodOrList; final int len = methods.size(); final Object[] data = methods.getArray(); for (int i = 0; i != len; ++i) { MetaMethod element = (MetaMethod) data[i]; if (!isGetter && //(element.getReturnType() == Void.class || element.getReturnType() == Void.TYPE) && element.getParameterTypes().length == 1) { ret = addElementToList(ret, element); } if (isGetter && !(element.getReturnType() == Void.class || element.getReturnType() == Void.TYPE) && element.getParameterTypes().length == 0) { ret = addElementToList(ret, element); } } } if (ret == null) return null; if (ret instanceof MetaMethod) return (MetaMethod) ret; // we found multiple matching methods // this is a problem, because we can use only one // if it is a getter, then use the most general return // type to decide which method to use. If it is a setter // we use the type of the first parameter MetaMethod method = null; int distance = -1; for (Iterator iter = ((List) ret).iterator(); iter.hasNext();) { MetaMethod element = (MetaMethod) iter.next(); Class c; if (isGetter) { c = element.getReturnType(); } else { c = element.getParameterTypes()[0].getTheClass(); } int localDistance = distanceToObject(c); //TODO: maybe implement the case localDistance==distance if (distance == -1 || distance > localDistance) { distance = localDistance; method = element; } } return method; } private Object addElementToList(Object ret, MetaMethod element) { if (ret == null) ret = element; else if (ret instanceof List) ((List) ret).add(element); else { List list = new LinkedList(); list.add(ret); list.add(element); ret = list; } return ret; } private static int distanceToObject(Class c) { int count; for (count = 0; c != null; count++) { c = c.getSuperclass(); } return count; } /** * This will build up the property map (Map of MetaProperty objects, keyed on * property name). */ private void setupProperties(PropertyDescriptor[] propertyDescriptors) { if (theCachedClass.isInterface) { LinkedList<CachedClass> superClasses = new LinkedList<CachedClass>(); superClasses.add(ReflectionCache.OBJECT_CLASS); Set interfaces = theCachedClass.getInterfaces(); classPropertyIndexForSuper = classPropertyIndex; final SingleKeyHashMap cPI = classPropertyIndex.getNotNull(theCachedClass); for (Iterator interfaceIter = interfaces.iterator(); interfaceIter.hasNext();) { CachedClass iclass = (CachedClass) interfaceIter.next(); SingleKeyHashMap iPropertyIndex = cPI; addFields(iclass, iPropertyIndex); classPropertyIndex.put(iclass, iPropertyIndex); } classPropertyIndex.put(ReflectionCache.OBJECT_CLASS, cPI); applyPropertyDescriptors(propertyDescriptors); applyStrayPropertyMethods(superClasses, classPropertyIndex, true); makeStaticPropertyIndex(); } else { LinkedList<CachedClass> superClasses = getSuperClasses(); Set interfaces = theCachedClass.getInterfaces(); // if this an Array, then add the special read-only "length" property if (theCachedClass.isArray) { SingleKeyHashMap map = new SingleKeyHashMap(); map.put("length", arrayLengthProperty); classPropertyIndex.put(theCachedClass, map); } inheritStaticInterfaceFields(superClasses, interfaces); inheritFields(superClasses); applyPropertyDescriptors(propertyDescriptors); applyStrayPropertyMethods(superClasses, classPropertyIndex, true); applyStrayPropertyMethods(superClasses, classPropertyIndexForSuper, false); copyClassPropertyIndexForSuper(classPropertyIndexForSuper); makeStaticPropertyIndex(); } } private void makeStaticPropertyIndex() { SingleKeyHashMap propertyMap = classPropertyIndex.getNotNull(theCachedClass); for (ComplexKeyHashMap.EntryIterator iter = propertyMap.getEntrySetIterator(); iter.hasNext();) { SingleKeyHashMap.Entry entry = ((SingleKeyHashMap.Entry) iter.next()); MetaProperty mp = (MetaProperty) entry.getValue(); if (mp instanceof CachedField) { CachedField mfp = (CachedField) mp; if (!mfp.isStatic()) continue; } else if (mp instanceof MetaBeanProperty) { MetaProperty result = establishStaticMetaProperty(mp); if (result == null) continue; else { mp = result; } } else { continue; // ignore all other types } staticPropertyIndex.put(entry.getKey(), mp); } } private MetaProperty establishStaticMetaProperty(MetaProperty mp) { MetaBeanProperty mbp = (MetaBeanProperty) mp; MetaProperty result = null; final MetaMethod getterMethod = mbp.getGetter(); final MetaMethod setterMethod = mbp.getSetter(); final CachedField metaField = mbp.getField(); boolean getter = getterMethod == null || getterMethod.isStatic(); boolean setter = setterMethod == null || setterMethod.isStatic(); boolean field = metaField == null || metaField.isStatic(); if (!getter && !setter && !field) { return result; } else { final String propertyName = mbp.getName(); final Class propertyType = mbp.getType(); if (setter && getter) { if (field) { result = mbp; // nothing to do } else { result = new MetaBeanProperty(propertyName, propertyType, getterMethod, setterMethod); } } else if (getter && !setter) { if (getterMethod == null) { result = metaField; } else { MetaBeanProperty newmp = new MetaBeanProperty(propertyName, propertyType, getterMethod, null); if (field) newmp.setField(metaField); result = newmp; } } else if (setter && !getter) { if (setterMethod == null) { result = metaField; } else { MetaBeanProperty newmp = new MetaBeanProperty(propertyName, propertyType, null, setterMethod); if (field) newmp.setField(metaField); result = newmp; } } else result = metaField; } return result; } private void copyClassPropertyIndexForSuper(Index dest) { for (ComplexKeyHashMap.EntryIterator iter = classPropertyIndex.getEntrySetIterator(); iter.hasNext();) { SingleKeyHashMap.Entry entry = (SingleKeyHashMap.Entry) iter.next(); SingleKeyHashMap newVal = new SingleKeyHashMap(); dest.put((CachedClass) entry.getKey(), newVal); } } private void inheritStaticInterfaceFields(LinkedList superClasses, Set interfaces) { for (Iterator interfaceIter = interfaces.iterator(); interfaceIter.hasNext();) { CachedClass iclass = (CachedClass) interfaceIter.next(); SingleKeyHashMap iPropertyIndex = classPropertyIndex.getNotNull(iclass); addFields(iclass, iPropertyIndex); for (Iterator classIter = superClasses.iterator(); classIter.hasNext();) { CachedClass sclass = (CachedClass) classIter.next(); if (!iclass.getTheClass().isAssignableFrom(sclass.getTheClass())) continue; SingleKeyHashMap sPropertyIndex = classPropertyIndex.getNotNull(sclass); copyNonPrivateFields(iPropertyIndex, sPropertyIndex); } } } private void inheritFields(LinkedList<CachedClass> superClasses) { SingleKeyHashMap last = null; for (CachedClass klass : superClasses) { SingleKeyHashMap propertyIndex = classPropertyIndex.getNotNull(klass); if (last != null) { copyNonPrivateFields(last, propertyIndex); } last = propertyIndex; addFields(klass, propertyIndex); } } private void addFields(final CachedClass klass, SingleKeyHashMap propertyIndex) { CachedField[] fields = klass.getFields(); for (CachedField field : fields) { propertyIndex.put(field.getName(), field); } } private void copyNonPrivateFields(SingleKeyHashMap from, SingleKeyHashMap to) { for (ComplexKeyHashMap.EntryIterator iter = from.getEntrySetIterator(); iter.hasNext();) { SingleKeyHashMap.Entry entry = (SingleKeyHashMap.Entry) iter.next(); CachedField mfp = (CachedField) entry.getValue(); if (!Modifier.isPublic(mfp.getModifiers()) && !Modifier.isProtected(mfp.getModifiers())) continue; to.put(entry.getKey(), mfp); } } private void applyStrayPropertyMethods(LinkedList<CachedClass> superClasses, Index classPropertyIndex, boolean isThis) { // now look for any stray getters that may be used to define a property for (CachedClass klass : superClasses) { MetaMethodIndex.Header header = metaMethodIndex.getHeader(klass.getTheClass()); SingleKeyHashMap propertyIndex = classPropertyIndex.getNotNull(klass); for (MetaMethodIndex.Entry e = header.head; e != null; e = e.nextClassEntry) { String methodName = e.name; // name too short? if (methodName.length() < 3 || (!methodName.startsWith("is") && methodName.length() < 4)) continue; // possible getter/setter? boolean isGetter = methodName.startsWith("get") || methodName.startsWith("is"); boolean isBooleanGetter = methodName.startsWith("is"); boolean isSetter = methodName.startsWith("set"); if (!isGetter && !isSetter) continue; MetaMethod propertyMethod = findPropertyMethod(isThis ? e.methods : e.methodsForSuper, isGetter, isBooleanGetter); if (propertyMethod == null) continue; String propName = getPropName(methodName); createMetaBeanProperty(propertyIndex, propName, isGetter, propertyMethod); } } } private static final HashMap<String, String> propNames = new HashMap<String, String>(1024); private String getPropName(String methodName) { String name = propNames.get(methodName); if (name != null) return name; synchronized (propNames) { // assume "is" or "[gs]et" String stripped = methodName.startsWith("is") ? methodName.substring(2) : methodName.substring(3); String propName = java.beans.Introspector.decapitalize(stripped); propNames.put(methodName, propName); return propName; } } private void createMetaBeanProperty(SingleKeyHashMap propertyIndex, String propName, boolean isGetter, MetaMethod propertyMethod) { // is this property already accounted for? MetaProperty mp = (MetaProperty) propertyIndex.get(propName); if (mp == null) { if (isGetter) { mp = new MetaBeanProperty(propName, propertyMethod.getReturnType(), propertyMethod, null); } else { //isSetter mp = new MetaBeanProperty(propName, propertyMethod.getParameterTypes()[0].getTheClass(), null, propertyMethod); } } else { MetaBeanProperty mbp; CachedField mfp; if (mp instanceof MetaBeanProperty) { mbp = (MetaBeanProperty) mp; mfp = mbp.getField(); } else if (mp instanceof CachedField) { mfp = (CachedField) mp; mbp = new MetaBeanProperty(propName, mfp.getType(), null, null); } else { throw new GroovyBugError("unknown MetaProperty class used. Class is " + mp.getClass()); } // we may have already found one for this name if (isGetter && mbp.getGetter() == null) { mbp.setGetter(propertyMethod); } else if (!isGetter && mbp.getSetter() == null) { mbp.setSetter(propertyMethod); } mbp.setField(mfp); mp = mbp; } propertyIndex.put(propName, mp); } protected void applyPropertyDescriptors(PropertyDescriptor[] propertyDescriptors) { // now iterate over the map of property descriptors and generate // MetaBeanProperty objects for (PropertyDescriptor pd : propertyDescriptors) { // skip if the property type is unknown (this seems to be the case if the // property descriptor is based on a setX() method that has two parameters, // which is not a valid property) if (pd.getPropertyType() == null) continue; // get the getter method Method method = pd.getReadMethod(); MetaMethod getter; if (method != null) { CachedMethod cachedGetter = CachedMethod.find(method); getter = cachedGetter == null ? null : findMethod(cachedGetter); } else { getter = null; } // get the setter method MetaMethod setter; method = pd.getWriteMethod(); if (method != null) { CachedMethod cachedSetter = CachedMethod.find(method); setter = cachedSetter == null ? null : findMethod(cachedSetter); } else { setter = null; } // now create the MetaProperty object MetaBeanProperty mp = new MetaBeanProperty(pd.getName(), pd.getPropertyType(), getter, setter); addMetaBeanProperty(mp); } } /** * Adds a new MetaBeanProperty to this MetaClass * * @param mp The MetaBeanProperty */ public void addMetaBeanProperty(MetaBeanProperty mp) { MetaProperty staticProperty = establishStaticMetaProperty(mp); if (staticProperty != null) { staticPropertyIndex.put(mp.getName(), mp); } else { SingleKeyHashMap propertyMap = classPropertyIndex.getNotNull(theCachedClass); //keep field CachedField field; MetaProperty old = (MetaProperty) propertyMap.get(mp.getName()); if (old != null) { if (old instanceof MetaBeanProperty) { field = ((MetaBeanProperty) old).getField(); } else { field = (CachedField) old; } mp.setField(field); } // put it in the list // this will overwrite a possible field property propertyMap.put(mp.getName(), mp); } } /** * Sets the property value on an object */ public void setProperty(Class sender, Object object, String name, Object newValue, boolean useSuper, boolean fromInsideClass) { checkInitalised(); //---------------------------------------------------------------------- // handling of static //---------------------------------------------------------------------- boolean isStatic = theClass != Class.class && object instanceof Class; if (isStatic && object != theClass) { MetaClass mc = registry.getMetaClass((Class) object); mc.getProperty(sender, object, name, useSuper, fromInsideClass); return; } //---------------------------------------------------------------------- // Unwrap wrapped values fo now - the new MOP will handle them properly //---------------------------------------------------------------------- if (newValue instanceof Wrapper) newValue = ((Wrapper) newValue).unwrap(); MetaMethod method = null; Object[] arguments = null; //---------------------------------------------------------------------- // setter //---------------------------------------------------------------------- MetaProperty mp = getMetaProperty(sender, name, useSuper, isStatic); MetaProperty field = null; if (mp != null) { if (mp instanceof MetaBeanProperty) { MetaBeanProperty mbp = (MetaBeanProperty) mp; method = mbp.getSetter(); MetaProperty f = mbp.getField(); if (method != null || (f != null && !Modifier.isFinal(f.getModifiers()))) { arguments = new Object[]{newValue}; field = f; } } else { field = mp; } } // check for a category method named like a setter if (!useSuper && !isStatic && GroovyCategorySupport.hasCategoryInCurrentThread() && name.length() > 0) { String getterName = GroovyCategorySupport.getPropertyCategorySetterName(name); if (getterName != null) { MetaMethod categoryMethod = getCategoryMethodSetter(sender, getterName, false); if (categoryMethod != null) { method = categoryMethod; arguments = new Object[]{newValue}; } } } //---------------------------------------------------------------------- // listener method //---------------------------------------------------------------------- boolean ambiguousListener = false; if (method == null) { method = (MetaMethod) listeners.get(name); ambiguousListener = method == AMBIGUOUS_LISTENER_METHOD; if (method != null && !ambiguousListener && newValue instanceof Closure) { // let's create a dynamic proxy Object proxy = Proxy.newProxyInstance( theClass.getClassLoader(), new Class[]{method.getParameterTypes()[0].getTheClass()}, new ConvertedClosure((Closure) newValue, name)); arguments = new Object[]{proxy}; newValue = proxy; } else { method = null; } } //---------------------------------------------------------------------- // field //---------------------------------------------------------------------- if (method == null && field != null) { if (Modifier.isFinal(field.getModifiers())) { throw new ReadOnlyPropertyException(name, theClass); } if(!(this.isMap && isPrivateOrPkgPrivate(field.getModifiers()))) { field.setProperty(object, newValue); return; } } //---------------------------------------------------------------------- // generic set method //---------------------------------------------------------------------- // check for a generic get method provided through a category if (method == null && !useSuper && !isStatic && GroovyCategorySupport.hasCategoryInCurrentThread()) { method = getCategoryMethodSetter(sender, "set", true); if (method != null) arguments = new Object[]{name, newValue}; } // the generic method is valid, if available (!=null), if static or // if it is not static and we do no static access if (method == null && genericSetMethod != null && !(!genericSetMethod.isStatic() && isStatic)) { arguments = new Object[]{name, newValue}; method = genericSetMethod; } //---------------------------------------------------------------------- // executing the getter method //---------------------------------------------------------------------- if (method != null) { if (arguments.length == 1) { newValue = DefaultTypeTransformation.castToType( newValue, method.getParameterTypes()[0].getTheClass()); arguments[0] = newValue; } else { newValue = DefaultTypeTransformation.castToType( newValue, method.getParameterTypes()[1].getTheClass()); arguments[1] = newValue; } method.doMethodInvoke(object, arguments); return; } //---------------------------------------------------------------------- // turn setProperty on a Map to put on the Map itself //---------------------------------------------------------------------- if (!isStatic && this.isMap) { ((Map) object).put(name, newValue); return; } //---------------------------------------------------------------------- // error due to missing method/field //---------------------------------------------------------------------- if (ambiguousListener) { throw new GroovyRuntimeException("There are multiple listeners for the property " + name + ". Please do not use the bean short form to access this listener."); } if (mp != null) { throw new ReadOnlyPropertyException(name, theClass); } invokeMissingProperty(object, name, newValue, false); } private boolean isPrivateOrPkgPrivate(int mod) { return !Modifier.isProtected(mod) && !Modifier.isPublic(mod); } private MetaProperty getMetaProperty(Class _clazz, String name, boolean useSuper, boolean useStatic) { if (_clazz == theClass) return getMetaProperty(name, useStatic); CachedClass clazz = ReflectionCache.getCachedClass(_clazz); while (true) { SingleKeyHashMap propertyMap; if (useStatic) { propertyMap = staticPropertyIndex; } else if (useSuper) { propertyMap = classPropertyIndexForSuper.getNullable(clazz); } else { propertyMap = classPropertyIndex.getNullable(clazz); } if (propertyMap == null) { if (clazz != theCachedClass) { clazz = theCachedClass; continue; } else { return null; } } return (MetaProperty) propertyMap.get(name); } } private MetaProperty getMetaProperty(String name, boolean useStatic) { CachedClass clazz = theCachedClass; SingleKeyHashMap propertyMap; if (useStatic) { propertyMap = staticPropertyIndex; } else { propertyMap = classPropertyIndex.getNullable(clazz); } if (propertyMap == null) { return null; } return (MetaProperty) propertyMap.get(name); } public Object getAttribute(Class sender, Object receiver, String messageName, boolean useSuper) { return getAttribute(receiver, messageName); } /** * Looks up the given attribute (field) on the given object */ public Object getAttribute(Class sender, Object object, String attribute, boolean useSuper, boolean fromInsideClass) { checkInitalised(); boolean isStatic = theClass != Class.class && object instanceof Class; if (isStatic && object != theClass) { MetaClass mc = registry.getMetaClass((Class) object); return mc.getAttribute(sender, object, attribute, useSuper); } MetaProperty mp = getMetaProperty(sender, attribute, useSuper, isStatic); if (mp != null) { if (mp instanceof MetaBeanProperty) { MetaBeanProperty mbp = (MetaBeanProperty) mp; mp = mbp.getField(); } try { // delegate the get operation to the metaproperty if (mp != null) return mp.getProperty(object); } catch (Exception e) { throw new GroovyRuntimeException("Cannot read field: " + attribute, e); } } throw new MissingFieldException(attribute, theClass); } /** * Sets the given attribute (field) on the given object */ public void setAttribute(Class sender, Object object, String attribute, Object newValue, boolean useSuper, boolean fromInsideClass) { checkInitalised(); boolean isStatic = theClass != Class.class && object instanceof Class; if (isStatic && object != theClass) { MetaClass mc = registry.getMetaClass((Class) object); mc.setAttribute(sender, object, attribute, newValue, useSuper, fromInsideClass); return; } MetaProperty mp = getMetaProperty(sender, attribute, useSuper, isStatic); if (mp != null) { if (mp instanceof MetaBeanProperty) { MetaBeanProperty mbp = (MetaBeanProperty) mp; mp = mbp.getField(); } if (mp != null) { mp.setProperty(object, newValue); return; } } throw new MissingFieldException(attribute, theClass); } public ClassNode getClassNode() { if (classNode == null && GroovyObject.class.isAssignableFrom(theClass)) { // let's try load it from the classpath String groovyFile = theClass.getName(); int idx = groovyFile.indexOf('$'); if (idx > 0) { groovyFile = groovyFile.substring(0, idx); } groovyFile = groovyFile.replace('.', '/') + ".groovy"; //System.out.println("Attempting to load: " + groovyFile); URL url = theClass.getClassLoader().getResource(groovyFile); if (url == null) { url = Thread.currentThread().getContextClassLoader().getResource(groovyFile); } if (url != null) { try { /** * todo there is no CompileUnit in scope so class name * checking won't work but that mostly affects the bytecode * generation rather than viewing the AST */ CompilationUnit.ClassgenCallback search = new CompilationUnit.ClassgenCallback() { public void call(ClassVisitor writer, ClassNode node) { if (node.getName().equals(theClass.getName())) { MetaClassImpl.this.classNode = node; } } }; final ClassLoader parent = theClass.getClassLoader(); GroovyClassLoader gcl = (GroovyClassLoader) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return new GroovyClassLoader(parent); } }); CompilationUnit unit = new CompilationUnit(); unit.setClassgenCallback(search); unit.addSource(url); unit.compile(Phases.CLASS_GENERATION); } catch (Exception e) { throw new GroovyRuntimeException("Exception thrown parsing: " + groovyFile + ". Reason: " + e, e); } } } return classNode; } public String toString() { return super.toString() + "[" + theClass + "]"; } // Implementation methods //------------------------------------------------------------------------- /** * adds a MetaMethod to this class. WARNING: this method will not * do the neccessary steps for multimethod logic and using this * method doesn't mean, that a method added here is replacing another * method from a parent class completely. These steps are usually done * by initialize, which means if you need these steps, you have to add * the method before running initialize the first time. * * @param method the MetaMethod * @see #initialize() */ public void addMetaMethod(MetaMethod method) { if (isInitialized()) { throw new RuntimeException("Already initialized, cannot add new method: " + method); } final CachedClass declaringClass = method.getDeclaringClass(); addMetaMethodToIndex(method, metaMethodIndex.getHeader(declaringClass.getTheClass())); } protected void addMetaMethodToIndex(MetaMethod method, MetaMethodIndex.Header header) { checkIfStdMethod(method); String name = method.getName(); MetaMethodIndex.Entry e = metaMethodIndex.getOrPutMethods(name, header); if (method.isStatic()) { e.staticMethods = metaMethodIndex.addMethodToList(e.staticMethods, method); } e.methods = metaMethodIndex.addMethodToList(e.methods, method); } /** * Checks if the metaMethod is a method from the GroovyObject interface such as setProperty, getProperty and invokeMethod * * @param metaMethod The metaMethod instance * @see GroovyObject */ protected final void checkIfGroovyObjectMethod(MetaMethod metaMethod) { if (metaMethod instanceof ClosureMetaMethod || metaMethod instanceof MixinInstanceMetaMethod) { if(isGetPropertyMethod(metaMethod)) { getPropertyMethod = metaMethod; } else if(isInvokeMethod(metaMethod)) { invokeMethodMethod = metaMethod; } else if(isSetPropertyMethod(metaMethod)) { setPropertyMethod = metaMethod; } } } private boolean isSetPropertyMethod(MetaMethod metaMethod) { return SET_PROPERTY_METHOD.equals(metaMethod.getName()) && metaMethod.getParameterTypes().length == 2; } private boolean isGetPropertyMethod(MetaMethod metaMethod) { return GET_PROPERTY_METHOD.equals(metaMethod.getName()); } private boolean isInvokeMethod(MetaMethod metaMethod) { return INVOKE_METHOD_METHOD.equals(metaMethod.getName()) && metaMethod.getParameterTypes().length == 2; } private void checkIfStdMethod(MetaMethod method) { checkIfGroovyObjectMethod(method); if (isGenericGetMethod(method) && genericGetMethod == null) { genericGetMethod = method; } else if (MetaClassHelper.isGenericSetMethod(method) && genericSetMethod == null) { genericSetMethod = method; } if (method.getName().equals(PROPERTY_MISSING)) { CachedClass[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { propertyMissingGet = method; } } if (propertyMissingSet == null && method.getName().equals(PROPERTY_MISSING)) { CachedClass[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 2) { propertyMissingSet = method; } } if (method.getName().equals(METHOD_MISSING)) { CachedClass[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 2 && parameterTypes[0].getTheClass() == String.class && parameterTypes[1].getTheClass() == Object.class) { methodMissing = method; } } if (theCachedClass.isNumber) { NumberMathModificationInfo.instance.checkIfStdMethod (method); } } protected boolean isInitialized() { return initialized; } /** * return false: add method * null: ignore method * true: replace */ private Boolean getMatchKindForCategory(MetaMethod aMethod, MetaMethod categoryMethod) { CachedClass[] params1 = aMethod.getParameterTypes(); CachedClass[] params2 = categoryMethod.getParameterTypes(); if (params1.length != params2.length) return Boolean.FALSE; for (int i = 0; i < params1.length; i++) { if (params1[i] != params2[i]) return Boolean.FALSE; } Class aMethodClass = aMethod.getDeclaringClass().getTheClass(); Class categoryMethodClass = categoryMethod.getDeclaringClass().getTheClass(); if (aMethodClass==categoryMethodClass) return Boolean.TRUE; boolean match = aMethodClass.isAssignableFrom(categoryMethodClass); if (match) return Boolean.TRUE; return null; } private void filterMatchingMethodForCategory(FastArray list, MetaMethod method) { int len = list.size(); if (len==0) { list.add(method); return; } Object data[] = list.getArray(); for (int j = 0; j != len; ++j) { MetaMethod aMethod = (MetaMethod) data[j]; Boolean match = getMatchKindForCategory(aMethod, method); // true == replace if (match==Boolean.TRUE) { list.set(j, method); return; // null == ignore (we have a better method already) } else if (match==null) { return; } } // the casese true and null for a match are through, the // remaining case is false and that means adding the method // to our list list.add(method); } private int findMatchingMethod(CachedMethod[] data, int from, int to, MetaMethod method) { for (int j = from; j <= to; ++j) { CachedMethod aMethod = data[j]; CachedClass[] params1 = aMethod.getParameterTypes(); CachedClass[] params2 = method.getParameterTypes(); if (params1.length == params2.length) { boolean matches = true; for (int i = 0; i < params1.length; i++) { if (params1[i] != params2[i]) { matches = false; break; } } if (matches) { return j; } } } return -1; } /** * @return the matching method which should be found */ private MetaMethod findMethod(CachedMethod aMethod) { Object methods = getMethods(theClass, aMethod.getName(), false); if (methods instanceof FastArray) { FastArray m = (FastArray) methods; final int len = m.size; final Object data[] = m.getArray(); for (int i = 0; i != len; ++i) { MetaMethod method = (MetaMethod) data[i]; if (method.isMethod(aMethod)) { return method; } } } else { MetaMethod method = (MetaMethod) methods; if (method.getName().equals(aMethod.getName()) // TODO: should be better check for case when only diff in modifiers can be SYNTHETIC flag // && method.getModifiers() == aMethod.getModifiers() && method.getReturnType().equals(aMethod.getReturnType()) && MetaMethod.equal(method.getParameterTypes(), aMethod.getParameterTypes())) { return method; } } //log.warning("Creating reflection based dispatcher for: " + aMethod); synchronized (aMethod.cachedClass) { return aMethod; } } /** * Chooses the correct method to use from a list of methods which match by * name. * * @param methodOrList the possible methods to choose from * @param arguments */ protected Object chooseMethod(String methodName, Object methodOrList, Class[] arguments) { Object method = chooseMethodInternal(methodName, methodOrList, arguments); if (method instanceof GeneratedMetaMethod.Proxy) return ((GeneratedMetaMethod.Proxy)method).proxy (); return method; } private Object chooseMethodInternal(String methodName, Object methodOrList, Class[] arguments) { if (methodOrList instanceof MetaMethod) { if (((ParameterTypes) methodOrList).isValidMethod(arguments)) { return methodOrList; } return null; } FastArray methods = (FastArray) methodOrList; if (methods==null) return null; int methodCount = methods.size(); if (methodCount <= 0) { return null; } else if (methodCount == 1) { Object method = methods.get(0); if (((ParameterTypes) method).isValidMethod(arguments)) { return method; } return null; } Object answer; if (arguments == null || arguments.length == 0) { answer = MetaClassHelper.chooseEmptyMethodParams(methods); } else if (arguments.length == 1 && arguments[0] == null) { answer = MetaClassHelper.chooseMostGeneralMethodWith1NullParam(methods); } else { Object matchingMethods = null; final int len = methods.size; Object data[] = methods.getArray(); for (int i = 0; i != len; ++i) { Object method = data[i]; // making this false helps find matches if (((ParameterTypes) method).isValidMethod(arguments)) { if (matchingMethods == null) matchingMethods = method; else if (matchingMethods instanceof ArrayList) ((ArrayList)matchingMethods).add(method); else { ArrayList arr = new ArrayList(4); arr.add(matchingMethods); arr.add(method); matchingMethods = arr; } } } if (matchingMethods == null) { return null; } else if (!(matchingMethods instanceof ArrayList)) { return matchingMethods; } return chooseMostSpecificParams(methodName, (List) matchingMethods, arguments); } if (answer != null) { return answer; } throw new MethodSelectionException(methodName, methods, 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(); ParameterTypes paramTypes = (ParameterTypes) method; long dist = MetaClassHelper.calculateParameterDistance(arguments, paramTypes); if (dist == 0) return method; if (matches.size() == 0) { 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.size() == 0) { return null; } //more than one matching method found --> ambiguous! 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();) { Class[] types = ((ParameterTypes) iter.next()).getNativeParameterTypes(); msg += "\n\t" + InvokerHelper.toString(types); } throw new GroovyRuntimeException(msg); } private boolean isGenericGetMethod(MetaMethod method) { if (method.getName().equals("get")) { CachedClass[] parameterTypes = method.getParameterTypes(); return parameterTypes.length == 1 && parameterTypes[0].getTheClass() == String.class; } return false; } public synchronized void initialize() { if (!isInitialized()) { fillMethodIndex(); addProperties(); initialized = true; } } private void addProperties() { BeanInfo info; final Class stopClass; // introspect try { if (isBeanDerivative(theClass)) { info = (BeanInfo) AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws IntrospectionException { return Introspector.getBeanInfo(theClass, Introspector.IGNORE_ALL_BEANINFO); } }); } else { info = (BeanInfo) AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws IntrospectionException { return Introspector.getBeanInfo(theClass); } }); } } catch (PrivilegedActionException pae) { throw new GroovyRuntimeException("exception during bean introspection", pae.getException()); } PropertyDescriptor[] descriptors = info.getPropertyDescriptors(); // build up the metaproperties based on the public fields, property descriptors, // and the getters and setters setupProperties(descriptors); EventSetDescriptor[] eventDescriptors = info.getEventSetDescriptors(); for (EventSetDescriptor descriptor : eventDescriptors) { Method[] listenerMethods = descriptor.getListenerMethods(); for (Method listenerMethod : listenerMethods) { final MetaMethod metaMethod = CachedMethod.find(descriptor.getAddListenerMethod()); addToAllMethodsIfPublic(metaMethod); String name = listenerMethod.getName(); if (listeners.containsKey(name)) { listeners.put(name, AMBIGUOUS_LISTENER_METHOD); } else { listeners.put(name, metaMethod); } } } } private boolean isBeanDerivative(Class theClass) { Class next = theClass; while (next != null) { if (Arrays.asList(next.getInterfaces()).contains(BeanInfo.class)) return true; next = next.getSuperclass(); } return false; } private void addToAllMethodsIfPublic(MetaMethod metaMethod) { if (Modifier.isPublic(metaMethod.getModifiers())) allMethods.add(metaMethod); } public List<MetaMethod> getMethods() { return allMethods; } public List<MetaMethod> getMetaMethods() { return new ArrayList<MetaMethod>(newGroovyMethodsSet); } protected void dropStaticMethodCache(String name) { metaMethodIndex.clearCaches(name); } protected void dropMethodCache(String name) { metaMethodIndex.clearCaches(name); } public CallSite createPojoCallSite(CallSite site, Object receiver, Object[] args) { if (!(this instanceof AdaptingMetaClass)) { Class [] params = MetaClassHelper.convertToTypeArray(args); MetaMethod metaMethod = getMethodWithCachingInternal(getTheClass(), site, params); if (metaMethod != null) return PojoMetaMethodSite.createPojoMetaMethodSite(site, this, metaMethod, params, receiver, args); } return new PojoMetaClassSite(site, this); } public CallSite createStaticSite(CallSite site, Object[] args) { if (!(this instanceof AdaptingMetaClass)) { Class [] params = MetaClassHelper.convertToTypeArray(args); MetaMethod metaMethod = retrieveStaticMethod(site.getName(), args); if (metaMethod != null) return StaticMetaMethodSite.createStaticMetaMethodSite(site, this, metaMethod, params, args); } return new StaticMetaClassSite(site, this); } public CallSite createPogoCallSite(CallSite site, Object[] args) { if (!GroovyCategorySupport.hasCategoryInCurrentThread() && !(this instanceof AdaptingMetaClass)) { Class [] params = MetaClassHelper.convertToTypeArray(args); MetaMethod metaMethod = getMethodWithCachingInternal(theClass, site, params); if (metaMethod != null) return PogoMetaMethodSite.createPogoMetaMethodSite(site, this, metaMethod, params, args); } return new PogoMetaClassSite(site, this); } public CallSite createPogoCallCurrentSite(CallSite site, Class sender, Object[] args) { if (!GroovyCategorySupport.hasCategoryInCurrentThread() && !(this instanceof AdaptingMetaClass)) { Class [] params = MetaClassHelper.convertToTypeArray(args); MetaMethod metaMethod = getMethodWithCachingInternal(sender, site, params); if (metaMethod != null) return PogoMetaMethodSite.createPogoMetaMethodSite(site, this, metaMethod, params, args); } return new PogoMetaClassSite(site, this); } public CallSite createConstructorSite(CallSite site, Object[] args) { if (!(this instanceof AdaptingMetaClass)) { Class[] params = MetaClassHelper.convertToTypeArray(args); CachedConstructor constructor = (CachedConstructor) chooseMethod("<init>", constructors, params); if (constructor != null) { return ConstructorSite.createConstructorSite(site, this,constructor,params, args); } else { if (args.length == 1 && args[0] instanceof Map) { constructor = (CachedConstructor) chooseMethod("<init>", constructors, MetaClassHelper.EMPTY_TYPE_ARRAY); if (constructor != null) { return new ConstructorSite.NoParamSite(site, this,constructor,params); } } } } return new MetaClassConstructorSite(site, this); } public ClassInfo getClassInfo() { return theCachedClass.classInfo; } public int getVersion() { return theCachedClass.classInfo.getVersion(); } public void incVersion() { theCachedClass.classInfo.incVersion(); } public MetaMethod[] getAdditionalMetaMethods() { return additionalMetaMethods; } protected MetaBeanProperty findPropertyInClassHierarchy(String propertyName, CachedClass theClass) { MetaBeanProperty property= null; if (theClass == null) return null; final CachedClass superClass = theClass.getCachedSuperClass(); if (superClass == null) return null; MetaClass metaClass = this.registry.getMetaClass(superClass.getTheClass()); if(metaClass instanceof MutableMetaClass) { property = getMetaPropertyFromMutableMetaClass(propertyName,metaClass); if(property == null) { if(superClass != ReflectionCache.OBJECT_CLASS) { property = findPropertyInClassHierarchy(propertyName, superClass); } if(property == null) { final Class[] interfaces = theClass.getTheClass().getInterfaces(); property = searchInterfacesForMetaProperty(propertyName, interfaces); } } } return property; } private MetaBeanProperty searchInterfacesForMetaProperty(String propertyName, Class[] interfaces) { MetaBeanProperty property = null; for (Class anInterface : interfaces) { MetaClass metaClass = 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(); if (isModified) { final MetaProperty metaProperty = metaClass.getMetaProperty(propertyName); if(metaProperty instanceof MetaBeanProperty) return (MetaBeanProperty)metaProperty; } return null; } protected MetaMethod findMixinMethod(String methodName, Class[] arguments) { return null; } protected static MetaMethod findMethodInClassHierarchy(Class instanceKlazz, String methodName, Class[] arguments, MetaClass metaClass) { if (metaClass instanceof MetaClassImpl) { boolean check = false; for (ClassInfo ci : ((MetaClassImpl)metaClass).theCachedClass.getHierarchy ()) { final MetaClass aClass = ci.getStrongMetaClass(); if (aClass instanceof MutableMetaClass && ((MutableMetaClass)aClass).isModified()) { check = true; break; } } if (!check) return null; } MetaMethod method = null; Class superClass; if (metaClass.getTheClass().isArray() && !metaClass.getTheClass().getComponentType().isPrimitive() && metaClass.getTheClass().getComponentType() != Object.class) { superClass = Object[].class; } else { superClass = metaClass.getTheClass().getSuperclass(); } if (superClass != null) { MetaClass superMetaClass = GroovySystem.getMetaClassRegistry().getMetaClass(superClass); method = findMethodInClassHierarchy(instanceKlazz, methodName, arguments, superMetaClass); } else { if (metaClass.getTheClass().isInterface()) { MetaClass superMetaClass = GroovySystem.getMetaClassRegistry().getMetaClass(Object.class); method = findMethodInClassHierarchy(instanceKlazz, methodName, arguments, superMetaClass); } } method = findSubClassMethod(instanceKlazz, methodName, arguments, metaClass, method); MetaMethod infMethod = searchInterfacesForMetaMethod(instanceKlazz, methodName, arguments, metaClass); if (infMethod != null) { if (method == null) method = infMethod; else method = mostSpecific(method, infMethod, instanceKlazz); } method = findOwnMethod(instanceKlazz, methodName, arguments, metaClass, method); return method; } private static MetaMethod findSubClassMethod(Class instanceKlazz, String methodName, Class[] arguments, MetaClass metaClass, MetaMethod method) { if (metaClass instanceof MetaClassImpl) { Object list = ((MetaClassImpl) metaClass).getSubclassMetaMethods(methodName); if (list != null) { if (list instanceof MetaMethod) { MetaMethod m = (MetaMethod) list; if (m.getDeclaringClass().getTheClass().isAssignableFrom(instanceKlazz)) { if (m.isValidExactMethod(arguments)) { if (method == null) method = m; else { method = mostSpecific (method, m, instanceKlazz); } } } } else { FastArray arr = (FastArray) list; for (int i = 0; i != arr.size(); ++i) { MetaMethod m = (MetaMethod) arr.get(i); if (m.getDeclaringClass().getTheClass().isAssignableFrom(instanceKlazz)) { if (m.isValidExactMethod(arguments)) { if (method == null) method = m; else { method = mostSpecific (method, m, instanceKlazz); } } } } } } } return method; } private static MetaMethod mostSpecific(MetaMethod method, MetaMethod newMethod, Class instanceKlazz) { Class newMethodC = newMethod.getDeclaringClass().getTheClass(); Class methodC = method.getDeclaringClass().getTheClass(); if (!newMethodC.isAssignableFrom(instanceKlazz)) return method; if (newMethodC == methodC) return newMethod; if (newMethodC.isAssignableFrom(methodC)) { return method; } if (methodC.isAssignableFrom(newMethodC)) { return newMethod; } return newMethod; } private static MetaMethod searchInterfacesForMetaMethod(Class instanceKlazz, String methodName, Class[] arguments, MetaClass metaClass) { Class[] interfaces = metaClass.getTheClass().getInterfaces(); MetaMethod method = null; for (Class anInterface : interfaces) { MetaClass infMetaClass = GroovySystem.getMetaClassRegistry().getMetaClass(anInterface); MetaMethod infMethod = searchInterfacesForMetaMethod(instanceKlazz, methodName, arguments, infMetaClass); if (infMethod != null) { if (method == null) method = infMethod; else method = mostSpecific(method, infMethod, instanceKlazz); } } method = findSubClassMethod(instanceKlazz, methodName, arguments, metaClass, method); method = findOwnMethod(instanceKlazz, methodName, arguments, metaClass, method); return method; } protected static MetaMethod findOwnMethod(Class instanceKlazz, String methodName, Class[] arguments, MetaClass metaClass, MetaMethod method) { // we trick ourselves here if (instanceKlazz == metaClass.getTheClass()) return method; MetaMethod ownMethod = metaClass.pickMethod(methodName, arguments); if (ownMethod != null) { if (method == null) method = ownMethod; else method = mostSpecific(method, ownMethod, instanceKlazz); } return method; } protected Object getSubclassMetaMethods(String methodName) { return null; } private abstract class MethodIndexAction { public void iterate() { final ComplexKeyHashMap.Entry[] table = metaMethodIndex.methodHeaders.getTable(); int len = table.length; for (int i = 0; i != len; ++i) { for (SingleKeyHashMap.Entry classEntry = (SingleKeyHashMap.Entry) table[i]; classEntry != null; classEntry = (SingleKeyHashMap.Entry) classEntry.next) { Class clazz = (Class) classEntry.getKey(); if (skipClass(clazz)) continue; MetaMethodIndex.Header header = (MetaMethodIndex.Header) classEntry.getValue(); for (MetaMethodIndex.Entry nameEntry = header.head; nameEntry != null; nameEntry = nameEntry.nextClassEntry) { methodNameAction(clazz, nameEntry); } } } } public abstract void methodNameAction(Class clazz, MetaMethodIndex.Entry methods); public boolean skipClass(Class clazz) { return false; } } public Object getProperty(Object object, String property) { return getProperty(theClass, object, property, false, false); } public void setProperty(Object object, String property, Object newValue) { setProperty(theClass, object, property, newValue, false, false); } public Object getAttribute(Object object, String attribute) { return getAttribute(theClass, object, attribute, false, false); } public void setAttribute(Object object, String attribute, Object newValue) { setAttribute(theClass, object, attribute, newValue, false, false); } public MetaMethod pickMethod(String methodName, Class[] arguments) { return getMethodWithoutCaching(theClass, methodName, arguments, false); } /** * @deprecated use pickMethod instead */ protected MetaMethod retrieveMethod(String methodName, Class[] arguments) { return pickMethod(methodName, arguments); } /** * remove all method call cache entries. This should be done if a * method is added during runtime, but not by using a category. */ protected void clearInvocationCaches() { metaMethodIndex.clearCaches (); } private static final SingleKeyHashMap.Copier NAME_INDEX_COPIER = new SingleKeyHashMap.Copier() { public Object copy(Object value) { if (value instanceof FastArray) return ((FastArray) value).copy(); else return value; } }; private static final SingleKeyHashMap.Copier METHOD_INDEX_COPIER = new SingleKeyHashMap.Copier() { public Object copy(Object value) { return SingleKeyHashMap.copy(new SingleKeyHashMap(false), (SingleKeyHashMap) value, NAME_INDEX_COPIER); } }; class MethodIndex extends Index { public MethodIndex(boolean b) { super(false); } public MethodIndex(int size) { super(size); } public MethodIndex() { super(); } MethodIndex copy() { return (MethodIndex) SingleKeyHashMap.copy(new MethodIndex(false), this, METHOD_INDEX_COPIER); } protected Object clone() throws CloneNotSupportedException { return super.clone(); } } public static class Index extends SingleKeyHashMap { public Index(int size) { } public Index() { } public Index(boolean size) { super(false); } public SingleKeyHashMap getNotNull(CachedClass key) { Entry res = getOrPut(key); if (res.value == null) { res.value = new SingleKeyHashMap(); } return (SingleKeyHashMap) res.value; } public void put(CachedClass key, SingleKeyHashMap value) { ((Entry) getOrPut(key)).value = value; } public SingleKeyHashMap getNullable(CachedClass clazz) { return (SingleKeyHashMap) get(clazz); } public boolean checkEquals(ComplexKeyHashMap.Entry e, Object key) { return ((Entry) e).key.equals(key); } } private static class DummyMetaMethod extends MetaMethod { public int getModifiers() { return 0; } public String getName() { return null; } public Class getReturnType() { return null; } public CachedClass getDeclaringClass() { return null; } public ParameterTypes getParamTypes() { return null; } public Object invoke(Object object, Object[] arguments) { return null; } } private static class GetMethodMetaProperty extends MetaProperty { private final MetaMethod theMethod; public GetMethodMetaProperty(String name, MetaMethod theMethod) { super(name, Object.class); this.theMethod = theMethod; } public Object getProperty(Object object) { return theMethod.doMethodInvoke(object, new Object[]{name}); } public void setProperty(Object object, Object newValue) { throw new UnsupportedOperationException(); } } private static class GetBeanMethodMetaProperty extends MetaProperty { private final MetaMethod theMethod; public GetBeanMethodMetaProperty(String name, MetaMethod theMethod) { super(name, Object.class); this.theMethod = theMethod; } public Object getProperty(Object object) { return theMethod.doMethodInvoke(object, EMPTY_ARGUMENTS); } public void setProperty(Object object, Object newValue) { throw new UnsupportedOperationException(); } } }