http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/MetaClassImpl.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/MetaClassImpl.java b/src/main/groovy/groovy/lang/MetaClassImpl.java new file mode 100644 index 0000000..c453a3f --- /dev/null +++ b/src/main/groovy/groovy/lang/MetaClassImpl.java @@ -0,0 +1,4021 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.lang; + +import org.apache.groovy.internal.util.UncheckedThrow; +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.CacheAccessControlException; +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.reflection.android.AndroidSupport; +import org.codehaus.groovy.runtime.ArrayTypeUtils; +import org.codehaus.groovy.runtime.ConvertedClosure; +import org.codehaus.groovy.runtime.CurriedClosure; +import org.codehaus.groovy.runtime.DefaultGroovyMethods; +import org.codehaus.groovy.runtime.GeneratedClosure; +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.AbstractCallSite; +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.MethodMetaProperty.GetBeanMethodMetaProperty; +import org.codehaus.groovy.runtime.metaclass.MethodMetaProperty.GetMethodMetaProperty; +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.MultipleSetterProperty; +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.Array; +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.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.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static org.codehaus.groovy.ast.tools.GeneralUtils.inSamePackage; +import static org.codehaus.groovy.ast.tools.GeneralUtils.isDefaultVisibility; +import static org.codehaus.groovy.reflection.ReflectionCache.isAssignableFrom; + +/** + * Allows methods to be dynamically added to existing classes at runtime + * @see groovy.lang.MetaClass + */ +public class MetaClassImpl implements MetaClass, MutableMetaClass { + + public static final Object[] EMPTY_ARGUMENTS = {}; + + 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"; + protected static final String INVOKE_METHOD_METHOD = "invokeMethod"; + + private static final String CLOSURE_CALL_METHOD = "call"; + private static final String CLOSURE_DO_CALL_METHOD = "doCall"; + private static final String GET_PROPERTY_METHOD = "getProperty"; + private static final String SET_PROPERTY_METHOD = "setProperty"; + 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; + private static final Comparator<CachedClass> CACHED_CLASS_NAME_COMPARATOR = new Comparator<CachedClass>() { + public int compare(final CachedClass o1, final CachedClass o2) { + return o1.getName().compareTo(o2.getName()); + } + }; + private static final MetaMethod[] EMPTY = new MetaMethod[0]; + private static final MetaMethod AMBIGUOUS_LISTENER_METHOD = new DummyMetaMethod(); + + protected final Class theClass; + protected final CachedClass theCachedClass; + protected final boolean isGroovyObject; + protected final boolean isMap; + protected final MetaMethodIndex metaMethodIndex; + + private final Index classPropertyIndex = new MethodIndex(); + private final SingleKeyHashMap staticPropertyIndex = new SingleKeyHashMap(); + private final Map<String, MetaMethod> listeners = new HashMap<String, MetaMethod>(); + private final List<MetaMethod> allMethods = new ArrayList<MetaMethod>(); + // we only need one of these that can be reused over and over. + private final MetaProperty arrayLengthProperty = new MetaArrayLengthProperty(); + private final Index classPropertyIndexForSuper = new MethodIndex(); + private final Set<MetaMethod> newGroovyMethodsSet = new HashSet<MetaMethod>(); + private final MetaMethod[] myNewMetaMethods; + private final MetaMethod[] additionalMetaMethods; + + protected MetaMethod getPropertyMethod; + protected MetaMethod invokeMethodMethod; + protected MetaMethod setPropertyMethod; + protected MetaClassRegistry registry; + private ClassNode classNode; + private FastArray constructors; + private volatile boolean initialized; + private MetaMethod genericGetMethod; + private MetaMethod genericSetMethod; + private MetaMethod propertyMissingGet; + private MetaMethod propertyMissingSet; + private MetaMethod methodMissing; + private MetaMethodIndex.Header mainClassMethodHeader; + + /** + * Constructor + * + * @param theClass The class this is the metaclass dor + * @param add The methods for this class + */ + 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)) { + List<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; + } + } + + /** + * Constructor that sets the methods to null + * + * @param theClass The class this is the metaclass dor + */ + public MetaClassImpl(final Class theClass) { + this(theClass, null); + } + + /** + * Constructor with registry + * + * @param registry The metaclass registry for this MetaClass + * @param theClass The class + * @param add The methods + */ + public MetaClassImpl(MetaClassRegistry registry, final Class theClass, MetaMethod add []) { + this(theClass, add); + this.registry = registry; + this.constructors = new FastArray(theCachedClass.getConstructors()); + } + + /** + * Constructor with registry setting methods to null + * + * @param registry The metaclass registry for this MetaClass + * @param theClass The class + */ + public MetaClassImpl(MetaClassRegistry registry, final Class theClass) { + this(registry, theClass, null); + } + + /** + * Returns the cached class for this metaclass + * + * @return The cached class. + */ + public final CachedClass getTheCachedClass() { + return theCachedClass; + } + + /** + * Returns the registry for this metaclass + * + * @return The resgistry + */ + public MetaClassRegistry getRegistry() { + return registry; + } + + /** + * @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); + if (m!=null) { + return Collections.singletonList(m); + } + return Collections.emptyList(); + } + + /** + * @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); + } + + /** + *Returns the class this object this is the metaclass of. + * + * @return The class contained by this metaclass + */ + public Class getTheClass() { + return this.theClass; + } + + /** + * Return wether the class represented by this metaclass instance is an instance of the GroovyObject class + * + * @return true if this is a groovy class, false otherwise. + */ + public boolean isGroovyObject() { + return isGroovyObject; + } + + /** + * Fills the method index + */ + private void fillMethodIndex() { + mainClassMethodHeader = metaMethodIndex.getHeader(theClass); + LinkedList<CachedClass> 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<CachedClass> superClasses, CachedClass firstGroovySuper) { + + MetaMethodIndex.Header header = metaMethodIndex.getHeader(firstGroovySuper.getTheClass()); + CachedClass c; + Iterator<CachedClass> iter = superClasses.iterator(); + for (; iter.hasNext();) { + c = 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 = 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 (method.getName().equals("<init>") && !method.getDeclaringClass().equals(theCachedClass)) continue; + 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; + + @Override + 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 ^ Modifier.isPrivate(method.getModifiers())) 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 ^ Modifier.isPrivate(method.getModifiers())) return; + String mopName = method.getMopName(); + // GROOVY-4922: Due to a numbering scheme change, we must find the super$X$method which exists + // with the highest number. If we don't, no method may be found, leading to a stack overflow + String[] decomposedMopName = decomposeMopName(mopName); + int distance = Integer.parseInt(decomposedMopName[1]); + while (distance>0) { + String fixedMopName = decomposedMopName[0] + distance + decomposedMopName[2]; + int index = Arrays.binarySearch(mopMethods, fixedMopName, CachedClass.CachedMethodComparatorWithString.INSTANCE); + if (index >= 0) { + int from = index; + while (from > 0 && mopMethods[from-1].getName().equals(fixedMopName)) + from--; + int to = index; + while (to < mopMethods.length-1 && mopMethods[to+1].getName().equals(fixedMopName)) + to++; + + int matchingMethod = findMatchingMethod(mopMethods, from, to, method); + if (matchingMethod != -1) { + e.methodsForSuper = mopMethods[matchingMethod]; + distance = 0; + } + } + distance--; + } + } + } + } + + private String[] decomposeMopName(final String mopName) { + int idx = mopName.indexOf("$"); + if (idx>0) { + int eidx = mopName.indexOf("$", idx+1); + if (eidx>0) { + return new String[] { + mopName.substring(0, idx+1), + mopName.substring(idx+1, eidx), + mopName.substring(eidx) + }; + } + } + return new String[]{"","0",mopName}; + } + + 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; + boolean isPrivate = Modifier.isPrivate(method.getModifiers()); + if (useThis ^ isPrivate) 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) { + boolean skip = false; + // skip DGM methods on an interface if the class already has the method + // but don't skip for GroovyObject-related methods as it breaks things :-( + if (method instanceof GeneratedMetaMethod && !isAssignableFrom(GroovyObject.class, method.getDeclaringClass().getTheClass())) { + for (Method m : theClass.getMethods()) { + if (method.getName().equals(m.getName()) + // below not true for DGM#push and also co-variant return scenarios + //&& method.getReturnType().equals(m.getReturnType()) + && MetaMethod.equal(method.getParameterTypes(), m.getParameterTypes())) { + skip = true; + break; + } + } + } + if (!skip) { + if (!newGroovyMethodsSet.contains(method)) { + newGroovyMethodsSet.add(method); + } + addMetaMethodToIndex(method, mainClassMethodHeader); + } + } + } + } + + private void connectMultimethods(List<CachedClass> superClasses, CachedClass firstGroovyClass) { + superClasses = DefaultGroovyMethods.reverse(superClasses); + MetaMethodIndex.Header last = null; + for (final CachedClass c : superClasses) { + 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; + } + + /** + * Gets all instance methods available on this class for the given name + * + * @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; + } + + /** + * Returns all the normal static methods on this class for the given name + * + * @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; + } + + /** + * Returns whether this MetaClassImpl has been modified. Since MetaClassImpl + * is not designed for modification this method always returns false + * + * @return false + */ + public boolean isModified() { + return false; // MetaClassImpl not designed for modification, just return false + } + + /** + *Adds an instance method to this metaclass. + * + * @param method The method to be added + */ + 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); + } + } + + /** + *Adds a static method to this metaclass. + * + * @param method The method to be added + */ + 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); + } + } + + /** + * Invoke a method on the given object with the given arguments. + * + * @param object The object the method should be invoked on. + * @param methodName The name of the method to invoke. + * @param arguments The arguments to the invoked method as null, a Tuple, an array or a single argument of any type. + * + * @return The result of the method invocation. + */ + 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}); + } + } + + /** + * Invoke a missing method on the given object with the given arguments. + * + * @param instance The object the method should be invoked on. + * @param methodName The name of the method to invoke. + * @param arguments The arguments to the invoked method. + * + * @return The result of the method invocation. + */ + public Object invokeMissingMethod(Object instance, String methodName, Object[] arguments) { + return invokeMissingMethod(instance, methodName, arguments, null, false); + } + + /** + * Invoke a missing property on the given object with the given arguments. + * + * @param instance The object the method should be invoked on. + * @param propertyName The name of the property to invoke. + * @param optionalValue The (optional) new value for the property + * @param isGetter Wether the method is a getter + * + * @return The result of the method invocation. + */ + 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) { + if (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 a method on the given receiver for the specified arguments. + * The MetaClass will attempt to establish the method to invoke based on the name and arguments provided. + * + * + * @param object The object which the method was invoked on + * @param methodName The name of the method + * @param originalArguments The arguments to the method + * + * @return The return value of the method + * + * @see MetaClass#invokeMethod(Class, Object, String, Object[], boolean, boolean) + */ + public Object invokeMethod(Object object, String methodName, Object[] originalArguments) { + return invokeMethod(theClass, object, methodName, originalArguments, false, false); + } + + private Object invokeMethodClosure(Object object, String methodName, Object[] arguments) { + final MethodClosure mc = (MethodClosure) object; + final Object owner = mc.getOwner(); + + methodName = mc.getMethod(); + final Class ownerClass = owner instanceof Class ? (Class) owner : owner.getClass(); + final MetaClass ownerMetaClass = registry.getMetaClass(ownerClass); + + // To conform to "Least Surprise" principle, try to invoke method with original arguments first, which can match most of use cases + try { + return ownerMetaClass.invokeMethod(ownerClass, owner, methodName, arguments, false, false); + } catch (MissingMethodExceptionNoStack e) { + // CONSTRUCTOR REFERENCE + if (owner instanceof Class && MethodClosure.NEW.equals(methodName)) { + if (ownerClass.isArray()) { + if (0 == arguments.length) { + throw new GroovyRuntimeException("The arguments(specifying size) are required to create array[" + ownerClass.getCanonicalName() + "]"); + } + + int arrayDimension = ArrayTypeUtils.dimension(ownerClass); + + if (arguments.length > arrayDimension) { + throw new GroovyRuntimeException("The length[" + arguments.length + "] of arguments should not be greater than the dimensions[" + arrayDimension + "] of array[" + ownerClass.getCanonicalName() + "]"); + } + + int[] sizeArray = new int[arguments.length]; + + for (int i = 0, n = sizeArray.length; i < n; i++) { + Object argument = arguments[i]; + + if (argument instanceof Integer) { + sizeArray[i] = (Integer) argument; + } else { + sizeArray[i] = Integer.parseInt(String.valueOf(argument)); + } + } + + Class arrayType = + arguments.length == arrayDimension + ? ArrayTypeUtils.elementType(ownerClass) // Just for better performance, though we can use reduceDimension only + : ArrayTypeUtils.reduceDimension(ownerClass, (arrayDimension - arguments.length)); + return Array.newInstance(arrayType, sizeArray); + } + + return ownerMetaClass.invokeConstructor(arguments); + } + + // METHOD REFERENCE + // if and only if the owner is a class and the method closure can be related to some instance methods, + // try to invoke method with adjusted arguments(first argument is the actual owner) again. + // otherwise throw the MissingMethodExceptionNoStack. + if (!(owner instanceof Class + && ((Boolean) mc.getProperty(MethodClosure.ANY_INSTANCE_METHOD_EXISTS)).booleanValue())) { + + throw e; + } + + if (arguments.length <= 0) { + return invokeMissingMethod(object, methodName, arguments); + } + + Object newOwner = arguments[0]; + Object[] newArguments = Arrays.copyOfRange(arguments, 1, arguments.length); + return ownerMetaClass.invokeMethod(ownerClass, newOwner, methodName, newArguments, false, false); + } + } + + /** + * <p>Invokes a method on the given receiver for the specified arguments. The sender is the class that invoked the method on the object. + * The MetaClass will attempt to establish the method to invoke based on the name and arguments provided. + * + * <p>The isCallToSuper and fromInsideClass help the Groovy runtime perform optimisations on the call to go directly + * to the super class if necessary + * + * @param sender The java.lang.Class instance that invoked the method + * @param object The object which the method was invoked on + * @param methodName The name of the method + * @param originalArguments The arguments to the method + * @param isCallToSuper Whether the method is a call to a super class method + * @param fromInsideClass Whether the call was invoked from the inside or the outside of the class + * + * @return The return value of the method + * + * @see MetaClass#invokeMethod(Class, Object, String, Object[], boolean, boolean) + */ + 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 = null; + if (CLOSURE_CALL_METHOD.equals(methodName) && object instanceof GeneratedClosure) { + method = getMethodWithCaching(sender, "doCall", arguments, isCallToSuper); + } + if (method==null) { + 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) { + return this.invokeMethodClosure(object, methodName, arguments); + } 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 = createTransformMetaMethod(getMethodWithCaching(sender, methodName, newArguments, isCallToSuper)); + } + return method; + } + + protected MetaMethod createTransformMetaMethod(MetaMethod method) { + if (method == null) { + return null; + } + + return 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); + } + }; + } + + 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 static 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) { + // 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 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))) { + return cacheEntry.method; + } + + cacheEntry = new MetaMethodIndex.CacheEntry (params, (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; + } + + final Class[] classes = MetaClassHelper.convertToTypeArray(arguments); + MetaMethod method = (MetaMethod) chooseMethod(e.name, e.methodsForSuper, classes); + cacheEntry = new MetaMethodIndex.CacheEntry (classes, method.isAbstract()?null:method); + + 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; + } + + final Class[] classes = MetaClassHelper.convertToTypeArray(arguments); + cacheEntry = new MetaMethodIndex.CacheEntry (classes, (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; + } + + final Class[] classes = MetaClassHelper.convertToTypeArray(arguments); + cacheEntry = new MetaMethodIndex.CacheEntry (classes, 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 = 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 static 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; + } + } + + public Object invokeConstructor(Object[] arguments) { + return invokeConstructor(theClass, arguments); + } + + public int selectConstructorAndTransformArguments(int numberOfConstructors, Object[] arguments) { + if (numberOfConstructors==-1) { + return selectConstructorAndTransformArguments1(arguments); + } else { + // falling back to pre 2.1.9 selection algorithm + // in practice this branch will only be reached if the class calling this code is a Groovy class + // compiled with an earlier version of the Groovy compiler + return selectConstructorAndTransformArguments0(numberOfConstructors, arguments); + } + + + } + + private int selectConstructorAndTransformArguments0(final 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()); + } + + CachedConstructor constructor = createCachedConstructor(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); + } + + private CachedConstructor createCachedConstructor(Object[] arguments) { + 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) + ")"); + } + return constructor; + } + + /** + * Constructor selection algorithm for Groovy 2.1.9+. + * This selection algorithm was introduced as a workaround for GROOVY-6080. Instead of generating an index between + * 0 and N where N is the number of super constructors at the time the class is compiled, this algorithm uses + * a hash of the constructor descriptor instead. + * + * This has the advantage of letting the super class add new constructors while being binary compatible. But there + * are still problems with this approach: + * <ul> + * <li>There's a risk of hash collision, even if it's very low (two constructors of the same class must have the same hash)</li> + * <li>If the super class adds a new constructor which takes as an argument a superclass of an existing constructor parameter and + * that this new constructor is selected at runtime, it would not find it.</li> + * </ul> + * + * Hopefully in the last case, the error message is much nicer now since it explains that it's a binary incompatible change. + * + * @param arguments the actual constructor call arguments + * @return a hash used to identify the constructor to be called + * @since 2.1.9 + */ + private int selectConstructorAndTransformArguments1(Object[] arguments) { + CachedConstructor constructor = createCachedConstructor(arguments); + final String methodDescriptor = BytecodeHelper.getMethodDescriptor(Void.TYPE, constructor.getNativeParameterTypes()); + // keeping 3 bits for additional information such as vargs + return BytecodeHelper.hashCode(methodDescriptor); + } + + + /** + * 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"); + } + + /** + * This is a helper class introduced in Groovy 2.1.0, which is used only by + * indy. This class is for internal use only. + * @since Groovy 2.1.0 + */ + public static final class MetaConstructor extends MetaMethod { + private final CachedConstructor cc; + private final boolean beanConstructor; + private MetaConstructor(CachedConstructor cc, boolean bean) { + super(cc.getNativeParameterTypes()); + this.setParametersTypes(cc.getParameterTypes()); + this.cc = cc; + this.beanConstructor = bean; + } + @Override + public int getModifiers() { return cc.getModifiers(); } + @Override + public String getName() { return "<init>"; } + @Override + public Class getReturnType() { return cc.getCachedClass().getTheClass(); } + @Override + public CachedClass getDeclaringClass() { return cc.getCachedClass(); } + @Override + public Object invoke(Object object, Object[] arguments) { + return cc.doConstructorInvoke(arguments); + } + public CachedConstructor getCachedConstrcutor() { return cc; } + public boolean isBeanConstructor() { return beanConstructor; } + } + + /** + * This is a helper method added in Groovy 2.1.0, which is used only by indy. + * This method is for internal use only. + * @since Groovy 2.1.0 + */ + public MetaMethod retrieveConstructor(Object[] arguments) { + checkInitalised(); + if (arguments == null) arguments = EMPTY_ARGUMENTS; + Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); + MetaClassHelper.unwrap(arguments); + Object res = chooseMethod("<init>", constructors, argClasses); + if (res instanceof MetaMethod) return (MetaMethod) res; + CachedConstructor constructor = (CachedConstructor) res; + if (constructor != null) return new MetaConstructor(constructor, false); + if (arguments.length == 1 && arguments[0] instanceof Map) { + res = chooseMethod("<init>", constructors, MetaClassHelper.EMPTY_TYPE_ARRAY); + } else if ( + arguments.length == 2 && arguments[1] instanceof Map && + theClass.getEnclosingClass()!=null && + theClass.getEnclosingClass().isAssignableFrom(argClasses[0])) + { + res = chooseMethod("<init>", constructors, new Class[]{argClasses[0]}); + } + if (res instanceof MetaMethod) return (MetaMethod) res; + constructor = (CachedConstructor) res; + if (constructor != null) return new MetaConstructor(constructor, true); + + return null; + } + + 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); + } + + Tuple2<MetaMethod, MetaProperty> methodAndProperty = createMetaMethodAndMetaProperty(sender, sender, name, useSuper, isStatic); + MetaMethod method = methodAndProperty.getFirst(); + + //---------------------------------------------------------------------- + // getter + //---------------------------------------------------------------------- + MetaProperty mp = methodAndProperty.getSecond(); + + //---------------------------------------------------------------------- + // 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; + } catch (CacheAccessControlException 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 + Object[] arguments = EMPTY_ARGUMENTS; + 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 = 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); + }
<TRUNCATED>
