| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package groovy.lang; |
| |
| import org.apache.groovy.internal.util.UncheckedThrow; |
| import org.apache.groovy.lang.GroovyObjectHelper; |
| import org.apache.groovy.runtime.ObjectUtil; |
| import org.apache.groovy.util.BeanUtils; |
| import org.apache.groovy.util.SystemUtil; |
| import org.codehaus.groovy.GroovyBugError; |
| import org.codehaus.groovy.ast.ClassNode; |
| import org.codehaus.groovy.classgen.asm.BytecodeHelper; |
| import org.codehaus.groovy.control.CompilationUnit; |
| import org.codehaus.groovy.control.Phases; |
| import org.codehaus.groovy.reflection.CachedClass; |
| import org.codehaus.groovy.reflection.CachedConstructor; |
| import org.codehaus.groovy.reflection.CachedField; |
| import org.codehaus.groovy.reflection.CachedMethod; |
| import org.codehaus.groovy.reflection.ClassInfo; |
| import org.codehaus.groovy.reflection.GeneratedMetaMethod; |
| import org.codehaus.groovy.reflection.ParameterTypes; |
| import org.codehaus.groovy.reflection.ReflectionCache; |
| import org.codehaus.groovy.reflection.ReflectionUtils; |
| import org.codehaus.groovy.reflection.android.AndroidSupport; |
| import org.codehaus.groovy.runtime.ArrayTypeUtils; |
| import org.codehaus.groovy.runtime.ConvertedClosure; |
| import org.codehaus.groovy.runtime.CurriedClosure; |
| import org.codehaus.groovy.runtime.DefaultGroovyMethods; |
| import org.codehaus.groovy.runtime.FormatHelper; |
| import org.codehaus.groovy.runtime.GeneratedClosure; |
| import org.codehaus.groovy.runtime.GroovyCategorySupport; |
| import org.codehaus.groovy.runtime.GroovyCategorySupport.CategoryMethod; |
| import org.codehaus.groovy.runtime.InvokerHelper; |
| import org.codehaus.groovy.runtime.InvokerInvocationException; |
| import org.codehaus.groovy.runtime.MetaClassHelper; |
| import org.codehaus.groovy.runtime.MethodClosure; |
| import org.codehaus.groovy.runtime.callsite.AbstractCallSite; |
| import org.codehaus.groovy.runtime.callsite.CallSite; |
| import org.codehaus.groovy.runtime.callsite.ConstructorSite; |
| import org.codehaus.groovy.runtime.callsite.MetaClassConstructorSite; |
| import org.codehaus.groovy.runtime.callsite.PogoMetaClassSite; |
| import org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite; |
| import org.codehaus.groovy.runtime.callsite.PojoMetaClassSite; |
| import org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite; |
| import org.codehaus.groovy.runtime.callsite.StaticMetaClassSite; |
| import org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite; |
| import org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod; |
| import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl; |
| import org.codehaus.groovy.runtime.metaclass.MetaMethodIndex; |
| import org.codehaus.groovy.runtime.metaclass.MethodMetaProperty.GetBeanMethodMetaProperty; |
| import org.codehaus.groovy.runtime.metaclass.MethodMetaProperty.GetMethodMetaProperty; |
| import org.codehaus.groovy.runtime.metaclass.MethodSelectionException; |
| import org.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack; |
| import org.codehaus.groovy.runtime.metaclass.MissingMethodExecutionFailed; |
| import org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack; |
| import org.codehaus.groovy.runtime.metaclass.MixinInstanceMetaMethod; |
| import org.codehaus.groovy.runtime.metaclass.MultipleSetterProperty; |
| import org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod; |
| import org.codehaus.groovy.runtime.metaclass.NewMetaMethod; |
| import org.codehaus.groovy.runtime.metaclass.NewStaticMetaMethod; |
| import org.codehaus.groovy.runtime.metaclass.TransformMetaMethod; |
| import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; |
| import org.codehaus.groovy.runtime.typehandling.NumberMathModificationInfo; |
| import org.codehaus.groovy.runtime.wrappers.Wrapper; |
| import org.codehaus.groovy.util.ComplexKeyHashMap; |
| import org.codehaus.groovy.util.FastArray; |
| import org.codehaus.groovy.util.SingleKeyHashMap; |
| import org.codehaus.groovy.vmplugin.VMPlugin; |
| import org.codehaus.groovy.vmplugin.VMPluginFactory; |
| import org.objectweb.asm.Opcodes; |
| |
| import java.beans.BeanInfo; |
| import java.beans.EventSetDescriptor; |
| import java.beans.Introspector; |
| import java.beans.PropertyDescriptor; |
| import java.lang.invoke.MethodHandle; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.lang.reflect.Proxy; |
| import java.net.URL; |
| import java.security.PrivilegedActionException; |
| import java.security.PrivilegedExceptionAction; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.function.BiConsumer; |
| import java.util.function.Function; |
| |
| import static groovy.lang.Tuple.tuple; |
| import static java.lang.Character.isUpperCase; |
| import static org.apache.groovy.util.Arrays.concat; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.inSamePackage; |
| import static org.codehaus.groovy.reflection.ReflectionCache.isAssignableFrom; |
| import static org.codehaus.groovy.reflection.ReflectionUtils.parameterTypeMatches; |
| |
| /** |
| * Allows methods to be dynamically added to existing classes at runtime |
| * |
| * @see groovy.lang.MetaClass |
| */ |
| public class MetaClassImpl implements MetaClass, MutableMetaClass { |
| |
| public static final Object[] EMPTY_ARGUMENTS = MetaClassHelper.EMPTY_ARRAY; |
| |
| protected static final String STATIC_METHOD_MISSING = "$static_methodMissing"; |
| protected static final String STATIC_PROPERTY_MISSING = "$static_propertyMissing"; |
| protected static final String METHOD_MISSING = "methodMissing"; |
| protected static final String PROPERTY_MISSING = "propertyMissing"; |
| protected static final String INVOKE_METHOD_METHOD = "invokeMethod"; |
| |
| private static final String CALL_METHOD = "call"; |
| private static final String DO_CALL_METHOD = "doCall"; |
| private static final String CONSTRUCTOR_NAME = "<init>"; |
| private static final String GET_PROPERTY_METHOD = "getProperty"; |
| private static final String SET_PROPERTY_METHOD = "setProperty"; |
| |
| private static final Class[] METHOD_MISSING_ARGS = new Class[]{String.class, Object.class}; |
| private static final Class[] GETTER_MISSING_ARGS = new Class[]{String.class}; |
| private static final Class[] SETTER_MISSING_ARGS = METHOD_MISSING_ARGS; |
| private static final MetaMethod AMBIGUOUS_LISTENER_METHOD = new DummyMetaMethod(); |
| private static final Comparator<CachedClass> CACHED_CLASS_NAME_COMPARATOR = Comparator.comparing(CachedClass::getName); |
| private static final boolean PERMISSIVE_PROPERTY_ACCESS = SystemUtil.getBooleanSafe("groovy.permissive.property.access"); |
| private static final VMPlugin VM_PLUGIN = VMPluginFactory.getPlugin(); |
| |
| protected final Class theClass; |
| protected final CachedClass theCachedClass; |
| protected final boolean isGroovyObject; |
| protected final boolean isMap; |
| protected final MetaMethodIndex metaMethodIndex; |
| |
| private final Map<CachedClass, LinkedHashMap<String, MetaProperty>> classPropertyIndex = new LinkedHashMap<>(); |
| private final Map<String, MetaProperty> staticPropertyIndex = new LinkedHashMap<>(); |
| private final Map<String, MetaMethod> listeners = new LinkedHashMap<>(); |
| private final List<MetaMethod> allMethods = new ArrayList<>(); |
| // we only need one of these that can be reused over and over. |
| private final MetaProperty arrayLengthProperty = new MetaArrayLengthProperty(); |
| private final Map<CachedClass, LinkedHashMap<String, MetaProperty>> classPropertyIndexForSuper = new LinkedHashMap<>(); |
| private final Set<MetaMethod> newGroovyMethodsSet = new LinkedHashSet<>(); |
| private final MetaMethod[] myNewMetaMethods; |
| private final MetaMethod[] additionalMetaMethods; |
| |
| protected MetaMethod getPropertyMethod; |
| protected MetaMethod invokeMethodMethod; |
| protected MetaMethod setPropertyMethod; |
| protected MetaClassRegistry registry; |
| private ClassNode classNode; |
| private FastArray constructors; |
| private volatile boolean initialized; |
| private MetaMethod genericGetMethod; |
| private MetaMethod genericSetMethod; |
| private MetaMethod propertyMissingGet; |
| private MetaMethod propertyMissingSet; |
| private MetaMethod methodMissing; |
| private MetaMethodIndex.Header mainClassMethodHeader; |
| private boolean permissivePropertyAccess = PERMISSIVE_PROPERTY_ACCESS; |
| |
| /** |
| * Constructor |
| * |
| * @param theClass The class this is the metaclass dor |
| * @param add The methods for this class |
| */ |
| public MetaClassImpl(final Class theClass, final MetaMethod[] add) { |
| this.theClass = theClass; |
| theCachedClass = ReflectionCache.getCachedClass(theClass); |
| this.isGroovyObject = GroovyObject.class.isAssignableFrom(theClass); |
| this.isMap = Map.class.isAssignableFrom(theClass); |
| this.registry = GroovySystem.getMetaClassRegistry(); |
| metaMethodIndex = new MetaMethodIndex(theCachedClass); |
| final MetaMethod[] metaMethods = theCachedClass.getNewMetaMethods(); |
| if (add != null && add.length != 0) { |
| myNewMetaMethods = concat(metaMethods, add); |
| additionalMetaMethods = metaMethods; |
| } else { |
| myNewMetaMethods = metaMethods; |
| additionalMetaMethods = MetaMethod.EMPTY_ARRAY; |
| } |
| } |
| |
| /** |
| * Constructor that sets the methods to null |
| * |
| * @param theClass The class this is the metaclass dor |
| */ |
| public MetaClassImpl(final Class theClass) { |
| this(theClass, null); |
| } |
| |
| /** |
| * Constructor with registry |
| * |
| * @param registry The metaclass registry for this MetaClass |
| * @param theClass The class |
| * @param add The methods |
| */ |
| public MetaClassImpl(final MetaClassRegistry registry, final Class theClass, final MetaMethod[] add) { |
| this(theClass, add); |
| this.registry = registry; |
| this.constructors = new FastArray(theCachedClass.getConstructors()); |
| } |
| |
| /** |
| * Constructor with registry setting methods to null |
| * |
| * @param registry The metaclass registry for this MetaClass |
| * @param theClass The class |
| */ |
| public MetaClassImpl(MetaClassRegistry registry, final Class theClass) { |
| this(registry, theClass, null); |
| } |
| |
| /** |
| * Returns the cached class for this metaclass |
| * |
| * @return The cached class. |
| */ |
| public final CachedClass getTheCachedClass() { |
| return theCachedClass; |
| } |
| |
| /** |
| * Returns the registry for this metaclass |
| * |
| * @return The registry |
| */ |
| public MetaClassRegistry getRegistry() { |
| return registry; |
| } |
| |
| /** |
| * @see MetaObjectProtocol#respondsTo(Object, String, Object[]) |
| */ |
| @Override |
| public List<MetaMethod> respondsTo(final Object obj, final String name, final Object[] argTypes) { |
| MetaMethod m = getMetaMethod(name, MetaClassHelper.castArgumentsToClassArray(argTypes)); |
| return (m != null ? Collections.singletonList(m) : Collections.emptyList()); |
| } |
| |
| /** |
| * @see MetaObjectProtocol#respondsTo(Object, String) |
| */ |
| @Override |
| public List<MetaMethod> respondsTo(final Object obj, final String name) { |
| final Object o = getMethods(getTheClass(), name, false); |
| if (o instanceof FastArray) { |
| return ((FastArray) o).toList(); |
| } |
| return Collections.<MetaMethod>singletonList((MetaMethod) o); |
| } |
| |
| /** |
| * @see MetaObjectProtocol#hasProperty(Object, String) |
| */ |
| @Override |
| public MetaProperty hasProperty(final Object obj, final String name) { |
| return getMetaProperty(name); |
| } |
| |
| /** |
| * @see MetaObjectProtocol#getMetaProperty(String) |
| */ |
| @Override |
| public MetaProperty getMetaProperty(final String name) { |
| MetaProperty metaProperty = null; |
| |
| LinkedHashMap<String, MetaProperty> propertyMap = classPropertyIndex.computeIfAbsent(theCachedClass, k -> new LinkedHashMap<>()); |
| metaProperty = propertyMap.get(name); |
| if (metaProperty == null) { |
| metaProperty = staticPropertyIndex.get(name); |
| if (metaProperty == null) { |
| propertyMap = classPropertyIndexForSuper.computeIfAbsent(theCachedClass, k -> new LinkedHashMap<>()); |
| metaProperty = propertyMap.get(name); |
| if (metaProperty == null) { |
| MetaBeanProperty property = findPropertyInClassHierarchy(name, theCachedClass); |
| if (property != null) { |
| onSuperPropertyFoundInHierarchy(property); |
| metaProperty = property; |
| } |
| } |
| } |
| } |
| |
| return metaProperty; |
| } |
| |
| /** |
| * @see MetaObjectProtocol#getStaticMetaMethod(String, Object[]) |
| */ |
| @Override |
| public MetaMethod getStaticMetaMethod(final String name, final Object[] argTypes) { |
| return pickStaticMethod(name, MetaClassHelper.castArgumentsToClassArray(argTypes)); |
| } |
| |
| /** |
| * @see MetaObjectProtocol#getMetaMethod(String, Object[]) |
| */ |
| @Override |
| public MetaMethod getMetaMethod(final String name, final Object[] argTypes) { |
| return pickMethod(name, MetaClassHelper.castArgumentsToClassArray(argTypes)); |
| } |
| |
| /** |
| * Returns the class this object this is the metaclass of. |
| * |
| * @return The class contained by this metaclass |
| */ |
| @Override |
| public Class getTheClass() { |
| return this.theClass; |
| } |
| |
| /** |
| * Return whether the class represented by this metaclass instance is an instance of the GroovyObject class |
| * |
| * @return true if this is a groovy class, false otherwise. |
| */ |
| public boolean isGroovyObject() { |
| return isGroovyObject; |
| } |
| |
| private void fillMethodIndex() { |
| mainClassMethodHeader = metaMethodIndex.getHeader(theClass); |
| |
| Set<CachedClass> interfaces = theCachedClass.getInterfaces(); |
| List<CachedClass> superClasses = getSuperClasses(); // in reverse order |
| CachedClass firstGroovySuper = calcFirstGroovySuperClass(superClasses); |
| |
| for (CachedClass c : interfaces) { |
| for (CachedMethod m : c.getMethods()) { |
| if (c == theCachedClass || (m.isPublic() && !m.isStatic())) { // GROOVY-8164 |
| addMetaMethodToIndex(m, mainClassMethodHeader); |
| } |
| } |
| } |
| |
| populateMethods(superClasses, firstGroovySuper); |
| |
| inheritInterfaceNewMetaMethods(interfaces); |
| |
| if (isGroovyObject) { |
| metaMethodIndex.copyMethodsToSuper(); // methods --> methodsForSuper |
| connectMultimethods(superClasses, firstGroovySuper); |
| removeMultimethodsOverloadedWithPrivateMethods(); |
| replaceWithMOPCalls(theCachedClass.mopMethods); |
| } |
| } |
| |
| private void populateMethods(final List<CachedClass> superClasses, final CachedClass firstGroovySuper) { |
| MetaMethodIndex.Header header = metaMethodIndex.getHeader(firstGroovySuper.getTheClass()); |
| CachedClass c; |
| Iterator<CachedClass> iter = superClasses.iterator(); |
| while (iter.hasNext()) { |
| c = iter.next(); |
| |
| for (final CachedMethod metaMethod : c.getMethods()) { |
| addToAllMethodsIfPublic(metaMethod); |
| if (!metaMethod.isPrivate() || c == firstGroovySuper) |
| addMetaMethodToIndex(metaMethod, header); |
| } |
| |
| for (final MetaMethod method : getNewMetaMethods(c)) { |
| if (!newGroovyMethodsSet.contains(method)) { |
| newGroovyMethodsSet.add(method); |
| addMetaMethodToIndex(method, header); |
| } |
| } |
| |
| if (c == firstGroovySuper) |
| break; |
| } |
| |
| MetaMethodIndex.Header last = header; |
| while (iter.hasNext()) { |
| c = iter.next(); |
| header = metaMethodIndex.getHeader(c.getTheClass()); |
| |
| if (last != null) { |
| metaMethodIndex.copyNonPrivateMethods(last, header); |
| } |
| last = header; |
| |
| for (final CachedMethod metaMethod : c.getMethods()) { |
| addToAllMethodsIfPublic(metaMethod); |
| addMetaMethodToIndex(metaMethod, header); |
| } |
| |
| for (final MetaMethod method : getNewMetaMethods(c)) { |
| if (method.getName().equals(CONSTRUCTOR_NAME) && !method.getDeclaringClass().equals(theCachedClass)) continue; |
| if (!newGroovyMethodsSet.contains(method)) { |
| newGroovyMethodsSet.add(method); |
| addMetaMethodToIndex(method, header); |
| } |
| } |
| } |
| } |
| |
| private MetaMethod[] getNewMetaMethods(final CachedClass c) { |
| if (theCachedClass != c) |
| return c.getNewMetaMethods(); |
| |
| return myNewMetaMethods; |
| } |
| |
| protected LinkedList<CachedClass> getSuperClasses() { |
| LinkedList<CachedClass> superClasses = new LinkedList<>(); |
| |
| if (theClass.isInterface()) { |
| superClasses.add(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() { |
| @Override |
| public boolean skipClass(final Class<?> clazz) { |
| return clazz == theClass; |
| } |
| |
| @Override |
| public void methodNameAction(final Class<?> clazz, final MetaMethodIndex.Entry e) { |
| if (e.methods == null) |
| return; |
| |
| boolean hasPrivate = false; |
| if (e.methods instanceof FastArray) { |
| FastArray methods = (FastArray) e.methods; |
| final int len = methods.size(); |
| final Object[] data = methods.getArray(); |
| for (int i = 0; i != len; ++i) { |
| MetaMethod method = (MetaMethod) data[i]; |
| if (method.isPrivate() && clazz == method.getDeclaringClass().getTheClass()) { |
| hasPrivate = true; |
| break; |
| } |
| } |
| } else { |
| MetaMethod method = (MetaMethod) e.methods; |
| if (method.isPrivate() && clazz == method.getDeclaringClass().getTheClass()) { |
| hasPrivate = true; |
| } |
| } |
| |
| if (!hasPrivate) return; |
| |
| // We have private methods for that name, so remove the |
| // multimethods. That is the same as in our index for |
| // super, so just copy the list from there. It is not |
| // possible to use a pointer here, because the methods |
| // in the index for super are replaced later by MOP |
| // methods like super$5$foo |
| final Object o = e.methodsForSuper; |
| if (o instanceof FastArray) { |
| e.methods = ((FastArray) o).copy(); |
| } else { |
| e.methods = o; |
| } |
| } |
| }; |
| mia.iterate(); |
| } |
| |
| private void replaceWithMOPCalls(final CachedMethod[] mopMethods) { |
| class MOPIter extends MethodIndexAction { |
| boolean useThis; |
| |
| @Override |
| public void methodNameAction(final Class<?> c, final MetaMethodIndex.Entry e) { |
| Object arrayOrMethod = (useThis ? e.methods : e.methodsForSuper); |
| if (arrayOrMethod instanceof FastArray) { |
| FastArray methods = (FastArray) arrayOrMethod; |
| Object[] data = methods.getArray(); |
| for (int i = 0; i < methods.size(); i += 1) { |
| MetaMethod method = (MetaMethod) data[i]; |
| int matchedMethod = mopArrayIndex(method, c); |
| if (matchedMethod >= 0) { |
| methods.set(i, mopMethods[matchedMethod]); |
| } else if (!useThis && !isDGM(method) && c == method.getDeclaringClass().getTheClass()) { |
| methods.remove(i--); // not fit for super usage |
| } |
| } |
| if (!useThis) { |
| int n = methods.size(); |
| if (n == 0) e.methodsForSuper = null; |
| else if (n == 1) e.methodsForSuper = data[0]; |
| } |
| } else if (arrayOrMethod != null) { |
| MetaMethod method = (MetaMethod) arrayOrMethod; |
| int matchedMethod = mopArrayIndex(method, c); |
| if (matchedMethod >= 0) { |
| if (useThis) e.methods = mopMethods[matchedMethod]; |
| else e.methodsForSuper = mopMethods[matchedMethod]; |
| } else if (!useThis && !isDGM(method) && c == method.getDeclaringClass().getTheClass()) { |
| e.methodsForSuper = null; // not fit for super usage |
| } |
| } |
| } |
| |
| private int mopArrayIndex(final MetaMethod method, final Class<?> c) { |
| if (mopMethods == null || mopMethods.length == 0) return -1; |
| if (isDGM(method) || (useThis ^ method.isPrivate())) return -1; |
| if (useThis) return mopArrayIndex(method, method.getMopName()); |
| // GROOVY-4922: Due to a numbering scheme change, find the super$number$methodName with |
| // the highest value. If we don't, no method may be found, leading to a stack overflow! |
| int distance = ReflectionCache.getCachedClass(c).getSuperClassDistance() - 1; |
| while (distance > 0) { |
| int index = mopArrayIndex(method, "super$" + distance + "$" + method.getName()); |
| if (index >= 0) return index; |
| distance -= 1; |
| } |
| return -1; |
| } |
| |
| private int mopArrayIndex(final MetaMethod method, final String mopName) { |
| int index = Arrays.binarySearch(mopMethods, mopName, CachedClass.CachedMethodComparatorWithString.INSTANCE); |
| if (index >= 0) { |
| int from = index, to = index; // include overloads in search |
| while (from > 0 && mopMethods[from - 1].getName().equals(mopName)) from -= 1; |
| while (to < mopMethods.length - 1 && mopMethods[to + 1].getName().equals(mopName)) to += 1; |
| |
| for (index = from; index <= to; index += 1) { |
| CachedClass[] params1 = mopMethods[index].getParameterTypes(); |
| CachedClass[] params2 = method.getParameterTypes(); |
| if (MetaMethod.equal(params1, params2)) { |
| return index; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| private boolean isDGM(final MetaMethod method) { |
| return method instanceof GeneratedMetaMethod || method instanceof NewMetaMethod; |
| } |
| } |
| MOPIter iter = new MOPIter(); |
| |
| // replace all calls for super with the correct MOP method |
| iter.useThis = false; |
| iter.iterate(); |
| |
| if (mopMethods == null || mopMethods.length == 0) return; |
| |
| // replace all calls for this with the correct MOP method |
| iter.useThis = true; |
| iter.iterate(); |
| } |
| |
| private void inheritInterfaceNewMetaMethods(final Set<CachedClass> interfaces) { |
| Method[] theClassMethods = null; |
| // add methods declared by DGM for interfaces |
| for (CachedClass face : interfaces) { |
| for (MetaMethod method : getNewMetaMethods(face)) { |
| boolean skip = false; |
| // skip DGM methods on an interface if the class already has the method |
| // but don't skip for GroovyObject-related methods as it breaks things :-( |
| if (method instanceof GeneratedMetaMethod && !isAssignableFrom(GroovyObject.class, method.getDeclaringClass().getTheClass())) { |
| final String generatedMethodName = method.getName(); |
| final CachedClass[] generatedMethodParameterTypes = method.getParameterTypes(); |
| for (Method m : (null == theClassMethods ? theClassMethods = theClass.getMethods() : theClassMethods)) { |
| if (generatedMethodName.equals(m.getName()) |
| // below not true for DGM#push and also co-variant return scenarios |
| //&& method.getReturnType().equals(m.getReturnType()) |
| && MetaMethod.equal(generatedMethodParameterTypes, m.getParameterTypes())) { |
| skip = true; |
| break; |
| } |
| } |
| } |
| if (!skip) { |
| newGroovyMethodsSet.add(method); |
| addMetaMethodToIndex(method, mainClassMethodHeader); |
| } |
| } |
| } |
| } |
| |
| private void connectMultimethods(final List<CachedClass> superClasses, final CachedClass firstGroovyClass) { |
| MetaMethodIndex.Header last = null; |
| for (ListIterator<CachedClass> iter = superClasses.listIterator(superClasses.size()); iter.hasPrevious(); ) { |
| CachedClass c = iter.previous(); |
| |
| MetaMethodIndex.Header methodIndex = metaMethodIndex.getHeader(c.getTheClass()); |
| // We don't copy DGM methods to superclasses' indexes |
| // The reason we can do that is particular set of DGM methods in use, |
| // if at some point we will define DGM method for some Groovy class or |
| // for a class derived from such, we will need to revise this condition. |
| // It saves us a lot of space and some noticeable time |
| if (last != null) metaMethodIndex.copyNonPrivateNonNewMetaMethods(last, methodIndex); |
| last = methodIndex; |
| |
| if (c == firstGroovyClass) |
| break; |
| } |
| } |
| |
| private CachedClass calcFirstGroovySuperClass(final List<CachedClass> superClasses) { |
| if (theCachedClass.isInterface) |
| return ReflectionCache.OBJECT_CLASS; |
| |
| CachedClass firstGroovy = null; |
| Iterator<CachedClass> iter = superClasses.iterator(); |
| while (iter.hasNext()) { |
| CachedClass c = iter.next(); |
| if (GroovyObject.class.isAssignableFrom(c.getTheClass())) { |
| firstGroovy = c; |
| break; |
| } |
| } |
| |
| if (firstGroovy == null) { |
| firstGroovy = theCachedClass; |
| } else if (firstGroovy.getTheClass() == GroovyObjectSupport.class && iter.hasNext()) { |
| firstGroovy = iter.next(); |
| if (firstGroovy.getTheClass() == Closure.class && iter.hasNext()) { |
| firstGroovy = iter.next(); |
| } |
| } |
| |
| return GroovyObject.class.isAssignableFrom(firstGroovy.getTheClass()) ? firstGroovy.getCachedSuperClass() : firstGroovy; |
| } |
| |
| /** |
| * Gets all the normal object methods of this class for the given name. |
| * |
| * @return object methods available from this class for given name |
| */ |
| private Object getMethods(final Class<?> sender, final String name, final boolean isCallToSuper) { |
| Object answer; |
| |
| final MetaMethodIndex.Entry entry = metaMethodIndex.getMethods(sender, name); |
| if (entry == null) { |
| answer = FastArray.EMPTY_LIST; |
| } else if (isCallToSuper) { |
| answer = entry.methodsForSuper; |
| } else { |
| answer = entry.methods; |
| } |
| |
| if (answer == null) answer = FastArray.EMPTY_LIST; |
| |
| if (!isCallToSuper) { |
| List<CategoryMethod> used = GroovyCategorySupport.getCategoryMethods(name); |
| if (used != null) { |
| FastArray arr; |
| if (answer instanceof MetaMethod) { |
| arr = new FastArray(); |
| arr.add(answer); |
| } else { |
| arr = ((FastArray) answer).copy(); |
| } |
| for (CategoryMethod cm : used) { |
| if (!cm.getDeclaringClass().getTheClass().isAssignableFrom(sender)) |
| continue; |
| filterMatchingMethodForCategory(arr, cm); |
| } |
| answer = arr; |
| } |
| } |
| return answer; |
| } |
| |
| /** |
| * Gets all the normal static methods of this class for the given name. |
| * |
| * @return static methods available from this class for given name |
| */ |
| private Object getStaticMethods(final Class<?> sender, final String name) { |
| final MetaMethodIndex.Entry entry = metaMethodIndex.getMethods(sender, name); |
| if (entry == null) |
| return FastArray.EMPTY_LIST; |
| Object answer = entry.staticMethods; |
| if (answer == null) |
| return FastArray.EMPTY_LIST; |
| return answer; |
| } |
| |
| /** |
| * Returns whether this MetaClassImpl has been modified. Since MetaClassImpl |
| * is not designed for modification this method always returns false |
| * |
| * @return false |
| */ |
| @Override |
| public boolean isModified() { |
| return false; // MetaClassImpl not designed for modification, just return false |
| } |
| |
| /** |
| * Adds an instance method to this metaclass. |
| * |
| * @param method The method to be added |
| */ |
| @Override |
| public void addNewInstanceMethod(final Method method) { |
| CachedMethod cachedMethod = CachedMethod.find(method); |
| NewInstanceMetaMethod newMethod = new NewInstanceMetaMethod(cachedMethod); |
| addNewInstanceMethodToIndex(newMethod, metaMethodIndex.getHeader(newMethod.getDeclaringClass().getTheClass())); |
| } |
| |
| private void addNewInstanceMethodToIndex(final MetaMethod newMethod, final MetaMethodIndex.Header header) { |
| if (!newGroovyMethodsSet.contains(newMethod)) { |
| newGroovyMethodsSet.add(newMethod); |
| addMetaMethodToIndex(newMethod, header); |
| } |
| } |
| |
| /** |
| * Adds a static method to this metaclass. |
| * |
| * @param method The method to be added |
| */ |
| @Override |
| public void addNewStaticMethod(final Method method) { |
| CachedMethod cachedMethod = CachedMethod.find(method); |
| NewStaticMetaMethod newMethod = new NewStaticMetaMethod(cachedMethod); |
| addNewStaticMethodToIndex(newMethod, metaMethodIndex.getHeader(newMethod.getDeclaringClass().getTheClass())); |
| } |
| |
| private void addNewStaticMethodToIndex(final MetaMethod newMethod, final MetaMethodIndex.Header header) { |
| if (!newGroovyMethodsSet.contains(newMethod)) { |
| newGroovyMethodsSet.add(newMethod); |
| addMetaMethodToIndex(newMethod, header); |
| } |
| } |
| |
| /** |
| * Invoke a method on the given object with the given arguments. |
| * |
| * @param object The object the method should be invoked on. |
| * @param methodName The name of the method to invoke. |
| * @param arguments The arguments to the invoked method as null, a Tuple, an array or a single argument of any type. |
| * @return The result of the method invocation. |
| */ |
| @Override |
| public Object invokeMethod(final Object object, final String methodName, final Object arguments) { |
| if (arguments == null) { |
| return invokeMethod(object, methodName, EMPTY_ARGUMENTS); |
| } |
| if (arguments instanceof Tuple) { |
| return invokeMethod(object, methodName, ((Tuple<?>) arguments).toArray()); |
| } |
| if (arguments instanceof Object[]) { |
| return invokeMethod(object, methodName, (Object[]) arguments); |
| } |
| return invokeMethod(object, methodName, new Object[]{arguments}); |
| } |
| |
| /** |
| * Invoke a missing method on the given object with the given arguments. |
| * |
| * @param instance The object the method should be invoked on. |
| * @param methodName The name of the method to invoke. |
| * @param arguments The arguments to the invoked method. |
| * @return The result of the method invocation. |
| */ |
| @Override |
| public Object invokeMissingMethod(final Object instance, final String methodName, final Object[] arguments) { |
| return invokeMissingMethod(instance, methodName, arguments, null, false); |
| } |
| |
| /** |
| * Invoke a missing property on the given object with the given arguments. |
| * |
| * @param instance The object the method should be invoked on. |
| * @param propertyName The name of the property to invoke. |
| * @param optionalValue The (optional) new value for the property |
| * @param isGetter Whether the method is a getter |
| * @return The result of the method invocation. |
| */ |
| @Override |
| public Object invokeMissingProperty(final Object instance, final String propertyName, final Object optionalValue, final boolean isGetter) { |
| MetaBeanProperty property = findPropertyInClassHierarchy(propertyName, theCachedClass); |
| if (property != null) { |
| onSuperPropertyFoundInHierarchy(property); |
| if (!isGetter) { |
| property.setProperty(instance, optionalValue); |
| return null; |
| } |
| return property.getProperty(instance); |
| } |
| |
| // look for getProperty or setProperty overrides |
| if (isGetter) { |
| final Class<?>[] getPropertyArgs = {String.class}; |
| final MetaMethod method = findMethodInClassHierarchy(instance.getClass(), GET_PROPERTY_METHOD, getPropertyArgs, this); |
| if (method instanceof ClosureMetaMethod) { |
| onGetPropertyFoundInHierarchy(method); |
| return method.invoke(instance, new Object[]{propertyName}); |
| } |
| } else { |
| final Class<?>[] setPropertyArgs = {String.class, Object.class}; |
| final MetaMethod method = findMethodInClassHierarchy(instance.getClass(), SET_PROPERTY_METHOD, setPropertyArgs, this); |
| if (method instanceof ClosureMetaMethod) { |
| onSetPropertyFoundInHierarchy(method); |
| return method.invoke(instance, new Object[]{propertyName, optionalValue}); |
| } |
| } |
| |
| try { |
| if (!(instance instanceof Class)) { |
| if (isGetter) { |
| if (propertyMissingGet != null) { |
| return propertyMissingGet.invoke(instance, new Object[]{propertyName}); |
| } |
| } else { |
| if (propertyMissingSet != null) { |
| return propertyMissingSet.invoke(instance, new Object[]{propertyName, optionalValue}); |
| } |
| } |
| } |
| } catch (InvokerInvocationException iie) { |
| boolean shouldHandle = isGetter && propertyMissingGet != null; |
| if (!shouldHandle) shouldHandle = !isGetter && propertyMissingSet != null; |
| if (shouldHandle && iie.getCause() instanceof MissingPropertyException) { |
| throw (MissingPropertyException) iie.getCause(); |
| } |
| throw iie; |
| } |
| |
| Class<?> theClass = instance instanceof Class ? (Class<?>) instance : instance.getClass(); |
| if (instance instanceof Class && theClass != Class.class) { |
| final MetaProperty metaProperty = InvokerHelper.getMetaClass(Class.class).hasProperty(instance, propertyName); |
| if (metaProperty != null) { |
| if (isGetter) { |
| return metaProperty.getProperty(instance); |
| } |
| metaProperty.setProperty(instance, optionalValue); |
| return null; |
| } |
| } |
| |
| throw new MissingPropertyExceptionNoStack(propertyName, theClass); |
| } |
| |
| private Object invokeMissingMethod(final Object instance, final String methodName, final Object[] arguments, final RuntimeException original, final boolean isCallToSuper) { |
| if (isCallToSuper) { |
| MetaClass metaClass = InvokerHelper.getMetaClass(theClass.getSuperclass()); |
| return metaClass.invokeMissingMethod(instance, methodName, arguments); |
| } |
| |
| Class<?> instanceKlazz = instance.getClass(); |
| if (theClass != instanceKlazz && theClass.isAssignableFrom(instanceKlazz)) |
| instanceKlazz = theClass; |
| |
| Class<?>[] argClasses = MetaClassHelper.convertToTypeArray(arguments); |
| |
| MetaMethod method = findMixinMethod(methodName, argClasses); |
| if (method != null) { |
| onMixinMethodFound(method); |
| return method.invoke(instance, arguments); |
| } |
| |
| method = findMethodInClassHierarchy(instanceKlazz, methodName, argClasses, this); |
| if (method != null) { |
| onSuperMethodFoundInHierarchy(method); |
| return method.invoke(instance, arguments); |
| } |
| |
| // still not method here, so see if there is an invokeMethod method up the hierarchy |
| final Class<?>[] invokeMethodArgs = {String.class, Object[].class}; |
| method = findMethodInClassHierarchy(instanceKlazz, INVOKE_METHOD_METHOD, invokeMethodArgs, this); |
| if (method instanceof ClosureMetaMethod) { |
| onInvokeMethodFoundInHierarchy(method); |
| return method.invoke(instance, invokeMethodArgs); |
| } |
| |
| // last resort look in the category |
| if (method == null && GroovyCategorySupport.hasCategoryInCurrentThread()) { |
| method = getCategoryMethodMissing(instanceKlazz); |
| if (method != null) { |
| return method.invoke(instance, new Object[]{methodName, arguments}); |
| } |
| } |
| |
| if (methodMissing != null) { |
| try { |
| return methodMissing.invoke(instance, new Object[]{methodName, arguments}); |
| } catch (InvokerInvocationException iie) { |
| if (methodMissing instanceof ClosureMetaMethod && iie.getCause() instanceof MissingMethodException) { |
| MissingMethodException mme = (MissingMethodException) iie.getCause(); |
| throw new MissingMethodExecutionFailed(mme.getMethod(), mme.getClass(), |
| mme.getArguments(), mme.isStatic(), mme); |
| } |
| throw iie; |
| } catch (MissingMethodException mme) { |
| if (methodMissing instanceof ClosureMetaMethod) { |
| throw new MissingMethodExecutionFailed(mme.getMethod(), mme.getClass(), |
| mme.getArguments(), mme.isStatic(), mme); |
| } else { |
| throw mme; |
| } |
| } |
| } else if (original != null) { |
| throw original; |
| } else { |
| throw new MissingMethodExceptionNoStack(methodName, theClass, arguments, false); |
| } |
| } |
| |
| protected void onSuperPropertyFoundInHierarchy(MetaBeanProperty property) { |
| } |
| |
| protected void onMixinMethodFound(MetaMethod method) { |
| } |
| |
| protected void onSuperMethodFoundInHierarchy(MetaMethod method) { |
| } |
| |
| protected void onInvokeMethodFoundInHierarchy(MetaMethod method) { |
| } |
| |
| protected void onSetPropertyFoundInHierarchy(MetaMethod method) { |
| } |
| |
| protected void onGetPropertyFoundInHierarchy(MetaMethod method) { |
| } |
| |
| /** |
| * Hook to deal with the case of MissingProperty for static properties. The method will look attempt to look up |
| * "propertyMissing" handlers and invoke them otherwise thrown a MissingPropertyException |
| * |
| * @param instance The instance |
| * @param propertyName The name of the property |
| * @param optionalValue The value in the case of a setter |
| * @param isGetter True if it's a getter |
| * @return The value in the case of a getter or a MissingPropertyException |
| */ |
| protected Object invokeStaticMissingProperty(Object instance, String propertyName, Object optionalValue, boolean isGetter) { |
| MetaClass mc = instance instanceof Class ? registry.getMetaClass((Class) instance) : this; |
| if (isGetter) { |
| MetaMethod propertyMissing = mc.getMetaMethod(STATIC_PROPERTY_MISSING, GETTER_MISSING_ARGS); |
| if (propertyMissing != null) { |
| return propertyMissing.invoke(instance, new Object[]{propertyName}); |
| } |
| } else { |
| MetaMethod propertyMissing = mc.getMetaMethod(STATIC_PROPERTY_MISSING, SETTER_MISSING_ARGS); |
| if (propertyMissing != null) { |
| return propertyMissing.invoke(instance, new Object[]{propertyName, optionalValue}); |
| } |
| } |
| |
| if (instance instanceof Class) { |
| throw new MissingPropertyException(propertyName, (Class) instance); |
| } |
| throw new MissingPropertyException(propertyName, theClass); |
| } |
| |
| /** |
| * Invokes a method on the given receiver for the specified arguments. |
| * The MetaClass will attempt to establish the method to invoke based on the name and arguments provided. |
| * |
| * @param object The object which the method was invoked on |
| * @param methodName The name of the method |
| * @param originalArguments The arguments to the method |
| * @return The return value of the method |
| * @see MetaClass#invokeMethod(Class, Object, String, Object[], boolean, boolean) |
| */ |
| @Override |
| public Object invokeMethod(Object object, String methodName, Object[] originalArguments) { |
| return invokeMethod(theClass, object, methodName, originalArguments, false, false); |
| } |
| |
| private Object invokeMethodClosure(final MethodClosure object, final Object[] arguments) { |
| Object owner = object.getOwner(); |
| String method = object.getMethod(); |
| boolean ownerIsClass = (owner instanceof Class); |
| Class ownerClass = ownerIsClass ? (Class) owner : owner.getClass(); |
| final MetaClass ownerMetaClass = registry.getMetaClass(ownerClass); |
| try { |
| return ownerMetaClass.invokeMethod(ownerClass, owner, method, arguments, false, false); |
| } catch (GroovyRuntimeException e) { // GroovyRuntimeException(cause:IllegalArgumentException) thrown for final fields |
| // InvokerInvocationException(cause:IllegalArgumentException) thrown for not this |
| if (!ownerIsClass || !(e instanceof MissingMethodException || e.getCause() instanceof IllegalArgumentException)) { |
| throw e; |
| } |
| if (MethodClosure.NEW.equals(method)) { |
| // CONSTRUCTOR REFERENCE |
| |
| if (!ownerClass.isArray()) |
| return ownerMetaClass.invokeConstructor(arguments); |
| |
| int nArguments = arguments.length; |
| if (nArguments == 0) { |
| throw new GroovyRuntimeException("The arguments(specifying size) are required to create array[" + ownerClass.getCanonicalName() + "]"); |
| } |
| int arrayDimension = ArrayTypeUtils.dimension(ownerClass); |
| if (arrayDimension < nArguments) { |
| throw new GroovyRuntimeException("The length[" + nArguments + "] of arguments should not be greater than the dimensions[" + arrayDimension + "] of array[" + ownerClass.getCanonicalName() + "]"); |
| } |
| Class elementType = arrayDimension == nArguments |
| ? ArrayTypeUtils.elementType(ownerClass) |
| : ArrayTypeUtils.elementType(ownerClass, arrayDimension - nArguments); |
| return Array.newInstance(elementType, Arrays.stream(arguments).mapToInt(argument -> |
| argument instanceof Integer ? (Integer) argument : Integer.parseInt(String.valueOf(argument)) |
| ).toArray()); |
| } else { |
| // METHOD REFERENCE |
| |
| // if the owner is a class and the method closure can be related to some instance method(s) |
| // try to invoke method with adjusted arguments -- first argument is instance of owner type |
| if (arguments.length > 0 && ownerClass.isAssignableFrom(arguments[0].getClass()) |
| && (Boolean) object.getProperty(MethodClosure.ANY_INSTANCE_METHOD_EXISTS)) { |
| try { |
| Object newReceiver = arguments[0]; |
| Object[] newArguments = Arrays.copyOfRange(arguments, 1, arguments.length); |
| return ownerMetaClass.invokeMethod(ownerClass, newReceiver, method, newArguments, false, false); |
| } catch (MissingMethodException ignore) {} |
| } |
| |
| if (ownerClass != Class.class) { // maybe it's a reference to a Class method |
| try { |
| MetaClass cmc = registry.getMetaClass(Class.class); |
| return cmc.invokeMethod(Class.class, owner, method, arguments, false, false); |
| } catch (MissingMethodException ignore) {} |
| } |
| |
| return invokeMissingMethod(object, method, arguments); |
| } |
| } |
| } |
| |
| /** |
| * Invokes a method on the given receiver for the specified arguments. The sender is the class that invoked the method on the object. |
| * The MetaClass will attempt to establish the method to invoke based on the name and arguments provided. |
| * <p> |
| * The isCallToSuper and fromInsideClass help the Groovy runtime perform optimisations on the call to go directly |
| * to the super class if necessary |
| * |
| * @param sender The java.lang.Class instance that invoked the method |
| * @param object The object which the method was invoked on |
| * @param methodName The name of the method |
| * @param arguments The arguments to the method |
| * @param isCallToSuper Whether the method is a call to a super class method |
| * @param fromInsideClass Whether the call was invoked from the inside or the outside of the class |
| * @return The return value of the method |
| * @see MetaClass#invokeMethod(Class, Object, String, Object[], boolean, boolean) |
| */ |
| @Override |
| public Object invokeMethod(final Class sender, final Object object, final String methodName, final Object[] arguments, final boolean isCallToSuper, final boolean fromInsideClass) { |
| try { |
| return doInvokeMethod(sender, object, methodName, arguments, isCallToSuper, fromInsideClass); |
| } catch (MissingMethodException mme) { |
| MethodHandles.Lookup lookup = null; |
| if (object instanceof GroovyObject) { |
| lookup = GroovyObjectHelper.lookup((GroovyObject) object).orElseThrow(() -> mme); |
| } |
| |
| Class<?> receiverClass = object.getClass(); |
| |
| if (isCallToSuper) { |
| if (lookup == null) throw mme; |
| MethodHandles.Lookup theLookup = lookup; |
| MethodHandle methodHandle = findMethod(sender.getSuperclass(), methodName, MetaClassHelper.convertToTypeArray(arguments), method -> { |
| try { |
| return theLookup.unreflectSpecial(method, receiverClass); |
| } catch (IllegalAccessException e) { |
| return null; |
| } |
| }); |
| if (methodHandle == null) throw mme; |
| try { |
| return methodHandle.bindTo(object).invokeWithArguments(arguments); |
| } catch (Throwable t) { |
| throw new GroovyRuntimeException(t); |
| } |
| } |
| |
| if (methodName.equals("clone") && arguments.length == 0) { |
| try { |
| return ObjectUtil.cloneObject(object); |
| } catch (Throwable t) { |
| throw new GroovyRuntimeException(t); |
| } |
| } |
| |
| boolean spyFound = (lookup != null); |
| if (!spyFound) { |
| try { |
| lookup = MethodHandles.lookup().in(receiverClass); |
| } catch (IllegalArgumentException e) { |
| throw mme; |
| } |
| } |
| MethodHandles.Lookup theLookup = lookup; |
| MethodHandle methodHandle = findMethod(receiverClass, methodName, MetaClassHelper.convertToTypeArray(arguments), method -> { |
| try { |
| return spyFound ? theLookup.unreflectSpecial(method, receiverClass) : theLookup.unreflect(method); |
| } catch (IllegalAccessException e) { |
| return null; |
| } |
| }); |
| if (methodHandle == null) throw mme; |
| try { |
| return methodHandle.bindTo(object).invokeWithArguments(arguments); |
| } catch (Throwable t) { |
| throw new GroovyRuntimeException(t); |
| } |
| } |
| } |
| |
| private static final ClassValue<Map<String, Set<Method>>> SPECIAL_METHODS_MAP = new ClassValue<Map<String, Set<Method>>>() { |
| @Override |
| protected Map<String, Set<Method>> computeValue(final Class<?> type) { |
| return new ConcurrentHashMap<>(4); |
| } |
| }; |
| |
| private static MethodHandle findMethod(final Class<?> clazz, final String methodName, final Class[] argTypes, final Function<Method, MethodHandle> mhFunc) { |
| Map<String, Set<Method>> map = SPECIAL_METHODS_MAP.get(clazz); |
| Set<Method> methods = map.get(methodName); |
| Method foundMethod = null; |
| if (methods != null) { |
| for (Method method : methods) { |
| if (parameterTypeMatches(method.getParameterTypes(), argTypes)) { |
| foundMethod = method; |
| break; |
| } |
| } |
| } |
| |
| if (foundMethod == null) { |
| for (Class<?> c = clazz; c != null; c = c.getSuperclass()) { |
| List<Method> declaredMethods = ReflectionUtils.getDeclaredMethods(c, methodName, argTypes); |
| if (!declaredMethods.isEmpty()) { |
| Method method = declaredMethods.get(0); |
| if (!Modifier.isAbstract(method.getModifiers())) { |
| foundMethod = method; |
| break; |
| } |
| } |
| } |
| if (foundMethod == null) return null; |
| map.computeIfAbsent(methodName, k -> Collections.newSetFromMap(new ConcurrentHashMap<>(2))).add(foundMethod); |
| } |
| |
| return mhFunc.apply(foundMethod); |
| } |
| |
| private Object doInvokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) { |
| checkInitalised(); |
| if (object == null) { |
| throw new NullPointerException("Cannot invoke method: " + methodName + " on null object"); |
| } |
| |
| final Object[] arguments = originalArguments == null ? EMPTY_ARGUMENTS : originalArguments; |
| |
| MetaMethod method = getMetaMethod(sender, object, methodName, isCallToSuper, arguments); |
| |
| final boolean isClosure = object instanceof Closure; |
| if (isClosure) { |
| final Closure closure = (Closure) object; |
| final Object owner = closure.getOwner(); |
| |
| if (CALL_METHOD.equals(methodName) || DO_CALL_METHOD.equals(methodName)) { |
| final Class objectClass = object.getClass(); |
| if (objectClass == MethodClosure.class) { |
| return this.invokeMethodClosure((MethodClosure) object, arguments); |
| } else if (objectClass == CurriedClosure.class) { |
| final CurriedClosure cc = (CurriedClosure) object; |
| // change the arguments for an uncurried call |
| final Object[] curriedArguments = cc.getUncurriedArguments(arguments); |
| final Class ownerClass = owner instanceof Class ? (Class) owner : owner.getClass(); |
| final MetaClass ownerMetaClass = registry.getMetaClass(ownerClass); |
| return ownerMetaClass.invokeMethod(owner, methodName, curriedArguments); |
| } |
| if (method == null) invokeMissingMethod(object, methodName, arguments); |
| } |
| |
| final Object delegate = closure.getDelegate(); |
| final boolean isClosureNotOwner = owner != closure; |
| final int resolveStrategy = closure.getResolveStrategy(); |
| |
| final Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); |
| |
| switch (resolveStrategy) { |
| case Closure.TO_SELF: |
| method = closure.getMetaClass().pickMethod(methodName, argClasses); |
| if (method != null) return method.invoke(closure, arguments); |
| break; |
| case Closure.DELEGATE_ONLY: |
| if (method == null && delegate != closure && delegate != null) { |
| MetaClass delegateMetaClass = lookupObjectMetaClass(delegate); |
| method = delegateMetaClass.pickMethod(methodName, argClasses); |
| if (method != null) { |
| return delegateMetaClass.invokeMethod(delegate, methodName, originalArguments); |
| } else if (delegate != closure && (delegate instanceof GroovyObject)) { |
| return invokeMethodOnGroovyObject(methodName, originalArguments, delegate); |
| } |
| } |
| break; |
| case Closure.OWNER_ONLY: |
| if (method == null && owner != closure) { |
| MetaClass ownerMetaClass = lookupObjectMetaClass(owner); |
| return ownerMetaClass.invokeMethod(owner, methodName, originalArguments); |
| } |
| break; |
| case Closure.DELEGATE_FIRST: |
| Tuple2<Object, MetaMethod> tuple = invokeMethod(method, delegate, closure, methodName, argClasses, originalArguments, owner); |
| Object result = tuple.getV1(); |
| method = tuple.getV2(); |
| if (InvokeMethodResult.NONE != result) { |
| return result; |
| } |
| |
| if (method == null && resolveStrategy != Closure.TO_SELF) { |
| // still no methods found, test if delegate or owner are GroovyObjects |
| // and invoke the method on them if so. |
| MissingMethodException last = null; |
| if (delegate != closure && (delegate instanceof GroovyObject)) { |
| try { |
| return invokeMethodOnGroovyObject(methodName, originalArguments, delegate); |
| } catch (MissingMethodException mme) { |
| if (last == null) last = mme; |
| } |
| } |
| if (isClosureNotOwner && (owner instanceof GroovyObject)) { |
| try { |
| return invokeMethodOnGroovyObject(methodName, originalArguments, owner); |
| } catch (MissingMethodException mme) { |
| last = mme; |
| } |
| } |
| if (last != null) |
| return invokeMissingMethod(object, methodName, originalArguments, last, isCallToSuper); |
| } |
| |
| break; |
| default: |
| Tuple2<Object, MetaMethod> t = invokeMethod(method, delegate, closure, methodName, argClasses, originalArguments, owner); |
| Object r = t.getV1(); |
| method = t.getV2(); |
| if (InvokeMethodResult.NONE != r) { |
| return r; |
| } |
| |
| if (method == null && resolveStrategy != Closure.TO_SELF) { |
| // still no methods found, test if delegate or owner are GroovyObjects |
| // and invoke the method on them if so. |
| MissingMethodException last = null; |
| if (isClosureNotOwner && (owner instanceof GroovyObject)) { |
| try { |
| return invokeMethodOnGroovyObject(methodName, originalArguments, owner); |
| } catch (MissingMethodException mme) { |
| if (methodName.equals(mme.getMethod())) { |
| if (last == null) last = mme; |
| } else { |
| throw mme; |
| } |
| } catch (InvokerInvocationException iie) { |
| if (iie.getCause() instanceof MissingMethodException) { |
| MissingMethodException mme = (MissingMethodException) iie.getCause(); |
| if (methodName.equals(mme.getMethod())) { |
| if (last == null) last = mme; |
| } else { |
| throw iie; |
| } |
| } else { |
| throw iie; |
| } |
| } |
| } |
| if (delegate != closure && (delegate instanceof GroovyObject)) { |
| try { |
| return invokeMethodOnGroovyObject(methodName, originalArguments, delegate); |
| } catch (MissingMethodException mme) { |
| last = mme; |
| } catch (InvokerInvocationException iie) { |
| if (iie.getCause() instanceof MissingMethodException) { |
| last = (MissingMethodException) iie.getCause(); |
| } else { |
| throw iie; |
| } |
| } |
| } |
| if (last != null) |
| return invokeMissingMethod(object, methodName, originalArguments, last, isCallToSuper); |
| } |
| } |
| } |
| |
| if (method != null) { |
| MetaMethod transformedMetaMethod = VM_PLUGIN.transformMetaMethod(this, method); |
| return transformedMetaMethod.doMethodInvoke(object, arguments); |
| } else { |
| return invokePropertyOrMissing(object, methodName, originalArguments, fromInsideClass, isCallToSuper); |
| } |
| } |
| |
| private MetaMethod getMetaMethod(Class sender, Object object, String methodName, boolean isCallToSuper, Object... arguments) { |
| MetaMethod method = null; |
| if (CALL_METHOD.equals(methodName) && object instanceof GeneratedClosure) { |
| method = getMethodWithCaching(sender, DO_CALL_METHOD, arguments, isCallToSuper); |
| } |
| if (method == null) { |
| method = getMethodWithCaching(sender, methodName, arguments, isCallToSuper); |
| } |
| MetaClassHelper.unwrap(arguments); |
| |
| if (method == null) |
| method = tryListParamMetaMethod(sender, methodName, isCallToSuper, arguments); |
| return method; |
| } |
| |
| private MetaMethod tryListParamMetaMethod(Class sender, String methodName, boolean isCallToSuper, Object[] arguments) { |
| MetaMethod method = null; |
| if (arguments.length == 1 && arguments[0] instanceof List) { |
| Object[] newArguments = ((List) arguments[0]).toArray(); |
| method = createTransformMetaMethod(getMethodWithCaching(sender, methodName, newArguments, isCallToSuper)); |
| } |
| return method; |
| } |
| |
| protected MetaMethod createTransformMetaMethod(MetaMethod method) { |
| if (method == null) { |
| return null; |
| } |
| |
| return new TransformMetaMethod(method) { |
| @Override |
| public Object invoke(Object object, Object[] arguments) { |
| Object firstArgument = arguments[0]; |
| List list = (List) firstArgument; |
| arguments = list.toArray(); |
| return super.invoke(object, arguments); |
| } |
| }; |
| } |
| |
| /** |
| * Tries to find a callable property and make the call. |
| */ |
| private Object invokePropertyOrMissing(final Object object, final String methodName, final Object[] originalArguments, final boolean fromInsideClass, final boolean isCallToSuper) { |
| MetaProperty metaProperty = this.getMetaProperty(methodName, false); |
| |
| Object value = null; |
| if (metaProperty != null) { |
| value = metaProperty.getProperty(object); |
| } else if (object instanceof Map) { |
| value = ((Map<?, ?>) object).get(methodName); |
| } else if (object instanceof Script) { |
| value = ((Script) object).getBinding().getVariables().get(methodName); |
| } |
| |
| if (value instanceof Closure) { |
| Closure<?> closure = (Closure<?>) value; |
| MetaClass metaClass = closure.getMetaClass(); |
| try { |
| return metaClass.invokeMethod(closure.getClass(), closure, DO_CALL_METHOD, originalArguments, false, fromInsideClass); |
| } catch (MissingMethodException mme) { |
| // fall through -- "doCall" is not intrinsic to Closure |
| } |
| } |
| |
| if (value != null && !(value instanceof Map) && !methodName.equals(CALL_METHOD)) { |
| try { |
| MetaClass metaClass = ((MetaClassRegistryImpl) registry).getMetaClass(value); |
| return metaClass.invokeMethod(value, CALL_METHOD, originalArguments); // delegate to call method of property value |
| } catch (MissingMethodException mme) { |
| // ignore |
| } |
| } |
| |
| return invokeMissingMethod(object, methodName, originalArguments, null, isCallToSuper); |
| } |
| |
| private MetaClass lookupObjectMetaClass(Object object) { |
| if (object instanceof GroovyObject) { |
| GroovyObject go = (GroovyObject) object; |
| return go.getMetaClass(); |
| } |
| Class ownerClass = object.getClass(); |
| if (ownerClass == Class.class) ownerClass = (Class) object; |
| MetaClass metaClass = registry.getMetaClass(ownerClass); |
| return metaClass; |
| } |
| |
| private static Object invokeMethodOnGroovyObject(String methodName, Object[] originalArguments, Object owner) { |
| GroovyObject go = (GroovyObject) owner; |
| return go.invokeMethod(methodName, originalArguments); |
| } |
| |
| public MetaMethod getMethodWithCaching(Class sender, String methodName, Object[] arguments, boolean isCallToSuper) { |
| if (!isCallToSuper && GroovyCategorySupport.hasCategoryInCurrentThread()) { |
| return getMethodWithoutCaching(sender, methodName, MetaClassHelper.convertToTypeArray(arguments), isCallToSuper); |
| } |
| MetaMethodIndex.Entry e = metaMethodIndex.getMethods(sender, methodName); |
| if (isCallToSuper && e != null && e.methodsForSuper == null) { |
| // allow "super.name()" to find DGM if class declares method "name" |
| e = metaMethodIndex.getMethods(sender.getSuperclass(), methodName); |
| } |
| if (e == null) { |
| return null; |
| } |
| if (isCallToSuper) { |
| return getSuperMethodWithCaching(arguments, e); |
| } else { |
| return getNormalMethodWithCaching(arguments, e); |
| } |
| } |
| |
| private static boolean sameClasses(final Class[] params, final Class[] arguments) { |
| // we do here a null check because the params field might not have been set yet |
| if (params == null || params.length != arguments.length) |
| return false; |
| |
| for (int i = params.length - 1; i >= 0; i -= 1) { |
| Object arg = arguments[i]; |
| if (arg != null) { |
| if (params[i] != arguments[i]) return false; |
| } else { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| // This method should be called by CallSite only |
| private MetaMethod getMethodWithCachingInternal(final Class sender, final CallSite site, final Class<?>[] params) { |
| if (GroovyCategorySupport.hasCategoryInCurrentThread()) |
| return getMethodWithoutCaching(sender, site.getName(), params, false); |
| |
| MetaMethodIndex.Entry e = metaMethodIndex.getMethods(sender, site.getName()); |
| if (e == null || e.methods == null) |
| return null; |
| |
| MetaMethodIndex.CacheEntry cacheEntry = e.cachedMethod; |
| if (cacheEntry != null && (sameClasses(cacheEntry.params, params))) { |
| return cacheEntry.method; |
| } |
| |
| cacheEntry = e.cachedMethod = new MetaMethodIndex.CacheEntry(params, (MetaMethod) chooseMethod(e.name, e.methods, params)); |
| |
| return cacheEntry.method; |
| } |
| |
| private MetaMethod getSuperMethodWithCaching(final Object[] arguments, final MetaMethodIndex.Entry e) { |
| if (e.methodsForSuper == null) |
| return null; |
| |
| MetaMethodIndex.CacheEntry cacheEntry = e.cachedMethodForSuper; |
| |
| if (cacheEntry != null && cacheEntry.method != null |
| && MetaClassHelper.sameClasses(cacheEntry.params, arguments, e.methodsForSuper instanceof MetaMethod)) { |
| return cacheEntry.method; |
| } |
| |
| Class<?>[] types = MetaClassHelper.convertToTypeArray(arguments); |
| MetaMethod method = (MetaMethod) chooseMethod(e.name, e.methodsForSuper, types); |
| cacheEntry = e.cachedMethodForSuper = new MetaMethodIndex.CacheEntry(types, method.isAbstract() ? null : method); |
| |
| return cacheEntry.method; |
| } |
| |
| private MetaMethod getNormalMethodWithCaching(final Object[] arguments, final MetaMethodIndex.Entry e) { |
| if (e.methods == null) |
| return null; |
| |
| MetaMethodIndex.CacheEntry cacheEntry = e.cachedMethod; |
| |
| if (cacheEntry != null && cacheEntry.method != null |
| && MetaClassHelper.sameClasses(cacheEntry.params, arguments, e.methods instanceof MetaMethod)) { |
| return cacheEntry.method; |
| } |
| |
| Class<?>[] types = MetaClassHelper.convertToTypeArray(arguments); |
| MetaMethod method = (MetaMethod) chooseMethod(e.name, e.methods, types); |
| cacheEntry = e.cachedMethod = new MetaMethodIndex.CacheEntry(types, method); |
| |
| return cacheEntry.method; |
| } |
| |
| public Constructor retrieveConstructor(Class[] arguments) { |
| CachedConstructor constructor = (CachedConstructor) chooseMethod(CONSTRUCTOR_NAME, constructors, arguments); |
| if (constructor != null) { |
| return constructor.getCachedConstructor(); |
| } |
| constructor = (CachedConstructor) chooseMethod(CONSTRUCTOR_NAME, constructors, arguments); |
| if (constructor != null) { |
| return constructor.getCachedConstructor(); |
| } |
| return null; |
| } |
| |
| public MetaMethod retrieveStaticMethod(String methodName, Object[] arguments) { |
| final MetaMethodIndex.Entry e = metaMethodIndex.getMethods(theClass, methodName); |
| MetaMethodIndex.CacheEntry cacheEntry; |
| if (e != null) { |
| cacheEntry = e.cachedStaticMethod; |
| |
| if (cacheEntry != null && |
| MetaClassHelper.sameClasses(cacheEntry.params, arguments, e.staticMethods instanceof MetaMethod)) { |
| return cacheEntry.method; |
| } |
| |
| final Class[] classes = MetaClassHelper.convertToTypeArray(arguments); |
| cacheEntry = new MetaMethodIndex.CacheEntry(classes, pickStaticMethod(methodName, classes)); |
| |
| e.cachedStaticMethod = cacheEntry; |
| |
| return cacheEntry.method; |
| } |
| return pickStaticMethod(methodName, MetaClassHelper.convertToTypeArray(arguments)); |
| } |
| |
| public MetaMethod getMethodWithoutCaching(Class sender, String methodName, Class[] arguments, boolean isCallToSuper) { |
| MetaMethod method = null; |
| Object methods = getMethods(sender, methodName, isCallToSuper); |
| if (methods != null) { |
| method = (MetaMethod) chooseMethod(methodName, methods, arguments); |
| } |
| return method; |
| } |
| |
| @Override |
| public Object invokeStaticMethod(Object object, String methodName, Object[] arguments) { |
| checkInitalised(); |
| |
| final Class sender = object instanceof Class ? (Class) object : object.getClass(); |
| if (sender != theClass) { |
| MetaClass mc = registry.getMetaClass(sender); |
| return mc.invokeStaticMethod(sender, methodName, arguments); |
| } |
| if (sender == Class.class) { |
| return invokeMethod(object, methodName, arguments); |
| } |
| |
| if (arguments == null) arguments = EMPTY_ARGUMENTS; |
| |
| MetaMethod method = retrieveStaticMethod(methodName, arguments); |
| // let's try to use the cache to find the method |
| |
| if (method != null) { |
| MetaClassHelper.unwrap(arguments); |
| return method.doMethodInvoke(object, arguments); |
| } |
| Object prop = null; |
| try { |
| prop = getProperty(theClass, theClass, methodName, false, false); |
| } catch (MissingPropertyException mpe) { |
| // ignore |
| } |
| |
| if (prop instanceof Closure) { |
| return invokeStaticClosureProperty(arguments, prop); |
| } |
| |
| Object[] originalArguments = arguments.clone(); |
| MetaClassHelper.unwrap(arguments); |
| |
| Class superClass = sender.getSuperclass(); |
| Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); |
| while (superClass != Object.class && superClass != null) { |
| MetaClass mc = registry.getMetaClass(superClass); |
| method = mc.getStaticMetaMethod(methodName, argClasses); |
| if (method != null) return method.doMethodInvoke(object, arguments); |
| |
| try { |
| prop = mc.getProperty(superClass, superClass, methodName, false, false); |
| } catch (MissingPropertyException mpe) { |
| // ignore |
| } |
| |
| if (prop instanceof Closure) { |
| return invokeStaticClosureProperty(originalArguments, prop); |
| } |
| |
| superClass = superClass.getSuperclass(); |
| } |
| |
| if (prop != null) { |
| MetaClass propMC = registry.getMetaClass(prop.getClass()); |
| return propMC.invokeMethod(prop, CALL_METHOD, arguments); |
| } |
| |
| return invokeStaticMissingMethod(sender, methodName, arguments); |
| } |
| |
| private static Object invokeStaticClosureProperty(Object[] originalArguments, Object prop) { |
| Closure closure = (Closure) prop; |
| MetaClass delegateMetaClass = closure.getMetaClass(); |
| return delegateMetaClass.invokeMethod(closure.getClass(), closure, 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) throws MethodSelectionException { |
| MetaMethod method = null; |
| MethodSelectionException mse = null; |
| Object methods = getStaticMethods(theClass, methodName); |
| |
| if (!(methods instanceof FastArray) || !((FastArray) methods).isEmpty()) { |
| try { |
| method = (MetaMethod) chooseMethod(methodName, methods, arguments); |
| } catch (MethodSelectionException msex) { |
| mse = msex; |
| } |
| } |
| if (method == null && theClass != Class.class) { |
| MetaClass cmc = registry.getMetaClass(Class.class); |
| method = cmc.pickMethod(methodName, arguments); |
| } |
| if (method == null) { |
| method = (MetaMethod) chooseMethod(methodName, methods, arguments); |
| } |
| |
| if (method == null && mse != null) { |
| throw mse; |
| } else { |
| return method; |
| } |
| } |
| |
| @Override |
| public Object invokeConstructor(Object[] arguments) { |
| return invokeConstructor(theClass, arguments); |
| } |
| |
| @Override |
| public int selectConstructorAndTransformArguments(int numberOfConstructors, Object[] arguments) { |
| if (numberOfConstructors == -1) { |
| return selectConstructorAndTransformArguments1(arguments); |
| } |
| // falling back to pre 2.1.9 selection algorithm |
| // in practice this branch will only be reached if the class calling this code is a Groovy class |
| // compiled with an earlier version of the Groovy compiler |
| return selectConstructorAndTransformArguments0(numberOfConstructors, arguments); |
| } |
| |
| private int selectConstructorAndTransformArguments0(final int numberOfConstructors, Object[] arguments) { |
| //TODO: that is just a quick prototype, not the real thing! |
| if (numberOfConstructors != constructors.size()) { |
| throw new IncompatibleClassChangeError("the number of constructors during runtime and compile time for " + |
| this.theClass.getName() + " do not match. Expected " + numberOfConstructors + " but got " + constructors.size()); |
| } |
| |
| CachedConstructor constructor = createCachedConstructor(arguments); |
| List l = new ArrayList(constructors.toList()); |
| Comparator comp = (arg0, 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); |
| }; |
| l.sort(comp); |
| int found = -1; |
| for (int i = 0, n = l.size(); i < n; i++) { |
| if (l.get(i) != constructor) continue; |
| found = i; |
| break; |
| } |
| // NOTE: must be changed to "1 |" if constructor was vargs |
| return (found << 8); |
| } |
| |
| private CachedConstructor createCachedConstructor(Object[] arguments) { |
| if (arguments == null) arguments = EMPTY_ARGUMENTS; |
| Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); |
| MetaClassHelper.unwrap(arguments); |
| CachedConstructor constructor = (CachedConstructor) chooseMethod(CONSTRUCTOR_NAME, constructors, argClasses); |
| if (constructor == null) { |
| constructor = (CachedConstructor) chooseMethod(CONSTRUCTOR_NAME, constructors, argClasses); |
| } |
| if (constructor == null) { |
| throw new GroovyRuntimeException( |
| "Could not find matching constructor for: " |
| + theClass.getName() |
| + "(" + FormatHelper.toTypeString(arguments) + ")"); |
| } |
| return constructor; |
| } |
| |
| /** |
| * Constructor selection algorithm for Groovy 2.1.9+. |
| * This selection algorithm was introduced as a workaround for GROOVY-6080. Instead of generating an index between |
| * 0 and N where N is the number of super constructors at the time the class is compiled, this algorithm uses |
| * a hash of the constructor descriptor instead. |
| * <p> |
| * This has the advantage of letting the super class add new constructors while being binary compatible. But there |
| * are still problems with this approach: |
| * <ul> |
| * <li>There's a risk of hash collision, even if it's very low (two constructors of the same class must have the same hash)</li> |
| * <li>If the super class adds a new constructor which takes as an argument a superclass of an existing constructor parameter and |
| * that this new constructor is selected at runtime, it would not find it.</li> |
| * </ul> |
| * <p> |
| * Hopefully in the last case, the error message is much nicer now since it explains that it's a binary incompatible change. |
| * |
| * @param arguments the actual constructor call arguments |
| * @return a hash used to identify the constructor to be called |
| * @since 2.1.9 |
| */ |
| private int selectConstructorAndTransformArguments1(Object[] arguments) { |
| CachedConstructor constructor = createCachedConstructor(arguments); |
| final String methodDescriptor = BytecodeHelper.getMethodDescriptor(Void.TYPE, constructor.getNativeParameterTypes()); |
| // keeping 3 bits for additional information such as vargs |
| return BytecodeHelper.hashCode(methodDescriptor); |
| } |
| |
| /** |
| * checks if the initialisation of the class id complete. |
| * This method should be called as a form of assert, it is no |
| * way to test if there is still initialisation work to be done. |
| * Such logic must be implemented in a different way. |
| * |
| * @throws IllegalStateException if the initialisation is incomplete yet |
| */ |
| protected void checkInitalised() { |
| if (!isInitialized()) |
| throw new IllegalStateException( |
| "initialize must be called for meta " + |
| "class of " + theClass + |
| "(" + this.getClass() + ") " + |
| "to complete initialisation process " + |
| "before any invocation or field/property " + |
| "access can be done"); |
| } |
| |
| /** |
| * This is a helper class introduced in Groovy 2.1.0, which is used only by |
| * indy. This class is for internal use only. |
| * |
| * @since Groovy 2.1.0 |
| */ |
| public static final class MetaConstructor extends MetaMethod { |
| private final CachedConstructor cc; |
| private final boolean beanConstructor; |
| |
| private MetaConstructor(CachedConstructor cc, boolean bean) { |
| super(cc.getNativeParameterTypes()); |
| this.setParametersTypes(cc.getParameterTypes()); |
| this.cc = cc; |
| this.beanConstructor = bean; |
| } |
| |
| @Override |
| public int getModifiers() { |
| return cc.getModifiers(); |
| } |
| |
| @Override |
| public String getName() { |
| return CONSTRUCTOR_NAME; |
| } |
| |
| @Override |
| public Class getReturnType() { |
| return cc.getCachedClass().getTheClass(); |
| } |
| |
| @Override |
| public CachedClass getDeclaringClass() { |
| return cc.getCachedClass(); |
| } |
| |
| @Override |
| public Object invoke(Object object, Object[] arguments) { |
| return cc.doConstructorInvoke(arguments); |
| } |
| |
| public CachedConstructor getCachedConstrcutor() { |
| return cc; |
| } |
| |
| public boolean isBeanConstructor() { |
| return beanConstructor; |
| } |
| } |
| |
| /** |
| * This is a helper method added in Groovy 2.1.0, which is used only by indy. |
| * This method is for internal use only. |
| * |
| * @since Groovy 2.1.0 |
| */ |
| public MetaMethod retrieveConstructor(Object[] arguments) { |
| checkInitalised(); |
| if (arguments == null) arguments = EMPTY_ARGUMENTS; |
| Class[] argTypes = MetaClassHelper.convertToTypeArray(arguments); |
| MetaClassHelper.unwrap(arguments); |
| Object res = chooseMethod(CONSTRUCTOR_NAME, constructors, argTypes); |
| if (res instanceof MetaMethod) return (MetaMethod) res; |
| CachedConstructor constructor = (CachedConstructor) res; |
| if (constructor != null) return new MetaConstructor(constructor, false); |
| // handle named args on class or inner class (one level only for now) |
| if ((arguments.length == 1 && arguments[0] instanceof Map) || |
| (arguments.length == 2 && arguments[1] instanceof Map && |
| theClass.getEnclosingClass() != null && |
| theClass.getEnclosingClass().isAssignableFrom(argTypes[0]))) { |
| res = retrieveNamedArgCompatibleConstructor(argTypes, arguments); |
| } |
| if (res instanceof MetaMethod) return (MetaMethod) res; |
| constructor = (CachedConstructor) res; |
| if (constructor != null) return new MetaConstructor(constructor, true); |
| |
| return null; |
| } |
| |
| private Object retrieveNamedArgCompatibleConstructor(Class[] origArgTypes, Object[] origArgs) { |
| // if we get here Map variant already not found so allow for no-arg plus setters |
| Class[] argTypes = Arrays.copyOf(origArgTypes, origArgTypes.length - 1); |
| Object[] args = Arrays.copyOf(origArgs, origArgs.length - 1); |
| Object res = chooseMethod(CONSTRUCTOR_NAME, constructors, argTypes); |
| // chooseMethod allows fuzzy matching implicit null case but we don't want that here |
| // code here handles inner class case but we currently don't do fuzzy matching for inner classes |
| if (res instanceof ParameterTypes && ((ParameterTypes) res).getParameterTypes().length == origArgTypes.length) { |
| String prettyOrigArgs = FormatHelper.toTypeString(origArgs); |
| if (prettyOrigArgs.endsWith("LinkedHashMap")) { |
| prettyOrigArgs = prettyOrigArgs.replaceFirst("LinkedHashMap$", "Map"); |
| } |
| throw new GroovyRuntimeException( |
| "Could not find named-arg compatible constructor. Expecting one of:\n" |
| + theClass.getName() + "(" + prettyOrigArgs + ")\n" |
| + theClass.getName() + "(" + FormatHelper.toTypeString(args) + ")" |
| ); |
| } |
| return res; |
| } |
| |
| private Object invokeConstructor(Class at, Object[] arguments) { |
| checkInitalised(); |
| if (arguments == null) arguments = EMPTY_ARGUMENTS; |
| Class[] argTypes = MetaClassHelper.convertToTypeArray(arguments); |
| MetaClassHelper.unwrap(arguments); |
| CachedConstructor constructor = (CachedConstructor) chooseMethod(CONSTRUCTOR_NAME, constructors, argTypes); |
| if (constructor != null) { |
| return constructor.doConstructorInvoke(arguments); |
| } |
| // handle named args on class or inner class (one level only for now) |
| if ((arguments.length == 1 && arguments[0] instanceof Map) || |
| (arguments.length == 2 && arguments[1] instanceof Map && |
| theClass.getEnclosingClass() != null && |
| theClass.getEnclosingClass().isAssignableFrom(argTypes[0]))) { |
| constructor = (CachedConstructor) retrieveNamedArgCompatibleConstructor(argTypes, arguments); |
| if (constructor != null) { |
| Object[] args = Arrays.copyOf(arguments, arguments.length - 1); |
| Object bean = constructor.doConstructorInvoke(args); |
| setProperties(bean, ((Map) arguments[arguments.length - 1])); |
| return bean; |
| } |
| } |
| |
| throw new GroovyRuntimeException( |
| "Could not find matching constructor for: " |
| + theClass.getName() |
| + "(" + FormatHelper.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 (Object o : map.entrySet()) { |
| Map.Entry entry = (Map.Entry) o; |
| String key = entry.getKey().toString(); |
| |
| Object value = entry.getValue(); |
| setProperty(bean, key, value); |
| } |
| } |
| |
| /** |
| * @return the given property's value on the object |
| */ |
| @Override |
| public Object getProperty(final Class sender, final Object object, final String name, final boolean useSuper, final 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(); |
| |
| //---------------------------------------------------------------------- |
| // getter |
| //---------------------------------------------------------------------- |
| Tuple2<MetaMethod, MetaProperty> methodAndProperty = createMetaMethodAndMetaProperty(sender, sender, name, useSuper, isStatic); |
| MetaMethod method = methodAndProperty.getV1(); |
| |
| if (method == null || isSpecialProperty(name)) { |
| //------------------------------------------------------------------ |
| // public field |
| //------------------------------------------------------------------ |
| MetaProperty mp = methodAndProperty.getV2(); |
| if (mp != null && Modifier.isPublic(mp.getModifiers())) { |
| try { |
| return mp.getProperty(object); |
| } catch (GroovyRuntimeException e) { |
| // can't access the field directly but there may be a getter |
| mp = null; |
| } |
| } |
| |
| //------------------------------------------------------------------ |
| // java.util.Map get method |
| //------------------------------------------------------------------ |
| if (isMap && !isStatic) { |
| return ((Map<?,?>) object).get(name); |
| } |
| |
| //------------------------------------------------------------------ |
| // non-public field |
| //------------------------------------------------------------------ |
| if (mp != null) { |
| try { |
| return mp.getProperty(object); |
| } catch (GroovyRuntimeException e) { |
| } |
| } |
| } |
| |
| //---------------------------------------------------------------------- |
| // propertyMissing (via category) or generic get method |
| //---------------------------------------------------------------------- |
| Object[] arguments = EMPTY_ARGUMENTS; |
| if (method == null && !useSuper && !isStatic && GroovyCategorySupport.hasCategoryInCurrentThread()) { |
| // check for propertyMissing provided through a category; TODO:should this have lower precedence? |
| method = getCategoryMethodGetter(sender, PROPERTY_MISSING, true); |
| if (method == null) { |
| // check for a generic get method provided through a category |
| method = getCategoryMethodGetter(sender, "get", true); |
| } |
| if (method != null) arguments = new Object[]{name}; |
| } |
| if (method == null && genericGetMethod != null && (genericGetMethod.isStatic() || !isStatic)) { |
| arguments = new Object[]{name}; |
| method = genericGetMethod; |
| } |
| |
| if (method != null) { |
| //------------------------------------------------------------------ |
| // executing the method |
| //------------------------------------------------------------------ |
| MetaMethod metaMethod = VM_PLUGIN.transformMetaMethod(this, method); |
| return metaMethod.doMethodInvoke(object, arguments); |
| } else { |
| //------------------------------------------------------------------ |
| // special cases -- maybe these cases should be special MetaClasses! |
| //------------------------------------------------------------------ |
| if (isStatic) { |
| MetaClass cmc = registry.getMetaClass(Class.class); |
| return cmc.getProperty(Class.class, object, name, false, false); |
| } |
| if (object instanceof Collection) { |
| return DefaultGroovyMethods.getAt((Collection<?>) object, name); |
| } |
| if (object instanceof Object[]) { |
| return DefaultGroovyMethods.getAt(Arrays.asList((Object[]) object), name); |
| } |
| MetaMethod addListenerMethod = listeners.get(name); |
| if (addListenerMethod != null) { |
| // TODO: one day we could try return the previously registered Closure listener for easy removal |
| return null; |
| } |
| } |
| |
| //---------------------------------------------------------------------- |
| // missing property protocol |
| //---------------------------------------------------------------------- |
| if (object instanceof Class) { |
| return invokeStaticMissingProperty(object, name, null, true); |
| } |
| return invokeMissingProperty(object, name, null, true); |
| } |
| |
| public MetaProperty getEffectiveGetMetaProperty(final Class sender, final Object object, String name, final boolean useSuper) { |
| |
| //---------------------------------------------------------------------- |
| // handling of static |
| //---------------------------------------------------------------------- |
| boolean isStatic = (theClass != Class.class && object instanceof Class); |
| if (isStatic && object != theClass) { |
| return new MetaProperty(name, Object.class) { |
| @Override |
| public Object getProperty(Object object) { |
| MetaClass mc = registry.getMetaClass((Class<?>) object); |
| return mc.getProperty(sender, object, name, useSuper, false); |
| } |
| |
| @Override |
| public void setProperty(Object object, Object newValue) { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| |
| checkInitalised(); |
| |
| //---------------------------------------------------------------------- |
| // getter |
| //---------------------------------------------------------------------- |
| Tuple2<MetaMethod, MetaProperty> methodAndProperty = createMetaMethodAndMetaProperty(sender, theClass, name, useSuper, isStatic); |
| MetaMethod method = methodAndProperty.getV1(); |
| |
| if (method == null || isSpecialProperty(name)) { |
| //------------------------------------------------------------------ |
| // public field |
| //------------------------------------------------------------------ |
| MetaProperty mp = methodAndProperty.getV2(); |
| if (mp != null && Modifier.isPublic(mp.getModifiers())) { |
| return mp; |
| } |
| |
| //---------------------------------------------------------------------- |
| // java.util.Map get method |
| //---------------------------------------------------------------------- |
| if (isMap && !isStatic) { |
| return new MetaProperty(name, Object.class) { |
| @Override |
| public Object getProperty(Object object) { |
| return ((Map<?,?>) object).get(name); |
| } |
| |
| @Override |
| public void setProperty(Object object, Object newValue) { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| |
| //------------------------------------------------------------------ |
| // non-public field |
| //------------------------------------------------------------------ |
| if (mp != null) { |
| return mp; |
| } |
| } |
| |
| if (method != null) { |
| return new GetBeanMethodMetaProperty(name, VM_PLUGIN.transformMetaMethod(this, method)); |
| } |
| |
| //---------------------------------------------------------------------- |
| // generic get method |
| //---------------------------------------------------------------------- |
| if (!useSuper && !isStatic && GroovyCategorySupport.hasCategoryInCurrentThread()) { |
| method = getCategoryMethodGetter(sender, "get", true); |
| if (null == method) { |
| method = getCategoryMethodGetter(sender, PROPERTY_MISSING, true); |
| } |
| if (method != null) { |
| return new GetMethodMetaProperty(name, VM_PLUGIN.transformMetaMethod(this, method)); |
| } |
| } |
| if (genericGetMethod != null && (genericGetMethod.isStatic() || !isStatic)) { |
| return new GetMethodMetaProperty(name, VM_PLUGIN.transformMetaMethod(this, genericGetMethod)); |
| } |
| |
| //---------------------------------------------------------------------- |
| // special cases |
| //---------------------------------------------------------------------- |
| if (theClass != Class.class && object instanceof Class) { |
| return new MetaProperty(name, Object.class) { |
| @Override |
| public Object getProperty(Object object) { |
| MetaClass cmc = registry.getMetaClass(Class.class); |
| return cmc.getProperty(Class.class, object, name, false, false); |
| } |
| |
| @Override |
| public void setProperty(Object object, Object newValue) { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| if (object instanceof Collection) { |
| return new MetaProperty(name, Object.class) { |
| @Override |
| public Object getProperty(Object object) { |
| return DefaultGroovyMethods.getAt((Collection<?>) object, name); |
| } |
| |
| @Override |
| public void setProperty(Object object, Object newValue) { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| if (object instanceof Object[]) { |
| return new MetaProperty(name, Object.class) { |
| @Override |
| public Object getProperty(Object object) { |
| return DefaultGroovyMethods.getAt(Arrays.asList((Object[]) object), name); |
| } |
| |
| @Override |
| public void setProperty(Object object, Object newValue) { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| MetaMethod addListenerMethod = listeners.get(name); |
| if (addListenerMethod != null) { |
| return new MetaProperty(name, Object.class) { |
| @Override |
| public Object getProperty(Object object) { |
| return null; |
| } |
| |
| @Override |
| public void setProperty(Object object, Object newValue) { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| |
| //---------------------------------------------------------------------- |
| // error due to missing method/field |
| //---------------------------------------------------------------------- |
| if (isStatic || object instanceof Class) { |
| return new MetaProperty(name, Object.class) { |
| @Override |
| public Object getProperty(Object object) { |
| return invokeStaticMissingProperty(object, name, null, true); |
| } |
| |
| @Override |
| public void setProperty(Object object, Object newValue) { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| return new MetaProperty(name, Object.class) { |
| @Override |
| public Object getProperty(Object object) { |
| return invokeMissingProperty(object, name, null, true); |
| } |
| |
| @Override |
| public void setProperty(Object object, Object newValue) { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| |
| /** |
| * Object#getClass, Map#isEmpty, GroovyObject#getMetaClass |
| */ |
| private boolean isSpecialProperty(final String name) { |
| return "class".equals(name) || (isMap && ("empty".equals(name) || "metaClass".equals(name))); |
| } |
| |
| private Tuple2<MetaMethod, MetaProperty> createMetaMethodAndMetaProperty(final Class senderForMP, final Class senderForCMG, final String name, final boolean useSuper, final boolean isStatic) { |
| MetaMethod method = null; |
| MetaProperty mp = getMetaProperty(senderForMP, name, useSuper, isStatic); |
| if ((mp == null || mp instanceof CachedField) && !name.isEmpty() && isUpperCase(name.charAt(0)) && (name.length() < 2 || !isUpperCase(name.charAt(1))) && !"Class".equals(name) && !"MetaClass".equals(name)) { |
| // GROOVY-9618 adjust because capitalised properties aren't stored as meta bean props |
| MetaProperty saved = mp; |
| mp = getMetaProperty(senderForMP, BeanUtils.decapitalize(name), useSuper, isStatic); |
| if (mp == null || (saved != null && mp instanceof CachedField)) { |
| // restore if we didn't find something better |
| mp = saved; |
| } |
| } |
| |
| if (mp instanceof MetaBeanProperty) { |
| MetaBeanProperty mbp = (MetaBeanProperty) mp; |
| method = mbp.getGetter(); |
| mp = mbp.getField(); |
| } |
| |
| // check for a category method named like a getter |
| if (!useSuper && !isStatic && GroovyCategorySupport.hasCategoryInCurrentThread()) { |
| String getterName = GroovyCategorySupport.getPropertyCategoryGetterName(name); |
| if (getterName != null) { |
| MetaMethod categoryMethod = getCategoryMethodGetter(senderForCMG, getterName, false); |
| if (categoryMethod != null) |
| method = categoryMethod; |
| } |
| } |
| |
| return tuple(method, mp); |
| } |
| |
| private static CategoryMethod getCategoryMethodMissing(final Class<?> sender) { |
| return findCategoryMethod(METHOD_MISSING, sender, params -> |
| params.length == 2 && params[0].getTheClass() == String.class |
| ); |
| } |
| |
| private static CategoryMethod getCategoryMethodGetter(final Class<?> sender, final String name, final boolean useLongVersion) { |
| return findCategoryMethod(name, sender, params -> |
| useLongVersion ? params.length == 1 && params[0].getTheClass() == String.class : params.length == 0 |
| ); |
| } |
| |
| private static CategoryMethod getCategoryMethodSetter(final Class<?> sender, final String name, final boolean useLongVersion) { |
| return findCategoryMethod(name, sender, params -> |
| useLongVersion ? params.length == 2 && params[0].getTheClass() == String.class : params.length == 1 |
| ); |
| } |
| |
| private static CategoryMethod findCategoryMethod(final String name, final Class<?> sender, final java.util.function.Predicate<CachedClass[]> paramFilter) { |
| List<CategoryMethod> categoryMethods = GroovyCategorySupport.getCategoryMethods(name); |
| if (categoryMethods != null) { |
| List<CategoryMethod> choices = new ArrayList<>(); |
| for (CategoryMethod categoryMethod : categoryMethods) { |
| if (categoryMethod.getOwnerClass().isAssignableFrom(sender) |
| && paramFilter.test(categoryMethod.getParameterTypes())) { |
| choices.add(categoryMethod); |
| } |
| } |
| if (!choices.isEmpty()) { |
| if (choices.size() > 1) { // GROOVY-5453, GROOVY-10214: order by self-type distance |
| choices.sort(Comparator.comparingLong(m -> MetaClassHelper.calculateParameterDistance( |
| new Class[]{sender}, new ParameterTypes(new CachedClass[]{m.getOwnerClass()})))); |
| } |
| return choices.get(0); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Get all the properties defined for this type |
| * |
| * @return a list of MetaProperty objects |
| */ |
| @Override |
| public List<MetaProperty> getProperties() { |
| checkInitalised(); |
| Map<String, MetaProperty> propertyMap = classPropertyIndex.get(theCachedClass); |
| if (propertyMap == null) { |
| // GROOVY-6903: May happen in some special environment, like Android, due to class-loading issues |
| propertyMap = Collections.emptyMap(); |
| } |
| // simply return the values of the metaproperty map as a List |
| List<MetaProperty> ret = new ArrayList<>(propertyMap.size()); |
| for (MetaProperty mp : propertyMap.values()) { |
| if (mp instanceof CachedField && (mp.getModifiers() & Opcodes.ACC_SYNTHETIC) != 0) { |
| continue; |
| } |
| if (mp instanceof MetaBeanProperty) { |
| MetaBeanProperty mbp = (MetaBeanProperty) mp; |
| // filter out extrinsic properties (DGM, ...) |
| boolean getter = true, setter = true; |
| MetaMethod getterMetaMethod = mbp.getGetter(); |
| if (getterMetaMethod == null || getterMetaMethod instanceof GeneratedMetaMethod || getterMetaMethod instanceof NewInstanceMetaMethod) { |
| getter = false; |
| } |
| MetaMethod setterMetaMethod = mbp.getSetter(); |
| if (setterMetaMethod == null || setterMetaMethod instanceof GeneratedMetaMethod || setterMetaMethod instanceof NewInstanceMetaMethod) { |
| setter = false; |
| } |
| if (!setter && !getter) continue; |
| |
| // TODO: I (ait) don't know why these strange tricks needed and comment following as it effects some Grails tests |
| // if (!setter && mbp.getSetter() != null) { |
| // mp = new MetaBeanProperty(mbp.getName(), mbp.getType(), mbp.getGetter(), null); |
| // } |
| // if (!getter && mbp.getGetter() != null) { |
| // mp = new MetaBeanProperty(mbp.getName(), mbp.getType(), null, mbp.getSetter()); |
| // } |
| |
| if (!permissivePropertyAccess) { |
| boolean getterAccessible = canAccessLegally(getterMetaMethod); |
| boolean setterAccessible = canAccessLegally(setterMetaMethod); |
| if (!(getterAccessible && setterAccessible)) continue; |
| } |
| } |
| |
| ret.add(mp); |
| } |
| return ret; |
| } |
| |
| private static boolean canAccessLegally(MetaMethod accessor) { |
| boolean accessible = true; |
| if (accessor instanceof CachedMethod) { |
| CachedMethod cm = (CachedMethod) accessor; |
| accessible = cm.canAccessLegally(MetaClassImpl.class); |
| } |
| |
| return accessible; |
| } |
| |
| /** |
| * return null if nothing valid has been found, a MetaMethod (for getter always the case if not null) or |
| * a LinkedList<MetaMethod> if there are multiple setter |
| */ |
| private static Object filterPropertyMethod(Object methodOrList, boolean isGetter, boolean booleanGetter) { |
| // Method has been optimized to reach a target of 325 bytecode size, making it JIT'able |
| Object ret = null; |
| |
| if (methodOrList instanceof MetaMethod) { |
| MetaMethod method = (MetaMethod) methodOrList; |
| int parameterCount = method.getParameterTypes().length; |
| Class<?> returnType = method.getReturnType(); |
| if (!isGetter && parameterCount == 1 |
| //&& returnType == Void.TYPE |
| ) { |
| ret = method; |
| } |
| if (isGetter && parameterCount == 0 && (booleanGetter ? returnType == Boolean.TYPE |
| : returnType != Void.TYPE && returnType != Void.class)) { |
| ret = method; |
| } |
| } else if (methodOrList instanceof FastArray) { |
| FastArray methods = (FastArray) methodOrList; |
| final int n = methods.size(); |
| final Object[] data = methods.getArray(); |
| for (int i = 0; i < n; i += 1) { |
| MetaMethod element = (MetaMethod) data[i]; |
| int parameterCount = element.getParameterTypes().length; |
| Class<?> returnType = element.getReturnType(); |
| if (!isGetter && parameterCount == 1 |
| //&& returnType == Void.TYPE |
| ) { |
| ret = addElementToList(ret, element); |
| } |
| if (isGetter && parameterCount == 0 && (booleanGetter ? returnType == Boolean.TYPE |
| : returnType != Void.TYPE && returnType != Void.class)) { |
| ret = addElementToList(ret, element); |
| } |
| } |
| } |
| |
| if (ret == null |
| || (ret instanceof MetaMethod) |
| || !isGetter) { |
| return 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 (final Object o : ((List) ret)) { |
| MetaMethod element = (MetaMethod) o; |
| int localDistance = distanceToObject(element.getReturnType()); |
| //TODO: maybe implement the case localDistance==distance |
| if (distance == -1 || distance > localDistance) { |
| distance = localDistance; |
| method = element; |
| } |
| } |
| return method; |
| } |
| |
| private static 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). |
| * |
| * @param propertyDescriptors the property descriptors |
| */ |
| private void setupProperties(final PropertyDescriptor[] propertyDescriptors) { |
| if (theCachedClass.isInterface) { |
| CachedClass superClass = ReflectionCache.OBJECT_CLASS; |
| List<CachedClass> superInterfaces = new ArrayList<>(theCachedClass.getInterfaces()); |
| superInterfaces.remove(theCachedClass); // always includes interface theCachedClass |
| // sort interfaces so that we may ensure a deterministic behaviour in case of |
| // ambiguous fields -- class implementing two interfaces using the same field |
| if (superInterfaces.size() > 1) { |
| superInterfaces.sort(CACHED_CLASS_NAME_COMPARATOR); |
| } |
| |
| Map<String, MetaProperty> iPropertyIndex = classPropertyIndex.computeIfAbsent(theCachedClass, x -> new LinkedHashMap<>()); |
| for (CachedClass sInterface : superInterfaces) { |
| Map<String, MetaProperty> sPropertyIndex = classPropertyIndex.computeIfAbsent(sInterface, x -> new LinkedHashMap<>()); |
| copyNonPrivateFields(sPropertyIndex, iPropertyIndex, null); |
| addFields(sInterface, iPropertyIndex); |
| } |
| addFields(theCachedClass, iPropertyIndex); |
| |
| applyPropertyDescriptors(propertyDescriptors); |
| applyStrayPropertyMethods(superClass, classPropertyIndex.computeIfAbsent(superClass, x -> new LinkedHashMap<>()), true); |
| } else { |
| List<CachedClass> superClasses = getSuperClasses(); |
| List<CachedClass> superInterfaces = new ArrayList<>(theCachedClass.getInterfaces()); |
| // sort interfaces so that we may ensure a deterministic behaviour in case of |
| // ambiguous fields -- class implementing two interfaces using the same field |
| if (superInterfaces.size() > 1) { |
| superInterfaces.sort(CACHED_CLASS_NAME_COMPARATOR); |
| } |
| |
| if (theCachedClass.isArray) { // add the special read-only "length" property |
| LinkedHashMap<String, MetaProperty> map = new LinkedHashMap<>(); |
| map.put("length", arrayLengthProperty); |
| classPropertyIndex.put(theCachedClass, map); |
| } |
| |
| inheritStaticInterfaceFields(superClasses, superInterfaces); |
| inheritFields(superClasses); |
| |
| applyPropertyDescriptors(propertyDescriptors); |
| |
| applyStrayPropertyMethods(superClasses, classPropertyIndex, true); |
| applyStrayPropertyMethods(superClasses, classPropertyIndexForSuper, false); |
| } |
| fillStaticPropertyIndex(); |
| } |
| |
| private void fillStaticPropertyIndex() { |
| BiConsumer<String, MetaProperty> indexStaticProperty = (name, prop) -> { |
| if (prop instanceof CachedField) { |
| CachedField field = (CachedField) prop; |
| if (!field.isStatic()) { prop = null; } |
| } else if (prop instanceof MetaBeanProperty) { |
| prop = establishStaticMetaProperty(prop); |
| } else if (prop instanceof MultipleSetterProperty) { |
| prop = ((MultipleSetterProperty) prop).createStaticVersion(); |
| } else { |
| prop = null; // ignore all other types |
| } |
| |
| if (prop != null) staticPropertyIndex.put(name, prop); |
| }; |
| |
| classPropertyIndex.computeIfAbsent(theCachedClass, x -> new LinkedHashMap<>()).forEach(indexStaticProperty); |
| |
| if (theCachedClass.isInterface) { // GROOVY-10592: static interface accessors |
| Map<String, MetaProperty> strayProperties = new LinkedHashMap<>(); |
| applyStrayPropertyMethods(theCachedClass, strayProperties, true); |
| strayProperties.forEach(indexStaticProperty); |
| } |
| } |
| |
| private MetaMethod findStaticAccessMethod(final MetaBeanProperty mbp) { |
| MetaMethod getterMethod = mbp.getGetter(); |
| // GROOVY-10962: non-static isser shadows static getter |
| if (getterMethod != null && !getterMethod.isStatic()) { |
| String name = getterMethod.getName(); |
| if (name.startsWith("is")) { |
| name = "get" + name.substring(2); |
| Object getter = filterPropertyMethod(getStaticMethods(theClass, name), true, false); |
| if (getter != null) { // keep original if no static getter |
| getterMethod = (MetaMethod) getter; |
| } |
| } |
| } |
| return getterMethod; |
| } |
| |
| private MetaProperty establishStaticMetaProperty(final MetaProperty mp) { |
| MetaBeanProperty mbp = (MetaBeanProperty) mp; |
| final MetaMethod setterMethod = mbp.getSetter(); |
| final MetaMethod getterMethod = findStaticAccessMethod(mbp); |
| final CachedField staticField = Optional.ofNullable(mbp.getField()).filter(f -> f.isStatic()).orElse(null); |
| |
| boolean getter = getterMethod == null || getterMethod.isStatic(); |
| boolean setter = setterMethod == null || setterMethod.isStatic(); |
| boolean field = staticField != null; |
| |
| MetaProperty staticProperty = staticField; |
| if (getter || setter || field) { |
| if (getter && setter) { |
| boolean shadow = (getterMethod != mbp.getGetter()); |
| if (field && !shadow) { |
| staticProperty = mbp; // mbp has static field, null or static getter and null or static setter |
| } else if (getterMethod != null || setterMethod != null) { |
| Class<?> type = mbp.getType(); |
| if (type == Boolean.TYPE && shadow) |
| type = getterMethod.getReturnType(); |
| staticProperty = new MetaBeanProperty(mbp.getName(), type, getterMethod, setterMethod); |
| } |
| } else if (getter) { |
| if (getterMethod != null) { |
| Class<?> type = getterMethod.getReturnType(); |
| MetaBeanProperty newmp = new MetaBeanProperty(mbp.getName(), type, getterMethod, null); |
| newmp.setField(staticField); |
| staticProperty = newmp; |
| } |
| } else if (setter) { |
| if (setterMethod != null) { |
| MetaBeanProperty newmp = new MetaBeanProperty(mbp.getName(), mbp.getType(), null, setterMethod); |
| newmp.setField(staticField); |
| staticProperty = newmp; |
| } |
| } |
| } |
| return staticProperty; |
| } |
| |
| private void inheritStaticInterfaceFields(List<CachedClass> superClasses, Iterable<CachedClass> interfaces) { |
| for (CachedClass iclass : interfaces) { |
| LinkedHashMap<String, MetaProperty> iPropertyIndex = classPropertyIndex.computeIfAbsent(iclass, k -> new LinkedHashMap<>()); |
| addFields(iclass, iPropertyIndex); |
| for (CachedClass superClass : superClasses) { |
| if (!iclass.getTheClass().isAssignableFrom(superClass.getTheClass())) continue; |
| LinkedHashMap<String, MetaProperty> sPropertyIndex = classPropertyIndex.computeIfAbsent(superClass, k -> new LinkedHashMap<>()); |
| copyNonPrivateFields(iPropertyIndex, sPropertyIndex, null); |
| } |
| } |
| } |
| |
| private void inheritFields(final Iterable<CachedClass> superClasses) { |
| Map<String, MetaProperty> sci = null; |
| for (CachedClass cc : superClasses) { |
| Map<String, MetaProperty> cci = classPropertyIndex.computeIfAbsent(cc, x -> new LinkedHashMap<>()); |
| if (sci != null && !sci.isEmpty()) { |
| copyNonPrivateFields(sci, cci, cc); |
| // GROOVY-9608, GROOVY-9609: add public, protected, and package-private fields to index for super |
| copyNonPrivateFields(sci, classPropertyIndexForSuper.computeIfAbsent(cc, x -> new LinkedHashMap<>()), cc); |
| } |
| sci = cci; |
| addFields(cc, cci); |
| } |
| } |
| |
| private static void addFields(CachedClass klass, Map<String, MetaProperty> propertyIndex) { |
| for (CachedField field : klass.getFields()) { |
| propertyIndex.put(field.getName(), field); |
| } |
| } |
| |
| private static void copyNonPrivateFields(Map<String, MetaProperty> from, Map<String, MetaProperty> to, @javax.annotation.Nullable CachedClass klass) { |
| for (Map.Entry<String, MetaProperty> entry : from.entrySet()) { |
| CachedField field = (CachedField) entry.getValue(); |
| int modifiers = field.getModifiers(); |
| if (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers) || (!Modifier.isPrivate(modifiers) |
| && klass != null && inSamePackage(field.getDeclaringClass(), klass.getTheClass()))) { |
| to.put(entry.getKey(), field); |
| } |
| } |
| } |
| |
| private void applyStrayPropertyMethods(Iterable<CachedClass> classes, Map<CachedClass, LinkedHashMap<String, MetaProperty>> propertyIndex, boolean isThis) { |
| for (CachedClass cc : classes) { |
| applyStrayPropertyMethods(cc, propertyIndex.computeIfAbsent(cc, x -> new LinkedHashMap<>()), isThis); |
| } |
| } |
| |
| /** |
| * Looks for any stray getters/setters that may be used to define a property. |
| */ |
| private void applyStrayPropertyMethods(CachedClass source, Map<String, MetaProperty> target, boolean isThis) { |
| MetaMethodIndex.Header header = metaMethodIndex.getHeader(source.getTheClass()); |
| for (MetaMethodIndex.Entry e = header.head; e != null; e = e.nextClassEntry) { |
| String methodName = e.name; |
| int methodNameLength = methodName.length(); |
| boolean isBooleanGetter = methodName.startsWith("is"); |
| if (methodNameLength < (isBooleanGetter ? 3 : 4)) continue; |
| |
| boolean isGetter = methodName.startsWith("get") || isBooleanGetter; |
| boolean isSetter = methodName.startsWith("set"); |
| if (!isGetter && !isSetter) continue; |
| |
| Object propertyMethods = filterPropertyMethod(isThis ? e.methods : e.methodsForSuper, isGetter, isBooleanGetter); |
| if (propertyMethods == null) continue; |
| |
| String propName = getPropName(methodName); |
| if (propertyMethods instanceof MetaMethod) { |
| createMetaBeanProperty(target, propName, isGetter, (MetaMethod) propertyMethods); |
| } else { |
| for (MetaMethod m : (Iterable<MetaMethod>) propertyMethods) { |
| createMetaBeanProperty(target, propName, isGetter, m); |
| } |
| } |
| } |
| } |
| |
| private static final ConcurrentMap<String, String> PROP_NAMES = new ConcurrentHashMap<>(1024); |
| |
| private static String getPropName(String methodName) { |
| return PROP_NAMES.computeIfAbsent(methodName, k -> { |
| // assume "is" or "[gs]et" |
| return BeanUtils.decapitalize(methodName.startsWith("is") |
| ? methodName.substring(2) : methodName.substring(3)); |
| }); |
| } |
| |
| private static MetaProperty makeReplacementMetaProperty(MetaProperty mp, String propName, boolean isGetter, MetaMethod propertyMethod) { |
| if (mp == null) { |
| if (isGetter) { |
| return new MetaBeanProperty(propName, |
| propertyMethod.getReturnType(), |
| propertyMethod, null); |
| } else { |
| //isSetter |
| return new MetaBeanProperty(propName, |
| propertyMethod.getParameterTypes()[0].getTheClass(), |
| null, propertyMethod); |
| } |
| } |
| |
| if (mp instanceof CachedField) { |
| CachedField mfp = (CachedField) mp; |
| MetaBeanProperty mbp = new MetaBeanProperty(propName, mfp.getType(), |
| isGetter ? propertyMethod : null, |
| isGetter ? null : propertyMethod); |
| mbp.setField(mfp); |
| return mbp; |
| } else if (mp instanceof MultipleSetterProperty) { |
| MultipleSetterProperty msp = (MultipleSetterProperty) mp; |
| if (isGetter) { |
| // GROOVY-10133: do not replace "isPropName()" with "getPropName()" or ... |
| if (msp.getGetter() == null || !msp.getGetter().getName().startsWith("is")) { |
| msp.setGetter(propertyMethod); |
| } |
| } |
| return msp; |
| } else if (mp instanceof MetaBeanProperty) { |
| MetaBeanProperty mbp = (MetaBeanProperty) mp; |
| if (isGetter) { |
| // GROOVY-10133: do not replace "isPropName()" with "getPropName()" or ... |
| if (mbp.getGetter() == null || !mbp.getGetter().getName().startsWith("is")) { |
| mbp.setGetter(propertyMethod); |
| } |
| return mbp; |
| } else if (mbp.getSetter() == null || mbp.getSetter() == propertyMethod) { |
| mbp.setSetter(propertyMethod); |
| return mbp; |
| } else { |
| MultipleSetterProperty msp = new MultipleSetterProperty(propName); |
| msp.setField(mbp.getField()); |
| msp.setGetter(mbp.getGetter()); |
| return msp; |
| } |
| } else { |
| throw new GroovyBugError("unknown MetaProperty class used. Class is " + mp.getClass()); |
| } |
| } |
| |
| private static void createMetaBeanProperty(Map<String, MetaProperty> propertyIndex, String propName, boolean isGetter, MetaMethod propertyMethod) { |
| // is this property already accounted for? |
| MetaProperty mp = propertyIndex.get(propName); |
| MetaProperty newMp = makeReplacementMetaProperty(mp, propName, isGetter, propertyMethod); |
| if (newMp != mp) { |
| propertyIndex.put(propName, newMp); |
| } |
| } |
| |
| protected void applyPropertyDescriptors(PropertyDescriptor[] propertyDescriptors) { |
| // now iterate over the map of property descriptors and generate |
| // MetaBeanProperty objects |
| for (PropertyDescriptor pd : propertyDescriptors) { |
| // skip if the property type is unknown (this seems to be the case if the |
| // property descriptor is based on a setX() method that has two parameters, |
| // which is not a valid property) |
| if (pd.getPropertyType() == null) |
| continue; |
| |
| // get the getter method |
| Method method = pd.getReadMethod(); |
| MetaMethod getter; |
| |
| if (method != null) { |
| CachedMethod cachedGetter = CachedMethod.find(method); |
| getter = cachedGetter == null ? null : findMethod(cachedGetter); |
| } else { |
| getter = null; |
| } |
| |
| // get the setter method |
| MetaMethod setter; |
| method = pd.getWriteMethod(); |
| if (method != null) { |
| CachedMethod cachedSetter = CachedMethod.find(method); |
| setter = cachedSetter == null ? null : findMethod(cachedSetter); |
| } else { |
| setter = null; |
| } |
| |
| // now create the MetaProperty object |
| MetaBeanProperty mp = new MetaBeanProperty(pd.getName(), pd.getPropertyType(), getter, setter); |
| addMetaBeanProperty(mp); |
| } |
| } |
| |
| /** |
| * Adds a new MetaBeanProperty to this MetaClass |
| * |
| * @param mp The MetaBeanProperty |
| */ |
| @Override |
| public void addMetaBeanProperty(MetaBeanProperty mp) { |
| MetaProperty staticProperty = establishStaticMetaProperty(mp); |
| if (staticProperty != null) { |
| staticPropertyIndex.put(mp.getName(), mp); |
| } else { |
| Map<String, MetaProperty> propertyMap = classPropertyIndex.computeIfAbsent(theCachedClass, k -> new LinkedHashMap<>()); |
| // remember field |
| CachedField field; |
| MetaProperty old = propertyMap.get(mp.getName()); |
| if (old != null) { |
| if (old instanceof MetaBeanProperty) { |
| field = ((MetaBeanProperty) old).getField(); |
| } else if (old instanceof MultipleSetterProperty) { |
| field = ((MultipleSetterProperty) old).getField(); |
| } else { |
| field = (CachedField) old; |
| } |
| mp.setField(field); |
| } |
| |
| // put it in the list |
| // this will overwrite a possible field property |
| propertyMap.put(mp.getName(), mp); |
| } |
| } |
| |
| /** |
| * <p>Retrieves a property on the given receiver for the specified arguments. The sender is the class that is requesting the property from the object. |
| * The MetaClass will attempt to establish the method to invoke based on the name and arguments provided. |
| * |
| * <p>The useSuper and fromInsideClass help the Groovy runtime perform optimisations on the call to go directly |
| * to the super class if necessary |
| * |
| * @param sender The java.lang.Class instance that is mutating the property |
| * @param object The Object which the property is being set on |
| * @param name The name of the property |
| * @param newValue The new value of the property to set |
| * @param useSuper Whether the call is to a super class property |
| * @param fromInsideClass Whether the call was invoked from the inside or the outside of the class. |
| */ |
| @Override |
| public void setProperty(final Class sender, final Object object, final String name, Object newValue, final boolean useSuper, final boolean fromInsideClass) { |
| |
| //---------------------------------------------------------------------- |
| // 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; |
| } |
| |
| checkInitalised(); |
| |
| //---------------------------------------------------------------------- |
| // Unwrap wrapped values fo now - the new MOP will handle them properly |
| //---------------------------------------------------------------------- |
| if (newValue instanceof Wrapper) newValue = ((Wrapper) newValue).unwrap(); |
| |
| MetaMethod method = null; |
| Object[] arguments = null; |
| |
| //---------------------------------------------------------------------- |
| // setter |
| //---------------------------------------------------------------------- |
| MetaProperty mp = getMetaProperty(sender, name, useSuper, isStatic); |
| MetaProperty field = null; |
| if (mp != null) { |
| if (mp instanceof MetaBeanProperty) { |
| MetaBeanProperty mbp = (MetaBeanProperty) mp; |
| method = mbp.getSetter(); |
| MetaProperty f = mbp.getField(); |
| if (method != null || (f != null && !Modifier.isFinal(f.getModifiers()))) { |
| arguments = new Object[]{newValue}; |
| field = f; |
| } |
| } else { |
| field = mp; |
| } |
| } |
| |
| // check for a category method named like a setter |
| if (!useSuper && !isStatic && GroovyCategorySupport.hasCategoryInCurrentThread() |
| && name.length() > 0) { |
| String getterName = GroovyCategorySupport.getPropertyCategorySetterName(name); |
| if (getterName != null) { |
| MetaMethod categoryMethod = getCategoryMethodSetter(sender, getterName, false); |
| if (categoryMethod != null) { |
| method = categoryMethod; |
| arguments = new Object[]{newValue}; |
| } |
| } |
| } |
| |
| //---------------------------------------------------------------------- |
| // listener method |
| //---------------------------------------------------------------------- |
| boolean ambiguousListener = false; |
| if (method == null) { |
| method = listeners.get(name); |
| ambiguousListener = method == AMBIGUOUS_LISTENER_METHOD; |
| if (method != null && |
| !ambiguousListener && |
| newValue instanceof Closure) { |
| // let's create a dynamic proxy |
| Object proxy = Proxy.newProxyInstance( |
| theClass.getClassLoader(), |
| new Class[]{method.getParameterTypes()[0].getTheClass()}, |
| new ConvertedClosure((Closure) newValue, name)); |
| arguments = new Object[]{proxy}; |
| newValue = proxy; |
| } else { |
| method = null; |
| } |
| } |
| |
| //---------------------------------------------------------------------- |
| // field |
| //---------------------------------------------------------------------- |
| if (method == null && field != null) { |
| boolean mapInstance = (isMap && !isStatic); |
| int modifiers = field.getModifiers(); |
| if (Modifier.isFinal(modifiers)) { |
| if (mapInstance) { // GROOVY-8065 |
| ((Map) object).put(name, newValue); |
| return; |
| } |
| throw new ReadOnlyPropertyException(name, theClass); // GROOVY-5985 |
| } |
| if (!mapInstance || Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)) { |
| field.setProperty(object, newValue); |
| return; |
| } |
| } |
| |
| //---------------------------------------------------------------------- |
| // generic set method |
| //---------------------------------------------------------------------- |
| // check for a generic get method provided through a category |
| if (method == null && !useSuper && !isStatic && GroovyCategorySupport.hasCategoryInCurrentThread()) { |
| method = getCategoryMethodSetter(sender, "set", true); |
| if (method != null) arguments = new Object[]{name, newValue}; |
| } |
| if (method == null && genericSetMethod != null && (genericSetMethod.isStatic() || !isStatic)) { |
| arguments = new Object[]{name, newValue}; |
| method = genericSetMethod; |
| } |
| |
| //---------------------------------------------------------------------- |
| // executing the method |
| //---------------------------------------------------------------------- |
| if (method != null) { |
| if (arguments.length == 1) { |
| newValue = DefaultTypeTransformation.castToType( |
| newValue, |
| method.getParameterTypes()[0].getTheClass()); |
| arguments[0] = newValue; |
| } else { |
| newValue = DefaultTypeTransformation.castToType( |
| newValue, |
| method.getParameterTypes()[1].getTheClass()); |
| arguments[1] = newValue; |
| } |
| |
| VM_PLUGIN.transformMetaMethod(this, method).doMethodInvoke(object, arguments); |
| return; |
| } |
| |
| //------------------------------------------------------------------ |
| // java.util.Map put method |
| //------------------------------------------------------------------ |
| if (isMap && !isStatic) { |
| ((Map) object).put(name, newValue); |
| return; |
| } |
| |
| //---------------------------------------------------------------------- |
| // missing property protocol |
| //---------------------------------------------------------------------- |
| if (ambiguousListener) { |
| throw new GroovyRuntimeException("There are multiple listeners for the property " + name + ". Please do not use the bean short form to access this listener."); |
| } |
| if (mp != null) { |
| throw new ReadOnlyPropertyException(name, theClass); |
| } |
| if (object instanceof Class && !"metaClass".equals(name)) { |
| invokeStaticMissingProperty(object, name, newValue, false); |
| } else { |
| invokeMissingProperty(object, name, newValue, false); |
| } |
| } |
| |
| private MetaProperty getMetaProperty(final Class clazz, final String name, final boolean useSuper, final boolean useStatic) { |
| if (clazz == theClass && !useSuper) |
| return getMetaProperty(name, useStatic); |
| |
| CachedClass cachedClass = ReflectionCache.getCachedClass(clazz); |
| while (true) { |
| Map<String, MetaProperty> propertyMap; |
| if (useStatic) { |
| propertyMap = staticPropertyIndex; |
| } else if (useSuper) { |
| propertyMap = classPropertyIndexForSuper.get(cachedClass); |
| } else { |
| propertyMap = classPropertyIndex.get(cachedClass); |
| } |
| if (propertyMap == null) { |
| if (cachedClass != theCachedClass) { |
| cachedClass = theCachedClass; |
| continue; |
| } else { |
| return null; |
| } |
| } |
| return propertyMap.get(name); |
| } |
| } |
| |
| private MetaProperty getMetaProperty(final String name, final boolean useStatic) { |
| CachedClass clazz = theCachedClass; |
| Map<String, MetaProperty> propertyMap; |
| if (useStatic) { |
| propertyMap = staticPropertyIndex; |
| } else { |
| propertyMap = classPropertyIndex.get(clazz); |
| } |
| if (propertyMap == null) { |
| return null; |
| } |
| return propertyMap.get(name); |
| } |
| |
| /** |
| * Retrieves the value of an attribute (field). This method is to support the Groovy runtime and not for general client API usage. |
| * |
| * @param sender The class of the object that requested the attribute |
| * @param object The instance |
| * @param attribute The name of the attribute |
| * @param useSuper Whether to look-up on the super class or not |
| * @return The attribute value |
| */ |
| @Override |
| public Object getAttribute(final Class sender, final Object object, final String attribute, final boolean useSuper) { |
| return getAttribute(sender, object, attribute, useSuper, false); |
| } |
| |
| /** |
| * Retrieves the value of an attribute (field). This method is to support the Groovy runtime and not for general client API usage. |
| * |
| * @param sender The class of the object that requested the attribute |
| * @param object The instance the attribute is to be retrieved from |
| * @param attribute The name of the attribute |
| * @param useSuper Whether to look-up on the super class or not |
| * @param fromInsideClass Whether the call was invoked from the inside or the outside of the class. |
| * @return The attribute value |
| */ |
| public Object getAttribute(final Class sender, final Object object, final String attribute, final boolean useSuper, final boolean fromInsideClass) { |
| checkInitalised(); |
| |
| boolean isStatic = (theClass != Class.class && object instanceof Class); |
| if (isStatic && object != theClass) { |
| MetaClass mc = registry.getMetaClass((Class<?>) object); |
| return mc.getAttribute(sender, object, attribute, useSuper); |
| } |
| |
| MetaProperty mp = getMetaProperty(sender, attribute, useSuper, isStatic); |
| if (mp != null) { |
| if (mp instanceof MetaBeanProperty) { |
| MetaBeanProperty mbp = (MetaBeanProperty) mp; |
| mp = mbp.getField(); |
| } |
| try { |
| // delegate the get operation to the metaproperty |
| if (mp != null) return mp.getProperty(object); |
| } catch (Exception e) { |
| throw new GroovyRuntimeException("Cannot read field: " + attribute, e); |
| } |
| } |
| |
| throw new MissingFieldException(attribute, !useSuper ? theClass : theClass.getSuperclass()); |
| } |
| |
| /** |
| * <p>Sets an attribute on the given receiver for the specified arguments. The sender is the class that is setting the attribute from the object. |
| * The MetaClass will attempt to establish the method to invoke based on the name and arguments provided. |
| * |
| * <p>The isCallToSuper and fromInsideClass help the Groovy runtime perform optimisations on the call to go directly |
| * to the super class if necessary |
| * |
| * @param sender The java.lang.Class instance that is mutating the property |
| * @param object The Object which the property is being set on |
| * @param attribute The name of the attribute, |
| * @param newValue The new value of the attribute to set |
| * @param useSuper Whether the call is to a super class property |
| * @param fromInsideClass Whether the call was invoked from the inside or the outside of the class |
| */ |
| @Override |
| public void setAttribute(final Class sender, final Object object, final String attribute, final Object newValue, final boolean useSuper, final boolean fromInsideClass) { |
| checkInitalised(); |
| |
| boolean isStatic = (theClass != Class.class && object instanceof Class); |
| if (isStatic && object != theClass) { |
| MetaClass mc = registry.getMetaClass((Class<?>) object); |
| mc.setAttribute(sender, object, attribute, newValue, useSuper, fromInsideClass); |
| return; |
| } |
| |
| MetaProperty mp = getMetaProperty(sender, attribute, useSuper, isStatic); |
| if (mp != null) { |
| if (mp instanceof MetaBeanProperty) { |
| MetaBeanProperty mbp = (MetaBeanProperty) mp; |
| mp = mbp.getField(); |
| } |
| if (mp != null) { |
| mp.setProperty(object, newValue); |
| return; |
| } |
| } |
| |
| throw new MissingFieldException(attribute, !useSuper ? theClass : theClass.getSuperclass()); |
| } |
| |
| /** |
| * Obtains a reference to the original AST for the MetaClass if it is available at runtime |
| * |
| * @return The original AST or null if it cannot be returned |
| */ |
| @Override |
| public ClassNode getClassNode() { |
| if (classNode == null && GroovyObject.class.isAssignableFrom(theClass)) { |
| // let's try load it from the classpath |
| String groovyFile = theClass.getName(); |
| int idx = groovyFile.indexOf('$'); |
| if (idx > 0) { |
| groovyFile = groovyFile.substring(0, idx); |
| } |
| groovyFile = groovyFile.replace('.', '/') + ".groovy"; |
| |
| //System.out.println("Attempting to load: " + groovyFile); |
| URL url = theClass.getClassLoader().getResource(groovyFile); |
| if (url == null) { |
| url = Thread.currentThread().getContextClassLoader().getResource(groovyFile); |
| } |
| if (url != null) { |
| try { |
| |
| /* |
| * todo there is no CompileUnit in scope so class name |
| * checking won't work but that mostly affects the bytecode |
| * generation rather than viewing the AST |
| */ |
| CompilationUnit.ClassgenCallback search = (writer, node) -> { |
| if (node.getName().equals(theClass.getName())) { |
| MetaClassImpl.this.classNode = node; |
| } |
| }; |
| |
| 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; |
| } |
| |
| /** |
| * Returns a string representation of this metaclass |
| */ |
| @Override |
| public String toString() { |
| return super.toString() + "[" + theClass + "]"; |
| } |
| |
| // Implementation methods |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * adds a MetaMethod to this class. WARNING: this method will not |
| * do the necessary steps for multimethod logic and using this |
| * method doesn't mean, that a method added here is replacing another |
| * method from a parent class completely. These steps are usually done |
| * by initialize, which means if you need these steps, you have to add |
| * the method before running initialize the first time. |
| * |
| * @param method the MetaMethod |
| * @see #initialize() |
| */ |
| @Override |
| public void addMetaMethod(MetaMethod method) { |
| if (isInitialized()) { |
| throw new RuntimeException("Already initialized, cannot add new method: " + method); |
| } |
| |
| final CachedClass declaringClass = method.getDeclaringClass(); |
| addMetaMethodToIndex(method, metaMethodIndex.getHeader(declaringClass.getTheClass())); |
| } |
| |
| protected void addMetaMethodToIndex(MetaMethod method, MetaMethodIndex.Header header) { |
| checkIfStdMethod(method); |
| |
| String name = method.getName(); |
| MetaMethodIndex.Entry e = metaMethodIndex.getOrPutMethods(name, header); |
| if (method.isStatic()) { |
| e.staticMethods = metaMethodIndex.addMethodToList(e.staticMethods, method); |
| } |
| e.methods = metaMethodIndex.addMethodToList(e.methods, method); |
| } |
| |
| /** |
| * Checks if the metaMethod is a method from the GroovyObject interface such as setProperty, getProperty and invokeMethod |
| * |
| * @param metaMethod The metaMethod instance |
| * @see GroovyObject |
| */ |
| protected final void checkIfGroovyObjectMethod(MetaMethod metaMethod) { |
| if (metaMethod instanceof ClosureMetaMethod |
| || metaMethod instanceof NewInstanceMetaMethod |
| || metaMethod instanceof MixinInstanceMetaMethod) { |
| if (isGetPropertyMethod(metaMethod)) { |
| getPropertyMethod = metaMethod; |
| } else if (isInvokeMethod(metaMethod)) { |
| invokeMethodMethod = metaMethod; |
| } else if (isSetPropertyMethod(metaMethod)) { |
| setPropertyMethod = metaMethod; |
| } |
| } |
| } |
| |
| private static boolean isSetPropertyMethod(MetaMethod metaMethod) { |
| return SET_PROPERTY_METHOD.equals(metaMethod.getName()) && metaMethod.getParameterTypes().length == 2; |
| } |
| |
| private static boolean isGetPropertyMethod(MetaMethod metaMethod) { |
| return GET_PROPERTY_METHOD.equals(metaMethod.getName()); |
| } |
| |
| private static boolean isInvokeMethod(MetaMethod metaMethod) { |
| return INVOKE_METHOD_METHOD.equals(metaMethod.getName()) && metaMethod.getParameterTypes().length == 2; |
| } |
| |
| private void checkIfStdMethod(MetaMethod method) { |
| checkIfGroovyObjectMethod(method); |
| |
| if (isGenericGetMethod(method) && genericGetMethod == null) { |
| genericGetMethod = method; |
| } else if (MetaClassHelper.isGenericSetMethod(method) && genericSetMethod == null) { |
| genericSetMethod = method; |
| } |
| if (method.getName().equals(PROPERTY_MISSING)) { |
| CachedClass[] parameterTypes = method.getParameterTypes(); |
| if (parameterTypes.length == 1) { |
| propertyMissingGet = method; |
| } |
| } |
| if (propertyMissingSet == null && method.getName().equals(PROPERTY_MISSING)) { |
| CachedClass[] parameterTypes = method.getParameterTypes(); |
| if (parameterTypes.length == 2) { |
| propertyMissingSet = method; |
| } |
| } |
| if (method.getName().equals(METHOD_MISSING)) { |
| CachedClass[] parameterTypes = method.getParameterTypes(); |
| if (parameterTypes.length == 2 |
| && parameterTypes[0].getTheClass() == String.class |
| && parameterTypes[1].getTheClass() == Object.class) { |
| methodMissing = method; |
| } |
| } |
| |
| if (theCachedClass.isNumber) { |
| NumberMathModificationInfo.instance.checkIfStdMethod(method); |
| } |
| } |
| |
| protected boolean isInitialized() { |
| return initialized; |
| } |
| |
| protected void setInitialized(boolean initialized) { |
| this.initialized = initialized; |
| } |
| |
| /** |
| * @return {@code false}: add method |
| * {@code null} : ignore method |
| * {@code true} : replace |
| */ |
| private static Boolean getMatchKindForCategory(final MetaMethod aMethod, final MetaMethod categoryMethod) { |
| CachedClass[] paramTypes1 = aMethod.getParameterTypes(); |
| CachedClass[] paramTypes2 = categoryMethod.getParameterTypes(); |
| int n = paramTypes1.length; |
| if (n != paramTypes2.length) return Boolean.FALSE; |
| for (int i = 0; i < n; i += 1) { |
| if (paramTypes1[i] != paramTypes2[i]) return Boolean.FALSE; |
| } |
| |
| Class selfType1 = aMethod.getDeclaringClass().getTheClass(); |
| Class selfType2 = categoryMethod.getDeclaringClass().getTheClass(); |
| // replace if self type is the same or the category self type is more specific |
| if (selfType1 == selfType2 || selfType1.isAssignableFrom(selfType2)) return Boolean.TRUE; |
| // GROOVY-6363: replace if the private method self type is more specific |
| if (aMethod.isPrivate() && selfType2.isAssignableFrom(selfType1)) return Boolean.TRUE; |
| |
| return null; |
| } |
| |
| private static 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 (Boolean.TRUE.equals(match)) { |
| list.set(j, method); |
| return; |
| // null == ignore (we have a better method already) |
| } else if (match == null) { |
| return; |
| } |
| } |
| // the cases 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); |
| } |
| |
| /** |
| * @return the matching method which should be found |
| */ |
| private MetaMethod findMethod(CachedMethod aMethod) { |
| Object methods = getMethods(theClass, aMethod.getName(), false); |
| if (methods instanceof FastArray) { |
| FastArray m = (FastArray) methods; |
| final int len = m.size(); |
| final Object[] data = m.getArray(); |
| for (int i = 0; i != len; ++i) { |
| MetaMethod method = (MetaMethod) data[i]; |
| if (method.isMethod(aMethod)) { |
| return method; |
| } |
| } |
| } else { |
| MetaMethod method = (MetaMethod) methods; |
| if (method.getName().equals(aMethod.getName()) |
| // TODO: should be better check for case when only diff in modifiers can be SYNTHETIC flag |
| // && method.getModifiers() == aMethod.getModifiers() |
| && method.getReturnType().equals(aMethod.getReturnType()) |
| && MetaMethod.equal(method.getParameterTypes(), aMethod.getParameterTypes())) { |
| return method; |
| } |
| } |
| return aMethod; |
| } |
| |
| /** |
| * Chooses the correct method to use from a list of methods which match by |
| * name. |
| * |
| * @param methodOrList the possible methods to choose from |
| * @param arguments the arguments |
| */ |
| protected Object chooseMethod(String methodName, Object methodOrList, Class[] arguments) throws MethodSelectionException { |
| Object method = chooseMethodInternal(methodName, methodOrList, arguments); |
| if (method instanceof GeneratedMetaMethod.Proxy) |
| return ((GeneratedMetaMethod.Proxy) method).proxy(); |
| return method; |
| } |
| |
| private Object chooseMethodInternal(String methodName, Object methodOrList, Class[] arguments) throws MethodSelectionException { |
| if (methodOrList instanceof MetaMethod) { |
| if (((ParameterTypes) methodOrList).isValidMethod(arguments)) { |
| return methodOrList; |
| } |
| return null; |
| } |
| |
| FastArray methods = (FastArray) methodOrList; |
| if (methods == null) return null; |
| int methodCount = methods.size(); |
| if (methodCount <= 0) { |
| return null; |
| } else if (methodCount == 1) { |
| Object method = methods.get(0); |
| if (((ParameterTypes) method).isValidMethod(arguments)) { |
| return method; |
| } |
| return null; |
| } |
| Object answer; |
| if (arguments == null || arguments.length == 0) { |
| answer = MetaClassHelper.chooseEmptyMethodParams(methods); |
| } else { |
| Object matchingMethods = null; |
| |
| final int len = methods.size; |
| Object[] data = methods.getArray(); |
| for (int i = 0; i != len; ++i) { |
| Object method = data[i]; |
| if (((ParameterTypes) method).isValidMethod(arguments)) { |
| if (matchingMethods == null) { |
| matchingMethods = method; |
| } else if (matchingMethods instanceof ArrayList) { |
| ((ArrayList) matchingMethods).add(method); |
| } else { |
| List arr = new ArrayList(4); |
| arr.add(matchingMethods); |
| arr.add(method); |
| matchingMethods = arr; |
| } |
| } |
| } |
| if (matchingMethods == null) { |
| return null; |
| } else if (!(matchingMethods instanceof ArrayList)) { |
| return matchingMethods; |
| } |
| return chooseMostSpecificParams(methodName, (List) matchingMethods, arguments); |
| |
| } |
| if (answer != null) { |
| return answer; |
| } |
| throw new MethodSelectionException(methodName, methods, arguments); |
| } |
| |
| private Object chooseMostSpecificParams(String name, List matchingMethods, Class[] arguments) { |
| return doChooseMostSpecificParams(theClass.getName(), name, matchingMethods, arguments, false); |
| } |
| |
| protected static Object doChooseMostSpecificParams(String theClassName, String name, List matchingMethods, Class[] arguments, boolean checkParametersCompatible) { |
| long matchesDistance = -1; |
| LinkedList matches = new LinkedList(); |
| for (Object method : matchingMethods) { |
| final ParameterTypes parameterTypes = (ParameterTypes) method; |
| if (checkParametersCompatible && !MetaClassHelper.parametersAreCompatible(arguments, parameterTypes.getNativeParameterTypes())) |
| continue; |
| long dist = MetaClassHelper.calculateParameterDistance(arguments, parameterTypes); |
| if (dist == 0) return method; |
| matchesDistance = handleMatches(matchesDistance, matches, method, dist); |
| } |
| |
| int size = matches.size(); |
| if (1 == size) { |
| return matches.getFirst(); |
| } |
| if (0 == size) { |
| return null; |
| } |
| |
| //more than one matching method found --> ambiguous! |
| throw new GroovyRuntimeException(createErrorMessageForAmbiguity(theClassName, name, arguments, matches)); |
| } |
| |
| protected static String createErrorMessageForAmbiguity(String theClassName, String name, Class[] arguments, LinkedList matches) { |
| StringBuilder msg = new StringBuilder("Ambiguous method overloading for method "); |
| msg.append(theClassName).append("#").append(name) |
| .append(".\nCannot resolve which method to invoke for ") |
| .append(FormatHelper.toString(arguments)) |
| .append(" due to overlapping prototypes between:"); |
| for (final Object match : matches) { |
| CachedClass[] types = ((ParameterTypes) match).getParameterTypes(); |
| msg.append("\n\t").append(FormatHelper.toString(types)); |
| } |
| return msg.toString(); |
| } |
| |
| protected static long handleMatches(long matchesDistance, LinkedList matches, Object method, long dist) { |
| if (matches.isEmpty()) { |
| matches.add(method); |
| matchesDistance = dist; |
| } else if (dist < matchesDistance) { |
| matchesDistance = dist; |
| matches.clear(); |
| matches.add(method); |
| } else if (dist == matchesDistance) { |
| matches.add(method); |
| } |
| return matchesDistance; |
| } |
| |
| private static boolean isGenericGetMethod(MetaMethod method) { |
| if (method.getName().equals("get")) { |
| CachedClass[] parameterTypes = method.getParameterTypes(); |
| return parameterTypes.length == 1 && parameterTypes[0].getTheClass() == String.class; |
| } |
| return false; |
| } |
| |
| /** |
| * Complete the initialisation process. After this method |
| * is called no methods should be added to the metaclass. |
| * Invocation of methods or access to fields/properties is |
| * forbidden unless this method is called. This method |
| * should contain any initialisation code, taking a longer |
| * time to complete. An example is the creation of the |
| * Reflector. It is suggested to synchronize this |
| * method. |
| */ |
| @Override |
| public synchronized void initialize() { |
| if (!isInitialized()) { |
| reinitialize(); |
| } |
| } |
| |
| protected synchronized void reinitialize() { |
| fillMethodIndex(); |
| try { |
| addProperties(); |
| } catch (Throwable e) { |
| if (!AndroidSupport.isRunningAndroid()) { |
| UncheckedThrow.rethrow(e); |
| } |
| // Introspection failure... |
| // May happen in Android |
| } |
| setInitialized(true); |
| } |
| |
| private void addProperties() { |
| BeanInfo info; |
| // introspect |
| try { |
| if (isBeanDerivative(theClass)) { |
| info = (BeanInfo) doPrivileged((PrivilegedExceptionAction) () -> Introspector.getBeanInfo(theClass, Introspector.IGNORE_ALL_BEANINFO)); |
| } else { |
| info = (BeanInfo) doPrivileged((PrivilegedExceptionAction) () -> Introspector.getBeanInfo(theClass)); |
| } |
| } catch (PrivilegedActionException pae) { |
| throw new GroovyRuntimeException("exception during bean introspection", pae.getException()); |
| } |
| PropertyDescriptor[] descriptors = info.getPropertyDescriptors(); |
| // build up the metaproperties based on the public fields, property descriptors, |
| // and the getters and setters |
| setupProperties(descriptors); |
| |
| EventSetDescriptor[] eventDescriptors = info.getEventSetDescriptors(); |
| for (EventSetDescriptor descriptor : eventDescriptors) { |
| Method[] listenerMethods = descriptor.getListenerMethods(); |
| for (Method listenerMethod : listenerMethods) { |
| final MetaMethod metaMethod = CachedMethod.find(descriptor.getAddListenerMethod()); |
| // GROOVY-5202 |
| // there might be a non-public listener of some kind |
| // we skip that here |
| if (metaMethod == null) continue; |
| addToAllMethodsIfPublic(metaMethod); |
| String name = listenerMethod.getName(); |
| if (listeners.containsKey(name)) { |
| listeners.put(name, AMBIGUOUS_LISTENER_METHOD); |
| } else { |
| listeners.put(name, metaMethod); |
| } |
| } |
| } |
| } |
| |
| @SuppressWarnings("removal") // TODO a future Groovy version should perform the operation not as a privileged action |
| private Object doPrivileged(PrivilegedExceptionAction action) throws PrivilegedActionException { |
| return java.security.AccessController.doPrivileged(action); |
| } |
| |
| private static boolean isBeanDerivative(Class theClass) { |
| Class next = theClass; |
| while (next != null) { |
| if (Arrays.asList(next.getInterfaces()).contains(BeanInfo.class)) return true; |
| next = next.getSuperclass(); |
| } |
| return false; |
| } |
| |
| private void addToAllMethodsIfPublic(final MetaMethod metaMethod) { |
| if (metaMethod.isPublic()) |
| allMethods.add(metaMethod); |
| } |
| |
| /** |
| * Retrieves the list of MetaMethods held by the class. This list does not include MetaMethods added by groovy.lang.ExpandoMetaClass. |
| * |
| * @return A list of MetaMethods |
| */ |
| @Override |
| public List<MetaMethod> getMethods() { |
| return allMethods; |
| } |
| |
| /** |
| * Retrieves the list of MetaMethods held by this class. This list includes MetaMethods added by groovy.lang.ExpandoMetaClass. |
| * |
| * @return A list of MetaMethods |
| */ |
| @Override |
| public List<MetaMethod> getMetaMethods() { |
| return new ArrayList<>(newGroovyMethodsSet); |
| } |
| |
| protected void dropStaticMethodCache(String name) { |
| metaMethodIndex.clearCaches(name); |
| } |
| |
| protected void dropMethodCache(String name) { |
| metaMethodIndex.clearCaches(name); |
| } |
| |
| /** |
| * Create a CallSite |
| */ |
| public CallSite createPojoCallSite(CallSite site, Object receiver, Object[] args) { |
| if (!(this instanceof AdaptingMetaClass)) { |
| Class[] params = MetaClassHelper.convertToTypeArray(args); |
| MetaMethod metaMethod = getMethodWithCachingInternal(getTheClass(), site, params); |
| if (metaMethod != null) |
| return PojoMetaMethodSite.createPojoMetaMethodSite(site, this, metaMethod, params, receiver, args); |
| } |
| return new PojoMetaClassSite(site, this); |
| } |
| |
| /** |
| * Create a CallSite |
| */ |
| public CallSite createStaticSite(CallSite site, Object[] args) { |
| if (!(this instanceof AdaptingMetaClass)) { |
| Class[] params = MetaClassHelper.convertToTypeArray(args); |
| MetaMethod metaMethod = retrieveStaticMethod(site.getName(), args); |
| if (metaMethod != null) |
| return StaticMetaMethodSite.createStaticMetaMethodSite(site, this, metaMethod, params, args); |
| } |
| return new StaticMetaClassSite(site, this); |
| } |
| |
| /** |
| * Create a CallSite |
| */ |
| public CallSite createPogoCallSite(CallSite site, Object[] args) { |
| if (!GroovyCategorySupport.hasCategoryInCurrentThread() && !(this instanceof AdaptingMetaClass)) { |
| Class[] params = MetaClassHelper.convertToTypeArray(args); |
| CallSite tempSite = site; |
| if (site.getName().equals(CALL_METHOD) && GeneratedClosure.class.isAssignableFrom(theClass)) { |
| // here, we want to point to a method named "doCall" instead of "call" |
| // but we don't want to replace the original call site name, otherwise |
| // we loose the fact that the original method name was "call" so instead |
| // we will point to a metamethod called "doCall" |
| // see GROOVY-5806 for details |
| tempSite = new AbstractCallSite(site.getArray(), site.getIndex(), DO_CALL_METHOD); |
| } |
| MetaMethod metaMethod = getMethodWithCachingInternal(theClass, tempSite, params); |
| if (metaMethod != null) |
| return PogoMetaMethodSite.createPogoMetaMethodSite(site, this, metaMethod, params, args); |
| } |
| return new PogoMetaClassSite(site, this); |
| } |
| |
| /** |
| * Create a CallSite |
| */ |
| public CallSite createPogoCallCurrentSite(CallSite site, Class sender, Object[] args) { |
| if (!GroovyCategorySupport.hasCategoryInCurrentThread() && !(this instanceof AdaptingMetaClass)) { |
| Class[] params = MetaClassHelper.convertToTypeArray(args); |
| MetaMethod metaMethod = getMethodWithCachingInternal(sender, site, params); |
| if (metaMethod != null) |
| return PogoMetaMethodSite.createPogoMetaMethodSite(site, this, metaMethod, params, args); |
| } |
| return new PogoMetaClassSite(site, this); |
| } |
| |
| /** |
| * Create a CallSite |
| */ |
| public CallSite createConstructorSite(CallSite site, Object[] args) { |
| if (!(this instanceof AdaptingMetaClass)) { |
| Class[] argTypes = MetaClassHelper.convertToTypeArray(args); |
| CachedConstructor constructor = (CachedConstructor) chooseMethod(CONSTRUCTOR_NAME, constructors, argTypes); |
| if (constructor != null) { |
| return ConstructorSite.createConstructorSite(site, this, constructor, argTypes, args); |
| } |
| if ((args.length == 1 && args[0] instanceof Map) || |
| (args.length == 2 && args[1] instanceof Map && |
| theClass.getEnclosingClass() != null && |
| theClass.getEnclosingClass().isAssignableFrom(argTypes[0]))) { |
| constructor = (CachedConstructor) retrieveNamedArgCompatibleConstructor(argTypes, args); |
| if (constructor != null) { |
| return args.length == 1 |
| ? new ConstructorSite.NoParamSite(site, this, constructor, argTypes) |
| : new ConstructorSite.NoParamSiteInnerClass(site, this, constructor, argTypes); |
| } |
| } |
| } |
| return new MetaClassConstructorSite(site, this); |
| } |
| |
| /** |
| * Returns the ClassInfo for the contained Class |
| * |
| * @return The ClassInfo for the contained class. |
| */ |
| public ClassInfo getClassInfo() { |
| return theCachedClass.classInfo; |
| } |
| |
| /** |
| * Returns version of the contained Class |
| * |
| * @return The version of the contained class. |
| */ |
| public int getVersion() { |
| return theCachedClass.classInfo.getVersion(); |
| } |
| |
| /** |
| * Increments version of the contained Class |
| */ |
| public void incVersion() { |
| theCachedClass.classInfo.incVersion(); |
| } |
| |
| /** |
| * Retrieves a list of additional MetaMethods held by this class |
| * |
| * @return A list of MetaMethods |
| */ |
| public MetaMethod[] getAdditionalMetaMethods() { |
| return additionalMetaMethods; |
| } |
| |
| protected MetaBeanProperty findPropertyInClassHierarchy(String propertyName, CachedClass theClass) { |
| if (theClass == null || theClass == ReflectionCache.OBJECT_CLASS) |
| return null; |
| |
| final CachedClass superClass = theClass.getCachedSuperClass(); |
| if (superClass == null) |
| return null; |
| |
| MetaBeanProperty property = null; |
| |
| MetaClass metaClass = this.registry.getMetaClass(superClass.getTheClass()); |
| if (metaClass instanceof MutableMetaClass) { |
| property = getMetaPropertyFromMutableMetaClass(propertyName, metaClass); |
| if (property == null) { |
| if (superClass != ReflectionCache.OBJECT_CLASS) { |
| property = findPropertyInClassHierarchy(propertyName, superClass); |
| } |
| if (property == null) { |
| final Class[] interfaces = theClass.getTheClass().getInterfaces(); |
| property = searchInterfacesForMetaProperty(propertyName, interfaces); |
| } |
| } |
| } |
| return property; |
| } |
| |
| private MetaBeanProperty searchInterfacesForMetaProperty(String propertyName, Class[] interfaces) { |
| MetaBeanProperty property = null; |
| for (Class anInterface : interfaces) { |
| MetaClass metaClass = registry.getMetaClass(anInterface); |
| if (metaClass instanceof MutableMetaClass) { |
| property = getMetaPropertyFromMutableMetaClass(propertyName, metaClass); |
| if (property != null) break; |
| } |
| Class[] superInterfaces = anInterface.getInterfaces(); |
| if (superInterfaces.length > 0) { |
| property = searchInterfacesForMetaProperty(propertyName, superInterfaces); |
| if (property != null) break; |
| } |
| |
| } |
| return property; |
| } |
| |
| private static MetaBeanProperty getMetaPropertyFromMutableMetaClass(String propertyName, MetaClass metaClass) { |
| final boolean isModified = ((MutableMetaClass) metaClass).isModified(); |
| if (isModified) { |
| final MetaProperty metaProperty = metaClass.getMetaProperty(propertyName); |
| if (metaProperty instanceof MetaBeanProperty) |
| return (MetaBeanProperty) metaProperty; |
| } |
| return null; |
| } |
| |
| protected MetaMethod findMixinMethod(String methodName, Class[] arguments) { |
| return null; |
| } |
| |
| protected static MetaMethod findMethodInClassHierarchy(Class instanceKlazz, String methodName, Class[] arguments, MetaClass metaClass) { |
| |
| if (metaClass instanceof MetaClassImpl) { |
| boolean check = false; |
| for (ClassInfo ci : ((MetaClassImpl) metaClass).theCachedClass.getHierarchy()) { |
| final MetaClass aClass = ci.getStrongMetaClass(); |
| if (aClass instanceof MutableMetaClass && ((MutableMetaClass) aClass).isModified()) { |
| check = true; |
| break; |
| } |
| } |
| |
| if (!check) |
| return null; |
| } |
| |
| MetaMethod method = null; |
| |
| Class superClass; |
| if (metaClass.getTheClass().isArray() && !metaClass.getTheClass().getComponentType().isPrimitive() && metaClass.getTheClass().getComponentType() != Object.class) { |
| superClass = Object[].class; |
| } else { |
| superClass = metaClass.getTheClass().getSuperclass(); |
| } |
| |
| if (superClass != null) { |
| MetaClass superMetaClass = GroovySystem.getMetaClassRegistry().getMetaClass(superClass); |
| method = findMethodInClassHierarchy(instanceKlazz, methodName, arguments, superMetaClass); |
| } else { |
| if (metaClass.getTheClass().isInterface()) { |
| MetaClass superMetaClass = GroovySystem.getMetaClassRegistry().getMetaClass(Object.class); |
| method = findMethodInClassHierarchy(instanceKlazz, methodName, arguments, superMetaClass); |
| } |
| } |
| |
| method = findSubClassMethod(instanceKlazz, methodName, arguments, metaClass, method); |
| |
| method = getMetaMethod(instanceKlazz, methodName, arguments, metaClass, method); |
| |
| method = findOwnMethod(instanceKlazz, methodName, arguments, metaClass, method); |
| |
| return method; |
| } |
| |
| private static MetaMethod getMetaMethod(Class instanceKlazz, String methodName, Class[] arguments, MetaClass metaClass, MetaMethod method) { |
| MetaMethod infMethod = searchInterfacesForMetaMethod(instanceKlazz, methodName, arguments, metaClass); |
| if (infMethod != null) { |
| method = (method == null ? infMethod : mostSpecific(method, infMethod, instanceKlazz)); |
| } |
| return method; |
| } |
| |
| private static MetaMethod findSubClassMethod(Class instanceKlazz, String methodName, Class[] arguments, MetaClass metaClass, MetaMethod method) { |
| if (metaClass instanceof MetaClassImpl) { |
| Object list = ((MetaClassImpl) metaClass).getSubclassMetaMethods(methodName); |
| if (list != null) { |
| if (list instanceof MetaMethod) { |
| MetaMethod m = (MetaMethod) list; |
| method = findSubClassMethod(instanceKlazz, arguments, method, m); |
| } else { |
| FastArray arr = (FastArray) list; |
| for (int i = 0; i != arr.size(); ++i) { |
| MetaMethod m = (MetaMethod) arr.get(i); |
| method = findSubClassMethod(instanceKlazz, arguments, method, m); |
| } |
| } |
| } |
| } |
| return method; |
| } |
| |
| private static MetaMethod findSubClassMethod(Class instanceKlazz, Class[] arguments, MetaMethod method, MetaMethod m) { |
| if (m.getDeclaringClass().getTheClass().isAssignableFrom(instanceKlazz) && m.isValidExactMethod(arguments)) { |
| method = (method == null ? m : mostSpecific(method, m, instanceKlazz)); |
| } |
| return method; |
| } |
| |
| private static MetaMethod mostSpecific(MetaMethod method, MetaMethod newMethod, Class instanceKlazz) { |
| Class newMethodC = newMethod.getDeclaringClass().getTheClass(); |
| Class methodC = method.getDeclaringClass().getTheClass(); |
| |
| if (!newMethodC.isAssignableFrom(instanceKlazz)) |
| return method; |
| |
| if (newMethodC == methodC) |
| return newMethod; |
| |
| if (newMethodC.isAssignableFrom(methodC)) { |
| return method; |
| } |
| |
| if (methodC.isAssignableFrom(newMethodC)) { |
| return newMethod; |
| } |
| |
| return newMethod; |
| } |
| |
| private static MetaMethod searchInterfacesForMetaMethod(Class instanceKlazz, String methodName, Class[] arguments, MetaClass metaClass) { |
| Class[] interfaces = metaClass.getTheClass().getInterfaces(); |
| |
| MetaMethod method = null; |
| for (Class anInterface : interfaces) { |
| MetaClass infMetaClass = GroovySystem.getMetaClassRegistry().getMetaClass(anInterface); |
| method = getMetaMethod(instanceKlazz, methodName, arguments, infMetaClass, method); |
| } |
| |
| method = findSubClassMethod(instanceKlazz, methodName, arguments, metaClass, method); |
| |
| method = findOwnMethod(instanceKlazz, methodName, arguments, metaClass, method); |
| |
| return method; |
| } |
| |
| protected static MetaMethod findOwnMethod(Class instanceKlazz, String methodName, Class[] arguments, MetaClass metaClass, MetaMethod method) { |
| // we trick ourselves here |
| if (instanceKlazz != metaClass.getTheClass()) { |
| MetaMethod ownMethod = metaClass.pickMethod(methodName, arguments); |
| if (ownMethod != null) { |
| method = (method == null ? ownMethod : mostSpecific(method, ownMethod, instanceKlazz)); |
| } |
| } |
| return method; |
| } |
| |
| protected Object getSubclassMetaMethods(String methodName) { |
| return null; |
| } |
| |
| private abstract class MethodIndexAction { |
| public void iterate() { |
| for (Map.Entry<Class, MetaMethodIndex.Header> classEntry : metaMethodIndex.methodHeaders.entrySet()) { |
| Class clazz = classEntry.getKey(); |
| if (skipClass(clazz)) continue; |
| MetaMethodIndex.Header 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(final Class<?> clazz) { |
| return false; |
| } |
| } |
| |
| /** |
| * <p>Retrieves a property on the given object for the specified arguments. |
| * |
| * @param object The Object which the property is being retrieved from |
| * @param property The name of the property |
| * @return The properties value |
| */ |
| @Override |
| public Object getProperty(Object object, String property) { |
| return getProperty(theClass, object, property, false, false); |
| } |
| |
| /** |
| * <p>Sets a property on the given object for the specified arguments. |
| * |
| * @param object The Object which the property is being retrieved from |
| * @param property The name of the property |
| * @param newValue The new value |
| */ |
| @Override |
| public void setProperty(Object object, String property, Object newValue) { |
| setProperty(theClass, object, property, newValue, false, false); |
| } |
| |
| /** |
| * Retrieves the value of an attribute (field). This method is to support the Groovy runtime and not for general client API usage. |
| * |
| * @param object The object to get the attribute from |
| * @param attribute The name of the attribute |
| * @return The attribute value |
| */ |
| @Override |
| public Object getAttribute(final Object object, final String attribute) { |
| return getAttribute(theClass, object, attribute, false, false); |
| } |
| |
| /** |
| * Sets the value of an attribute (field). This method is to support the Groovy runtime and not for general client API usage. |
| * |
| * @param object The object to get the attribute from |
| * @param attribute The name of the attribute |
| * @param newValue The new value of the attribute |
| */ |
| @Override |
| public void setAttribute(final Object object, final String attribute, final Object newValue) { |
| setAttribute(theClass, object, attribute, newValue, false, false); |
| } |
| |
| /** |
| * Selects a method by name and argument classes. This method |
| * does not search for an exact match, it searches for a compatible |
| * method. For this the method selection mechanism is used as provided |
| * by the implementation of this MetaClass. pickMethod may or may |
| * not be used during the method selection process when invoking a method. |
| * There is no warranty for that. |
| * |
| * @param methodName the name of the method to pick |
| * @param arguments the method arguments |
| * @return a matching MetaMethod or null |
| * @throws GroovyRuntimeException if there is more than one matching method |
| */ |
| @Override |
| public MetaMethod pickMethod(String methodName, Class[] arguments) { |
| return getMethodWithoutCaching(theClass, methodName, arguments, false); |
| } |
| |
| /** |
| * indicates is the metaclass method invocation for non-static methods is done |
| * through a custom invoker object. |
| * |
| * @return true - if the method invocation is not done by the metaclass itself |
| */ |
| public boolean hasCustomInvokeMethod() { |
| return invokeMethodMethod != null; |
| } |
| |
| /** |
| * indicates is the metaclass method invocation for static methods is done |
| * through a custom invoker object. |
| * |
| * @return true - if the method invocation is not done by the metaclass itself |
| */ |
| public boolean hasCustomStaticInvokeMethod() { |
| return false; |
| } |
| |
| /** |
| * remove all method call cache entries. This should be done if a |
| * method is added during runtime, but not by using a category. |
| */ |
| protected void clearInvocationCaches() { |
| metaMethodIndex.clearCaches(); |
| } |
| |
| @Deprecated |
| private static final SingleKeyHashMap.Copier NAME_INDEX_COPIER = value -> { |
| if (value instanceof FastArray) { |
| return ((FastArray) value).copy(); |
| } else { |
| return value; |
| } |
| }; |
| |
| @Deprecated |
| private static final SingleKeyHashMap.Copier METHOD_INDEX_COPIER = value -> SingleKeyHashMap.copy(new SingleKeyHashMap(false), (SingleKeyHashMap) value, NAME_INDEX_COPIER); |
| |
| /** |
| * @deprecated use {@link LinkedHashMap} instead |
| */ |
| @Deprecated |
| static 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); |
| } |
| |
| @Override |
| protected Object clone() throws CloneNotSupportedException { |
| return super.clone(); |
| } |
| } |
| |
| /** |
| * @deprecated use {@link LinkedHashMap} instead |
| */ |
| @Deprecated |
| 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) { |
| 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 { |
| |
| @Override |
| public int getModifiers() { |
| return 0; |
| } |
| |
| @Override |
| public String getName() { |
| return null; |
| } |
| |
| @Override |
| public Class getReturnType() { |
| return null; |
| } |
| |
| @Override |
| public CachedClass getDeclaringClass() { |
| return null; |
| } |
| |
| public ParameterTypes getParamTypes() { |
| return null; |
| } |
| |
| @Override |
| public Object invoke(Object object, Object[] arguments) { |
| return null; |
| } |
| } |
| |
| private Tuple2<Object, MetaMethod> invokeMethod(MetaMethod method, |
| Object delegate, |
| Closure closure, |
| String methodName, |
| Class[] argClasses, |
| Object[] originalArguments, |
| Object owner) { |
| if (method == null && delegate != closure && delegate != null) { |
| MetaClass delegateMetaClass = lookupObjectMetaClass(delegate); |
| method = delegateMetaClass.pickMethod(methodName, argClasses); |
| if (method != null) |
| return tuple(delegateMetaClass.invokeMethod(delegate, methodName, originalArguments), method); |
| } |
| if (method == null && owner != closure) { |
| MetaClass ownerMetaClass = lookupObjectMetaClass(owner); |
| method = ownerMetaClass.pickMethod(methodName, argClasses); |
| if (method != null) |
| return tuple(ownerMetaClass.invokeMethod(owner, methodName, originalArguments), method); |
| } |
| |
| return tuple(InvokeMethodResult.NONE, method); |
| } |
| |
| private enum InvokeMethodResult { |
| NONE |
| } |
| |
| public boolean isPermissivePropertyAccess() { |
| return permissivePropertyAccess; |
| } |
| |
| public void setPermissivePropertyAccess(boolean permissivePropertyAccess) { |
| this.permissivePropertyAccess = permissivePropertyAccess; |
| } |
| } |