| /* |
| * Copyright 2003-2007 the original author or authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package groovy.lang; |
| |
| import org.codehaus.groovy.GroovyBugError; |
| import org.codehaus.groovy.ast.ClassNode; |
| import org.codehaus.groovy.classgen.BytecodeHelper; |
| import org.codehaus.groovy.control.CompilationUnit; |
| import org.codehaus.groovy.control.Phases; |
| import org.codehaus.groovy.reflection.*; |
| import org.codehaus.groovy.runtime.*; |
| import org.codehaus.groovy.runtime.metaclass.*; |
| import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; |
| import org.codehaus.groovy.runtime.wrappers.Wrapper; |
| import org.objectweb.asm.ClassVisitor; |
| |
| import java.beans.*; |
| 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.*; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| /** |
| * 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"; |
| private static final String CLOSURE_CURRY_METHOD = "curry"; |
| 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 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 static final Logger LOG = Logger.getLogger(MetaClass.class.getName()); |
| protected final Class theClass; |
| protected final CachedClass 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 listeners = new HashMap(); |
| private final Map methodCache = new ConcurrentReaderHashMap(); |
| private final Map staticMethodCache = new ConcurrentReaderHashMap(); |
| private FastArray constructors; |
| private final List allMethods = new ArrayList(); |
| private List interfaceMethods; |
| 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 AMBIGOUS_LISTENER_METHOD = new DummyMetaMethod(); |
| private static final Object[] EMPTY_ARGUMENTS = {}; |
| private final Set newGroovyMethodsSet = new HashSet(); |
| |
| private MetaMethod genericGetMethod; |
| private MetaMethod genericSetMethod; |
| private MetaMethod propertyMissingGet; |
| private MetaMethod propertyMissingSet; |
| private static final MetaMethod NULL_METHOD = new DummyMetaMethod(); |
| private MetaMethod methodMissing; |
| private MetaMethodIndex.Header mainClassMethodHeader; |
| private final MetaMethodIndex metaMethodIndex; |
| protected static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; |
| |
| |
| public MetaClassImpl(final Class theClass) { |
| 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); |
| } |
| |
| public MetaClassImpl(MetaClassRegistry registry, final Class theClass) { |
| this(theClass); |
| this.registry = registry; |
| this.constructors = new FastArray(theCachedClass.getConstructors()); |
| } |
| |
| /** |
| * @see MetaObjectProtocol#respondsTo(Object,String, Object[]) |
| */ |
| public List respondsTo(Object obj, String name, Object[] argTypes) { |
| Class[] classes = castArgumentsToClassArray(argTypes); |
| MetaMethod m = getMetaMethod(name, classes); |
| List methods = new ArrayList(); |
| if (m != null) { |
| methods.add(m); |
| } |
| return methods; |
| } |
| |
| private Class[] castArgumentsToClassArray(Object[] argTypes) { |
| if (argTypes == null) return EMPTY_CLASS_ARRAY; |
| Class[] classes = new Class[argTypes.length]; |
| for (int i = 0; i < argTypes.length; i++) { |
| Object argType = argTypes[i]; |
| if (argType instanceof Class) { |
| classes[i] = (Class) argType; |
| } else if (argType == null) { |
| classes[i] = null; |
| } else { |
| // throw new IllegalArgumentException("Arguments to method [respondsTo] must be of type java.lang.Class!"); |
| classes[i] = argType.getClass(); |
| } |
| } |
| return classes; |
| } |
| |
| /** |
| * @see MetaObjectProtocol#respondsTo(Object,String, Object[]) |
| */ |
| 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); |
| return (MetaProperty) propertyMap.get(name); |
| } |
| } |
| |
| /** |
| * @see MetaObjectProtocol#getStaticMetaMethod(String, Object[]) |
| */ |
| public MetaMethod getStaticMetaMethod(String name, Object[] argTypes) { |
| Class[] classes = castArgumentsToClassArray(argTypes); |
| return retrieveStaticMethod(name, classes); |
| } |
| |
| |
| /** |
| * @see MetaObjectProtocol#getMetaMethod(String, Object[]) |
| */ |
| public MetaMethod getMetaMethod(String name, Object[] argTypes) { |
| Class[] classes = 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 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.getCachedClass()); |
| CachedClass c; |
| for (; iter.hasNext();) { |
| c = (CachedClass) iter.next(); |
| |
| CachedMethod[] cachedMethods = c.getMethods(); |
| for (int i = 0; i < cachedMethods.length; i++) { |
| MetaMethod metaMethod = cachedMethods[i].getReflectionMetaMethod(); |
| addToAllMethodsIfPublic(metaMethod); |
| if (!metaMethod.isPrivate() || c == firstGroovySuper) |
| addMetaMethodToIndex(metaMethod, header); |
| } |
| |
| MetaMethod[] cachedMethods1 = c.getNewMetaMethods(); |
| for (int i = 0; i < cachedMethods1.length; i++) { |
| final MetaMethod method = cachedMethods1[i]; |
| |
| 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.getCachedClass()); |
| |
| if (last != null) { |
| metaMethodIndex.copyNonPrivateMethods(last, header); |
| } |
| last = header; |
| |
| CachedMethod[] cachedMethods = c.getMethods(); |
| for (int i = 0; i < cachedMethods.length; i++) { |
| MetaMethod metaMethod = cachedMethods[i].getReflectionMetaMethod(); |
| addToAllMethodsIfPublic(metaMethod); |
| addMetaMethodToIndex(metaMethod, header); |
| } |
| |
| MetaMethod[] cachedMethods1 = c.getNewMetaMethods(); |
| for (int i = 0; i < cachedMethods1.length; i++) { |
| final MetaMethod method = cachedMethods1[i]; |
| |
| if (!newGroovyMethodsSet.contains(method)) { |
| newGroovyMethodsSet.add(method); |
| addMetaMethodToIndex(method, header); |
| } |
| } |
| } |
| } |
| |
| private void addInterfaceMethods(Set interfaces) { |
| MetaMethodIndex.Header header = metaMethodIndex.getHeader(theClass); |
| for (Iterator iter = interfaces.iterator(); iter.hasNext();) { |
| CachedClass c = (CachedClass) iter.next(); |
| final CachedMethod[] m = c.getMethods(); |
| for (int i=0; i != m.length; ++i) { |
| MetaMethod method = m [i].getReflectionMetaMethod(); |
| String name = method.getName(); |
| MetaMethodIndex.Entry e = metaMethodIndex.getOrPutMethods(name, header); |
| e.methods = metaMethodIndex.addMethodToList(e.methods, method); |
| } |
| } |
| } |
| |
| private LinkedList getSuperClasses() { |
| LinkedList superClasses = new LinkedList(); |
| |
| 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().getCachedClass()) { |
| hasPrivate = true; |
| break; |
| } |
| } |
| } |
| else { |
| MetaMethod method = (MetaMethod) e.methods; |
| if (method.isPrivate() && clazz == method.getDeclaringClass().getCachedClass()) { |
| 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].getReflectionMetaMethod(); |
| } |
| } |
| } |
| } |
| 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].getReflectionMetaMethod(); |
| } |
| } |
| } |
| } |
| } |
| |
| 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].getReflectionMetaMethod()); |
| } |
| } |
| } |
| } |
| } |
| 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 interfaces) { |
| // add methods declared by DGM for interfaces |
| for (Iterator it = interfaces.iterator(); it.hasNext(); ) { |
| CachedClass cls = (CachedClass) it.next(); |
| MetaMethod methods [] = cls.getNewMetaMethods(); |
| for (int i = 0; i < methods.length; i++) { |
| MetaMethod method = methods[i]; |
| if (!newGroovyMethodsSet.contains(method)) { |
| newGroovyMethodsSet.add(method); |
| } |
| MetaMethodIndex.Entry e = metaMethodIndex.getOrPutMethods(method.getName(), mainClassMethodHeader); |
| e.methods = metaMethodIndex.addMethodToList(e.methods, method); |
| } |
| } |
| } |
| |
| private void populateInterfaces(Set interfaces) { |
| // for (Iterator iter = interfaces.iterator(); iter.hasNext();) { |
| // CachedClass iClass = (CachedClass) iter.next(); |
| // classIndex.copyNonPrivateMethods(mainClassMethodHeader, classIndex.getHeader(iClass.getCachedClass())); |
| // } |
| } |
| |
| 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.getCachedClass()); |
| // 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 noticable time |
| if (last != null) metaMethodIndex.copyNonPrivateNonNewMetaMethods(last, methodIndex); |
| last = methodIndex; |
| |
| if (c == firstGroovyClass) |
| break; |
| } |
| } |
| |
| private void inheritMethods(Collection superClasses, CachedClass firstGroovySuper) { |
| Iterator iter = superClasses.iterator(); |
| for (; iter.hasNext();) { |
| CachedClass c = (CachedClass) iter.next(); |
| if (c == firstGroovySuper.getCachedSuperClass()) |
| break; |
| } |
| |
| MetaMethodIndex.Header last = null; |
| for (; iter.hasNext();) { |
| CachedClass c = (CachedClass) iter.next(); |
| MetaMethodIndex.Header methodIndex = metaMethodIndex.getHeader(c.getCachedClass()); |
| if (last != null) { |
| metaMethodIndex.copyNonPrivateMethods(last, methodIndex); |
| } |
| last = methodIndex; |
| } |
| } |
| |
| 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.getCachedClass())) { |
| firstGroovy = c; |
| break; |
| } |
| } |
| |
| if (firstGroovy == null) |
| firstGroovy = theCachedClass; |
| else { |
| if (firstGroovy.getCachedClass() == GroovyObjectSupport.class && iter.hasNext()) { |
| firstGroovy = (CachedClass) iter.next(); |
| if (firstGroovy.getCachedClass() == Closure.class && iter.hasNext()) { |
| firstGroovy = (CachedClass) iter.next(); |
| } |
| } |
| } |
| |
| return GroovyObject.class.isAssignableFrom(firstGroovy.getCachedClass()) ? firstGroovy.getCachedSuperClass() : firstGroovy; |
| } |
| |
| /** |
| * @return all the normal instance methods avaiable 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 && GroovyCategorySupport.hasCategoryInAnyThread()) { |
| List used = GroovyCategorySupport.getCategoryMethods(sender, 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(); |
| filterMatchingMethodForCategory(arr, element); |
| } |
| answer = arr; |
| } |
| } |
| return answer; |
| } |
| |
| /** |
| * @return all the normal static methods avaiable 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.getCachedClass())); |
| } |
| |
| 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.getCachedClass())); |
| } |
| |
| private void addNewStaticMethodToIndex(MetaMethod newMethod, MetaMethodIndex.Header header) { |
| if (!newGroovyMethodsSet.contains(newMethod)) { |
| newGroovyMethodsSet.add(newMethod); |
| addMetaMethodToIndex(newMethod, header); |
| } |
| } |
| |
| private void unwrap(Object[] arguments) { |
| // |
| // Temp code to ignore wrapped parameters |
| // The New MOP will deal with these properly |
| // |
| for (int i = 0; i != arguments.length; i++) { |
| if (arguments[i] instanceof Wrapper) { |
| arguments[i] = ((Wrapper) arguments[i]).unwrap(); |
| } |
| } |
| } |
| |
| 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); |
| } |
| |
| public Object invokeMissingProperty(Object instance, String propertyName, Object optionalValue, boolean isGetter) { |
| |
| 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}); |
| } |
| } |
| |
| throw new MissingPropertyExceptionNoStack(propertyName, theClass); |
| } |
| |
| private Object invokeMissingMethod(Object instance, String methodName, Object[] arguments, RuntimeException original) { |
| if (methodMissing != null) { |
| return methodMissing.invoke(instance, new Object[]{methodName, arguments}); |
| } else if (original != null) throw original; |
| else throw new MissingMethodExceptionNoStack(methodName, theClass, arguments, false); |
| } |
| |
| |
| /** |
| * 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"); |
| } |
| if (LOG.isLoggable(Level.FINER)) { |
| MetaClassHelper.logMethodCall(object, methodName, originalArguments); |
| } |
| final Object[] arguments = originalArguments == null ? EMPTY_ARGUMENTS : originalArguments; |
| final Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); |
| |
| unwrap(arguments); |
| |
| MetaMethod method = getMethodWithCaching(sender, methodName, argClasses, isCallToSuper); |
| |
| if (method == null && arguments.length == 1 && arguments[0] instanceof List) { |
| Object[] newArguments = ((List) arguments[0]).toArray(); |
| Class[] newArgClasses = MetaClassHelper.convertToTypeArray(newArguments); |
| method = getMethodWithCaching(sender, methodName, newArgClasses, 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); |
| } |
| }; |
| } |
| } |
| |
| 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); |
| } else if (CLOSURE_CURRY_METHOD.equals(methodName)) { |
| return closure.curry(arguments); |
| } |
| |
| final Object delegate = closure.getDelegate(); |
| final boolean isClosureNotOwner = owner != closure; |
| final int resolveStrategy = closure.getResolveStrategy(); |
| |
| 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); |
| } |
| |
| 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 (last == null) last = mme; |
| } |
| } |
| if (delegate != closure && (delegate instanceof GroovyObject)) { |
| try { |
| return invokeMethodOnGroovyObject(methodName, originalArguments, delegate); |
| } catch (MissingMethodException mme) { |
| last = mme; |
| } |
| } |
| if (last != null) return invokeMissingMethod(object, methodName, originalArguments, last); |
| } |
| } |
| } |
| |
| if (method != null) { |
| return MetaClassHelper.doMethodInvoke(object, method, arguments); |
| } else { |
| // if no method was found, try to find a closure defined as a field of the class and run it |
| Object value = null; |
| final MetaProperty metaProperty = this.getMetaProperty(theCachedClass, methodName, false, 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); |
| } |
| |
| return invokeMissingMethod(object, methodName, originalArguments); |
| } |
| } |
| |
| 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, Class[] arguments, boolean isCallToSuper) { |
| // lets try use the cache to find the method |
| if (GroovyCategorySupport.hasCategoryInAnyThread() && !isCallToSuper) { |
| return getMethodWithoutCaching(sender, methodName, arguments, isCallToSuper); |
| } else { |
| MethodKey methodKey = new DefaultMethodKey(sender, methodName, arguments, isCallToSuper); |
| MetaMethod method = (MetaMethod) methodCache.get(methodKey); |
| if (method == null) { |
| method = getMethodWithoutCaching(sender, methodName, arguments, isCallToSuper); |
| if (method == null) |
| method = NULL_METHOD; |
| cacheInstanceMethod(methodKey, method); |
| if (method == NULL_METHOD) |
| method = null; |
| } else { |
| if (method == NULL_METHOD) |
| method = null; |
| } |
| return method; |
| } |
| } |
| |
| protected void cacheInstanceMethod(MethodKey key, MetaMethod method) { |
| if (method != null && method.isCacheable()) { |
| methodCache.put(key, method); |
| } |
| } |
| |
| protected void cacheStaticMethod(MethodKey key, MetaMethod method) { |
| if (method != null && method.isCacheable()) { |
| staticMethodCache.put(key, method); |
| } |
| } |
| |
| |
| public Constructor retrieveConstructor(Class[] arguments) { |
| CachedConstructor constructor = (CachedConstructor) chooseMethod("<init>", constructors, arguments, false); |
| if (constructor != null) { |
| return constructor.cachedConstructor; |
| } |
| constructor = (CachedConstructor) chooseMethod("<init>", constructors, arguments, true); |
| if (constructor != null) { |
| return constructor.cachedConstructor; |
| } |
| return null; |
| } |
| |
| public MetaMethod retrieveStaticMethod(String methodName, Class[] arguments) { |
| MethodKey methodKey = new DefaultMethodKey(theClass, methodName, arguments, false); |
| MetaMethod method = (MetaMethod) staticMethodCache.get(methodKey); |
| if (method == null) { |
| method = pickStaticMethod(methodName, arguments); |
| cacheStaticMethod(methodKey.createCopy(), method); |
| } |
| return method; |
| } |
| |
| 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, false); |
| } |
| return method; |
| } |
| |
| public Object invokeStaticMethod(Object object, String methodName, Object[] arguments) { |
| checkInitalised(); |
| if (LOG.isLoggable(Level.FINER)) { |
| MetaClassHelper.logMethodCall(object, methodName, arguments); |
| } |
| |
| 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, argClasses); |
| // lets try use the cache to find the method |
| |
| if (method != null) { |
| unwrap(arguments); |
| return MetaClassHelper.doMethodInvoke(object, method, 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(); |
| unwrap(arguments); |
| |
| Class superClass = sender.getSuperclass(); |
| while (superClass != Object.class && superClass != null) { |
| MetaClass mc = registry.getMetaClass(superClass); |
| method = mc.getStaticMetaMethod(methodName, argClasses); |
| if (method != null) return MetaClassHelper.doMethodInvoke(object, method, 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(); |
| } |
| |
| 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; |
| Object methods = getStaticMethods(theClass, methodName); |
| |
| if (!(methods instanceof FastArray) || !((FastArray)methods).isEmpty()) { |
| method = (MetaMethod) chooseMethod(methodName, methods, arguments, false); |
| } |
| 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), true); |
| } |
| 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 numberOfCosntructors, Object[] arguments) { |
| //TODO: that is just a quick prototype, not the real thing! |
| if (numberOfCosntructors != constructors.size()) { |
| throw new IncompatibleClassChangeError("the number of constructors during runtime and compile time for " + |
| this.theClass.getName() + " do not match. Expected " + numberOfCosntructors + " but got " + constructors.size()); |
| } |
| |
| if (arguments == null) arguments = EMPTY_ARGUMENTS; |
| Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); |
| unwrap(arguments); |
| CachedConstructor constructor = (CachedConstructor) chooseMethod("<init>", constructors, argClasses, false); |
| if (constructor == null) { |
| constructor = (CachedConstructor) chooseMethod("<init>", constructors, argClasses, true); |
| } |
| 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); |
| unwrap(arguments); |
| CachedConstructor constructor = (CachedConstructor) chooseMethod("<init>", constructors, argClasses, false); |
| if (constructor != null) { |
| return doConstructorInvoke(at, constructor, arguments, true); |
| } |
| constructor = (CachedConstructor) chooseMethod("<init>", constructors, argClasses, true); |
| if (constructor != null) { |
| return doConstructorInvoke(at, constructor, arguments, true); |
| } |
| |
| if (arguments.length == 1) { |
| Object firstArgument = arguments[0]; |
| if (firstArgument instanceof Map) { |
| constructor = (CachedConstructor) chooseMethod("<init>", constructors, MetaClassHelper.EMPTY_TYPE_ARRAY, false); |
| if (constructor != null) { |
| Object bean = doConstructorInvoke(at, constructor, MetaClassHelper.EMPTY_ARRAY, true); |
| 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(ReflectionCache.getCachedClass(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.hasCategoryInAnyThread()) { |
| String getterName = "get" + MetaClassHelper.capitalize(name); |
| 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.hasCategoryInAnyThread()) { |
| 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 MetaClassHelper.doMethodInvoke(object, method, 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); |
| } |
| |
| |
| private MetaMethod getCategoryMethodGetter(Class sender, String name, boolean useLongVersion) { |
| List possibleGenericMethods = GroovyCategorySupport.getCategoryMethods(sender, name); |
| if (possibleGenericMethods != null) { |
| for (Iterator iter = possibleGenericMethods.iterator(); iter.hasNext();) { |
| MetaMethod mmethod = (MetaMethod) iter.next(); |
| CachedClass[] paramTypes = mmethod.getParameterTypes(); |
| if (useLongVersion) { |
| if (paramTypes.length == 1 && paramTypes[0].getCachedClass() == 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(sender, name); |
| if (possibleGenericMethods != null) { |
| for (Iterator iter = possibleGenericMethods.iterator(); iter.hasNext();) { |
| MetaMethod mmethod = (MetaMethod) iter.next(); |
| CachedClass[] paramTypes = mmethod.getParameterTypes(); |
| if (useLongVersion) { |
| if (paramTypes.length == 2 && paramTypes[0].getCachedClass() == 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 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 MetaFieldProperty) 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 NewInstanceMetaMethod) { |
| getter = false; |
| } |
| if (mp.getSetter() == null || mp.getSetter() instanceof NewInstanceMetaMethod) { |
| setter = false; |
| } |
| if (!setter && !getter) continue; |
| 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) { |
| 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) && |
| 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].getCachedClass(); |
| } |
| 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 superClasses = new LinkedList(); |
| 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 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 MetaFieldProperty) { |
| MetaFieldProperty mfp = (MetaFieldProperty) 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 MetaFieldProperty 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.getCachedClass().isAssignableFrom(sclass.getCachedClass())) continue; |
| SingleKeyHashMap sPropertyIndex = classPropertyIndex.getNotNull(sclass); |
| copyNonPrivateFields(iPropertyIndex, sPropertyIndex); |
| } |
| } |
| } |
| |
| private void inheritFields(LinkedList superClasses) { |
| SingleKeyHashMap last = null; |
| for (Iterator iter = superClasses.iterator(); iter.hasNext();) { |
| CachedClass klass = (CachedClass) iter.next(); |
| 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 (int i = 0; i < fields.length; i++) { |
| MetaFieldProperty mfp = MetaFieldProperty.create(fields[i]); |
| propertyIndex.put(fields[i].getName(), mfp); |
| } |
| } |
| |
| private void copyNonPrivateFields(SingleKeyHashMap from, SingleKeyHashMap to) { |
| for (ComplexKeyHashMap.EntryIterator iter = from.getEntrySetIterator(); iter.hasNext();) { |
| SingleKeyHashMap.Entry entry = (SingleKeyHashMap.Entry) iter.next(); |
| MetaFieldProperty mfp = (MetaFieldProperty) entry.getValue(); |
| if (!Modifier.isPublic(mfp.getModifiers()) && !Modifier.isProtected(mfp.getModifiers())) continue; |
| to.put(entry.getKey(), mfp); |
| } |
| } |
| |
| private void applyStrayPropertyMethods(LinkedList superClasses, Index classPropertyIndex, boolean isThis) { |
| // now look for any stray getters that may be used to define a property |
| for (Iterator iter = superClasses.iterator(); iter.hasNext();) { |
| CachedClass klass = (CachedClass) iter.next(); |
| MetaMethodIndex.Header header = metaMethodIndex.getHeader(klass.getCachedClass()); |
| 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() < 4) continue; |
| // possible getter/setter? |
| boolean isGetter = methodName.startsWith("get"); |
| boolean isSetter = methodName.startsWith("set"); |
| if (!isGetter && !isSetter) continue; |
| |
| MetaMethod propertyMethod = findPropertyMethod(isThis ? e.methods : e.methodsForSuper, isGetter); |
| if (propertyMethod == null) continue; |
| |
| String propName = getPropName(methodName); |
| createMetaBeanProperty(propertyIndex, propName, isGetter, propertyMethod); |
| } |
| } |
| } |
| |
| private static final HashMap propNames = new HashMap(1024); |
| |
| private String getPropName(String methodName) { |
| String name = (String) propNames.get(methodName); |
| if (name != null) |
| return name; |
| |
| synchronized (propNames) { |
| // get the name of the property |
| final int len = methodName.length() - 3; |
| char[] pn = new char[len]; |
| methodName.getChars(3, 3 + len, pn, 0); |
| pn[0] = Character.toLowerCase(pn[0]); |
| String propName = new String(pn); |
| 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].getCachedClass(), |
| null, propertyMethod); |
| } |
| } else { |
| MetaBeanProperty mbp; |
| MetaFieldProperty mfp; |
| if (mp instanceof MetaBeanProperty) { |
| mbp = (MetaBeanProperty) mp; |
| mfp = mbp.getField(); |
| } else if (mp instanceof MetaFieldProperty) { |
| mfp = (MetaFieldProperty) 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); |
| } |
| |
| private void applyPropertyDescriptors(PropertyDescriptor[] propertyDescriptors) { |
| // now iterate over the map of property descriptors and generate |
| // MetaBeanProperty objects |
| for (int i = 0; i < propertyDescriptors.length; i++) { |
| PropertyDescriptor pd = propertyDescriptors[i]; |
| |
| // 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) |
| getter = findMethod(CachedMethod.find(method)); |
| else |
| getter = null; |
| |
| // get the setter method |
| MetaMethod setter; |
| method = pd.getWriteMethod(); |
| if (method != null) |
| setter = findMethod(CachedMethod.find(method)); |
| 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 |
| MetaFieldProperty field; |
| MetaProperty old = (MetaProperty) propertyMap.get(mp.getName()); |
| if (old != null) { |
| if (old instanceof MetaBeanProperty) { |
| field = ((MetaBeanProperty) old).getField(); |
| } else { |
| field = (MetaFieldProperty) 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(); |
| |
| //---------------------------------------------------------------------- |
| // turn setProperty on a Map to put on the Map itself |
| //---------------------------------------------------------------------- |
| if (!isStatic && this.isMap) { |
| ((Map) object).put(name, newValue); |
| return; |
| } |
| |
| |
| MetaMethod method = null; |
| Object[] arguments = null; |
| |
| //---------------------------------------------------------------------- |
| // setter |
| //---------------------------------------------------------------------- |
| MetaProperty mp = getMetaProperty(ReflectionCache.getCachedClass(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.hasCategoryInAnyThread()) { |
| String getterName = "set" + MetaClassHelper.capitalize(name); |
| MetaMethod categoryMethod = getCategoryMethodSetter(sender, getterName, false); |
| if (categoryMethod != null) { |
| method = categoryMethod; |
| arguments = new Object[]{newValue}; |
| } |
| } |
| |
| //---------------------------------------------------------------------- |
| // listener method |
| //---------------------------------------------------------------------- |
| boolean ambigousListener = false; |
| if (method == null) { |
| method = (MetaMethod) listeners.get(name); |
| ambigousListener = method == AMBIGOUS_LISTENER_METHOD; |
| if (method != null && |
| !ambigousListener && |
| newValue instanceof Closure) { |
| // lets create a dynamic proxy |
| Object proxy = Proxy.newProxyInstance( |
| theClass.getClassLoader(), |
| new Class[]{method.getParameterTypes()[0].getCachedClass()}, |
| new ConvertedClosure((Closure) newValue, name)); |
| arguments = new Object[]{proxy}; |
| newValue = proxy; |
| } else { |
| method = null; |
| } |
| } |
| |
| //---------------------------------------------------------------------- |
| // field |
| //---------------------------------------------------------------------- |
| if (method == null && field != null) { |
| field.setProperty(object, newValue); |
| return; |
| } |
| |
| //---------------------------------------------------------------------- |
| // generic set method |
| //---------------------------------------------------------------------- |
| // check for a generic get method provided through a category |
| if (method == null && !useSuper && !isStatic && GroovyCategorySupport.hasCategoryInAnyThread()) { |
| 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].getCachedClass()); |
| arguments[0] = newValue; |
| } else { |
| newValue = DefaultTypeTransformation.castToType( |
| newValue, |
| method.getParameterTypes()[1].getCachedClass()); |
| arguments[1] = newValue; |
| } |
| MetaClassHelper.doMethodInvoke(object, method, arguments); |
| return; |
| } |
| |
| //---------------------------------------------------------------------- |
| // error due to missing method/field |
| //---------------------------------------------------------------------- |
| if (ambigousListener) { |
| 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 MetaProperty getMetaProperty(CachedClass clazz, String name, boolean useSuper, boolean useStatic) { |
| 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); |
| } |
| } |
| |
| |
| 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(ReflectionCache.getCachedClass(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(ReflectionCache.getCachedClass(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)) { |
| // lets 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 initalize, 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.getCachedClass())); |
| } |
| |
| private 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); |
| } |
| |
| private void addMetaMethodToSuperIndex(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.methodsForSuper = metaMethodIndex.addMethodToList(e.methodsForSuper, method); |
| } |
| |
| private void checkIfStdMethod(MetaMethod method) { |
| if (isGenericGetMethod(method) && genericGetMethod == null) { |
| genericGetMethod = method; |
| } else if (MetaClassHelper.isGenericSetMethod(method) && genericSetMethod == null) { |
| genericSetMethod = method; |
| } |
| if (propertyMissingGet == null && 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].getCachedClass() == String.class |
| && parameterTypes[1].getCachedClass() == Object.class) { |
| methodMissing = 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().getCachedClass(); |
| Class categoryMethodClass = categoryMethod.getDeclaringClass().getCachedClass(); |
| |
| 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.cachedMethod)) { |
| return method; |
| } |
| } |
| } |
| else { |
| MetaMethod method = (MetaMethod) methods; |
| if (method.isMethod(aMethod.cachedMethod)) { |
| return method; |
| } |
| } |
| //log.warning("Creating reflection based dispatcher for: " + aMethod); |
| synchronized (aMethod.cachedClass) { |
| return aMethod.getReflectionMetaMethod(); |
| } |
| } |
| |
| |
| private static Object doConstructorInvoke(final Class at, CachedConstructor constructor, Object[] argumentArray, boolean setAccessible) { |
| if (LOG.isLoggable(Level.FINER)) { |
| MetaClassHelper.logMethodCall(constructor.cachedConstructor.getDeclaringClass(), constructor.cachedConstructor.getName(), argumentArray); |
| } |
| |
| // if (setAccessible) { |
| // // To fix JIRA 435 |
| // // Every constructor should be opened to the accessible classes. |
| // final boolean accessible = MetaClassHelper.accessibleToConstructor(at, constructor); |
| // final Constructor ctor = constructor; |
| // AccessController.doPrivileged(new PrivilegedAction() { |
| // public Object run() { |
| // ctor.setAccessible(accessible); |
| // return null; |
| // } |
| // }); |
| // } |
| return MetaClassHelper.doConstructorInvoke(constructor, argumentArray); |
| } |
| |
| /** |
| * 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 |
| */ |
| private Object chooseMethod(String methodName, Object methodOrList, Class[] arguments, boolean coerce) { |
| if (methodOrList instanceof MetaMethod) { |
| if (MetaClassHelper.isValidMethod(methodOrList, arguments, coerce)) { |
| return methodOrList; |
| } |
| return null; |
| } |
| |
| FastArray methods = (FastArray) methodOrList; |
| int methodCount = methods.size(); |
| if (methodCount <= 0) { |
| return null; |
| } else if (methodCount == 1) { |
| Object method = methods.get(0); |
| if (MetaClassHelper.isValidMethod(method, arguments, coerce)) { |
| 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 { |
| List matchingMethods = new ArrayList(methods.size()); |
| |
| 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 (MetaClassHelper.isValidMethod(method, arguments, coerce)) { |
| matchingMethods.add(method); |
| } |
| } |
| if (matchingMethods.isEmpty()) { |
| return null; |
| } else if (matchingMethods.size() == 1) { |
| return matchingMethods.get(0); |
| } |
| return chooseMostSpecificParams(methodName, 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(); |
| Class[] paramTypes = MetaClassHelper.getParameterTypes(method).getNativeParameterTypes(); |
| 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 --> ambigous! |
| String msg = "Ambiguous method overloading for method "; |
| msg += theClass.getName() + "#" + name; |
| msg += ".\nCannot resolve which method to invoke for "; |
| msg += InvokerHelper.toString(arguments); |
| msg += " due to overlapping prototypes between:"; |
| for (Iterator iter = matches.iterator(); iter.hasNext();) { |
| Class[] types = MetaClassHelper.getParameterTypes(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].getCachedClass() == String.class; |
| } |
| return false; |
| } |
| |
| |
| public synchronized void initialize() { |
| if (!isInitialized()) { |
| fillMethodIndex(); |
| addProperties(); |
| initialized = true; |
| } |
| } |
| |
| private void addProperties() { |
| BeanInfo info; |
| // introspect |
| try { |
| info = (BeanInfo) AccessController.doPrivileged(new PrivilegedExceptionAction() { |
| public Object run() throws IntrospectionException { |
| return Introspector.getBeanInfo(theClass); |
| } |
| }); |
| } catch (PrivilegedActionException pae) { |
| throw new GroovyRuntimeException("exception while 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 (int i = 0; i < eventDescriptors.length; i++) { |
| EventSetDescriptor descriptor = eventDescriptors[i]; |
| Method[] listenerMethods = descriptor.getListenerMethods(); |
| for (int j = 0; j < listenerMethods.length; j++) { |
| Method listenerMethod = listenerMethods[j]; |
| final ReflectionMetaMethod metaMethod = new ReflectionMetaMethod(CachedMethod.find(descriptor.getAddListenerMethod())); |
| addToAllMethodsIfPublic(metaMethod); |
| String name = listenerMethod.getName(); |
| if (listeners.containsKey(name)) { |
| listeners.put(name, AMBIGOUS_LISTENER_METHOD); |
| } else { |
| listeners.put(name, metaMethod); |
| } |
| } |
| } |
| } |
| |
| private void addToAllMethodsIfPublic(MetaMethod metaMethod) { |
| if (Modifier.isPublic(metaMethod.getModifiers())) |
| allMethods.add(metaMethod); |
| } |
| |
| public List getMethods() { |
| return allMethods; |
| } |
| |
| public List getMetaMethods() { |
| return new ArrayList(newGroovyMethodsSet); |
| } |
| |
| protected void dropStaticMethodCache(String name) { |
| for (Iterator it = staticMethodCache.keySet().iterator(); it.hasNext();) { |
| MethodKey k = (MethodKey) it.next(); |
| if (name.equals(k.getName())) |
| it.remove(); |
| } |
| } |
| |
| protected void dropMethodCache(String name) { |
| for (Iterator it = methodCache.keySet().iterator(); it.hasNext();) { |
| MethodKey k = (MethodKey) it.next(); |
| if (name.equals(k.getName())) |
| it.remove(); |
| } |
| } |
| |
| 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() { |
| staticMethodCache.clear(); |
| methodCache.clear(); |
| } |
| |
| 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; |
| } |
| } |
| |
| } |