All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.groovy.runtime.metaclass.ClosureMetaClass Maven / Gradle / Ivy

There is a newer version: 3.0.21
Show newest version
/*
 *  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 org.codehaus.groovy.runtime.metaclass;

import groovy.lang.*;

import org.codehaus.groovy.reflection.*;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.MetaClassHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.codehaus.groovy.runtime.callsite.PogoMetaClassSite;
import org.codehaus.groovy.runtime.wrappers.Wrapper;
import org.codehaus.groovy.util.FastArray;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.*;

/**
 * A meta class for closures generated by the Groovy compiler. These classes
 * have special characteristics this MetaClass uses. One of these is that a
 * generated Closure has only additional doCall methods, all other methods
 * are in the Closure class as well. To use this fact this MetaClass uses
 * a MetaClass for Closure as static field And delegates calls to this
 * MetaClass if needed. This allows a lean implementation for this MetaClass.
 * Multiple generated closures will then use the same MetaClass for Closure.
 * For static dispatching this class uses the MetaClass of Class, again
 * all instances of this class will share that MetaClass. The Class MetaClass
 * is initialized lazy, because most operations do not need this MetaClass.
 * 

* The Closure and Class MetaClasses are not replaceable. *

* This MetaClass is for internal usage only! * * @author Jochen Theodorou * @since 1.5 */ public final class ClosureMetaClass extends MetaClassImpl { private boolean initialized; private final FastArray closureMethods = new FastArray(3); private Map attributes = new HashMap(); private MethodChooser chooser; private volatile boolean attributeInitDone = false; private static MetaClassImpl CLOSURE_METACLASS; private static MetaClassImpl classMetaClass; private static final Object[] EMPTY_ARGUMENTS = {}; private static final String CLOSURE_CALL_METHOD = "call"; private static final String CLOSURE_DO_CALL_METHOD = "doCall"; static { resetCachedMetaClasses(); } public static void resetCachedMetaClasses() { MetaClassImpl temp = new MetaClassImpl(Closure.class); temp.initialize(); synchronized (ClosureMetaClass.class) { CLOSURE_METACLASS = temp; } if (classMetaClass!=null) { temp = new MetaClassImpl(Class.class); temp.initialize(); synchronized (ClosureMetaClass.class) { classMetaClass = temp; } } } private static synchronized MetaClass getStaticMetaClass() { if (classMetaClass == null) { classMetaClass = new MetaClassImpl(Class.class); classMetaClass.initialize(); } return classMetaClass; } private interface MethodChooser { Object chooseMethod(Class[] arguments, boolean coerce); } private static class StandardClosureChooser implements MethodChooser { private final MetaMethod doCall0; private final MetaMethod doCall1; StandardClosureChooser(MetaMethod m0, MetaMethod m1) { doCall0 = m0; doCall1 = m1; } public Object chooseMethod(Class[] arguments, boolean coerce) { if (arguments.length == 0) return doCall0; if (arguments.length == 1) return doCall1; return null; } } private static class NormalMethodChooser implements MethodChooser { private final FastArray methods; final Class theClass; NormalMethodChooser(Class theClass, FastArray methods) { this.theClass = theClass; this.methods = methods; } public Object chooseMethod(Class[] arguments, boolean coerce) { if (arguments.length == 0) { return MetaClassHelper.chooseEmptyMethodParams(methods); } else if (arguments.length == 1 && arguments[0] == null) { return MetaClassHelper.chooseMostGeneralMethodWith1NullParam(methods); } else { List matchingMethods = new ArrayList(); final int len = methods.size(); final Object[] data = methods.getArray(); for (int i = 0; i != len; ++i) { Object method = data[i]; // making this false helps find matches if (((ParameterTypes) method).isValidMethod(arguments)) { matchingMethods.add(method); } } if (matchingMethods.isEmpty()) { return null; } else if (matchingMethods.size() == 1) { return matchingMethods.get(0); } return chooseMostSpecificParams(CLOSURE_DO_CALL_METHOD, matchingMethods, arguments); } } private Object chooseMostSpecificParams(String name, List matchingMethods, Class[] arguments) { long matchesDistance = -1; LinkedList matches = new LinkedList(); for (Iterator iter = matchingMethods.iterator(); iter.hasNext();) { Object method = iter.next(); final ParameterTypes parameterTypes = (ParameterTypes) method; Class[] paramTypes = parameterTypes.getNativeParameterTypes(); if (!MetaClassHelper.parametersAreCompatible(arguments, paramTypes)) continue; long dist = MetaClassHelper.calculateParameterDistance(arguments, parameterTypes); if (dist == 0) return method; 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); } } if (matches.size() == 1) { return matches.getFirst(); } if (matches.isEmpty()) { return null; } // more than one matching method found --> ambiguous! String msg = "Ambiguous method overloading for method "; msg += theClass.getName() + "#" + name; msg += ".\nCannot resolve which method to invoke for "; msg += InvokerHelper.toString(arguments); msg += " due to overlapping prototypes between:"; for (Object match : matches) { CachedClass[] types = ((ParameterTypes) match).getParameterTypes(); msg += "\n\t" + InvokerHelper.toString(types); } throw new GroovyRuntimeException(msg); } } public ClosureMetaClass(MetaClassRegistry registry, Class theClass) { super(registry, theClass); } public MetaProperty getMetaProperty(String name) { return CLOSURE_METACLASS.getMetaProperty(name); } private static void unwrap(Object[] arguments) { for (int i = 0; i != arguments.length; i++) { if (arguments[i] instanceof Wrapper) { arguments[i] = ((Wrapper) arguments[i]).unwrap(); } } } private MetaMethod pickClosureMethod(Class[] argClasses) { Object answer = chooser.chooseMethod(argClasses, false); return (MetaMethod) answer; } private MetaMethod getDelegateMethod(Closure closure, Object delegate, String methodName, Class[] argClasses) { if (delegate == closure || delegate == null) return null; if (delegate instanceof Class) { for (Class superClass = (Class) delegate; superClass != Object.class && superClass != null; superClass = superClass.getSuperclass()) { MetaClass mc = registry.getMetaClass(superClass); MetaMethod method = mc.getStaticMetaMethod(methodName, argClasses); if (method != null) return method; } return null; } else { MetaClass delegateMetaClass = lookupObjectMetaClass(delegate); MetaMethod method = delegateMetaClass.pickMethod(methodName, argClasses); if (method != null) { return method; } if (delegateMetaClass instanceof ExpandoMetaClass) { method = ((ExpandoMetaClass) delegateMetaClass).findMixinMethod(methodName, argClasses); if (method != null) { onMixinMethodFound(method); return method; } } if (delegateMetaClass instanceof MetaClassImpl) { method = MetaClassImpl.findMethodInClassHierarchy(getTheClass(), methodName, argClasses, this); if (method != null) { onSuperMethodFoundInHierarchy(method); return method; } } return method; } } public Object invokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) { checkInitalised(); if (object == null) { throw new NullPointerException("Cannot invoke method: " + methodName + " on null object"); } final Object[] arguments = makeArguments(originalArguments, methodName); final Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); unwrap(arguments); MetaMethod method = null; final Closure closure = (Closure) object; if (CLOSURE_DO_CALL_METHOD.equals(methodName) || CLOSURE_CALL_METHOD.equals(methodName)) { method = pickClosureMethod(argClasses); if (method == null && arguments.length == 1 && arguments[0] instanceof List) { Object[] newArguments = ((List) arguments[0]).toArray(); Class[] newArgClasses = MetaClassHelper.convertToTypeArray(newArguments); method = pickClosureMethod(newArgClasses); if (method != null) { method = new TransformMetaMethod(method) { public Object invoke(Object object, Object[] arguments) { Object firstArgument = arguments[0]; List list = (List) firstArgument; arguments = list.toArray(); return super.invoke(object, arguments); } }; } } if (method == null) throw new MissingMethodException(methodName, theClass, arguments, false); } boolean shouldDefer = closure.getResolveStrategy() == Closure.DELEGATE_ONLY && isInternalMethod(methodName); if (method == null && !shouldDefer) { method = CLOSURE_METACLASS.pickMethod(methodName, argClasses); } if (method != null) return method.doMethodInvoke(object, arguments); MissingMethodException last = null; Object callObject = object; if (method == null) { final Object owner = closure.getOwner(); final Object delegate = closure.getDelegate(); final Object thisObject = closure.getThisObject(); final int resolveStrategy = closure.getResolveStrategy(); boolean invokeOnDelegate = false; boolean invokeOnOwner = false; boolean ownerFirst = true; switch (resolveStrategy) { case Closure.TO_SELF: break; case Closure.DELEGATE_ONLY: method = getDelegateMethod(closure, delegate, methodName, argClasses); callObject = delegate; if (method == null) { invokeOnDelegate = delegate != closure && (delegate instanceof GroovyObject); } break; case Closure.OWNER_ONLY: method = getDelegateMethod(closure, owner, methodName, argClasses); callObject = owner; if (method == null) { invokeOnOwner = owner != closure && (owner instanceof GroovyObject); } break; case Closure.DELEGATE_FIRST: method = getDelegateMethod(closure, delegate, methodName, argClasses); callObject = delegate; if (method == null) { method = getDelegateMethod(closure, owner, methodName, argClasses); callObject = owner; } if (method == null) { invokeOnDelegate = delegate != closure && (delegate instanceof GroovyObject); invokeOnOwner = owner != closure && (owner instanceof GroovyObject); ownerFirst = false; } break; default: // owner first // owner first means we start with the outer most owner that is not a generated closure // this owner is equal to the this object, so we check that one first. method = getDelegateMethod(closure, thisObject, methodName, argClasses); callObject = thisObject; if (method == null) { // try finding a delegate that has that method... we start from // outside building a stack and try each delegate LinkedList list = new LinkedList(); for (Object current = closure; current != thisObject;) { if (!(current instanceof Closure)) break; Closure currentClosure = (Closure) current; if (currentClosure.getDelegate() != null) list.add(current); current = currentClosure.getOwner(); } while (!list.isEmpty() && method == null) { Closure closureWithDelegate = (Closure) list.removeLast(); Object currentDelegate = closureWithDelegate.getDelegate(); method = getDelegateMethod(closureWithDelegate, currentDelegate, methodName, argClasses); callObject = currentDelegate; } } if (method == null) { invokeOnDelegate = delegate != closure && (delegate instanceof GroovyObject); invokeOnOwner = owner != closure && (owner instanceof GroovyObject); } } if (method == null && (invokeOnOwner || invokeOnDelegate)) { try { if (ownerFirst) { return invokeOnDelegationObjects(invokeOnOwner, owner, invokeOnDelegate, delegate, methodName, arguments); } else { return invokeOnDelegationObjects(invokeOnDelegate, delegate, invokeOnOwner, owner, methodName, arguments); } } catch (MissingMethodException mme) { last = mme; } } } if (method != null) { MetaClass metaClass = registry.getMetaClass(callObject.getClass()); if (metaClass instanceof ProxyMetaClass) { return metaClass.invokeMethod(callObject, methodName, arguments); } else { return method.doMethodInvoke(callObject, arguments); } } else { // if no method was found, try to find a closure defined as a field of the class and run it Object value = null; try { value = this.getProperty(object, methodName); } catch (MissingPropertyException mpe) { // ignore } if (value instanceof Closure) { // This test ensures that value != this If you ever change this ensure that value != this Closure cl = (Closure) value; MetaClass delegateMetaClass = cl.getMetaClass(); return delegateMetaClass.invokeMethod(cl.getClass(), closure, CLOSURE_DO_CALL_METHOD, originalArguments, false, fromInsideClass); } } if (last != null) throw last; throw new MissingMethodException(methodName, theClass, arguments, false); } private static boolean isInternalMethod(String methodName) { return methodName.equals("curry") || methodName.equals("ncurry") || methodName.equals("rcurry") || methodName.equals("leftShift") || methodName.equals("rightShift"); } private static Object[] makeArguments(Object[] arguments, String methodName) { if (arguments == null) return EMPTY_ARGUMENTS; return arguments; } private static Throwable unwrap(GroovyRuntimeException gre) { Throwable th = gre; if (th.getCause() != null && th.getCause() != gre) th = th.getCause(); if (th != gre && (th instanceof GroovyRuntimeException)) return unwrap((GroovyRuntimeException) th); return th; } private static Object invokeOnDelegationObjects( boolean invoke1, Object o1, boolean invoke2, Object o2, String methodName, Object[] args) { MissingMethodException first = null; if (invoke1) { GroovyObject go = (GroovyObject) o1; try { return go.invokeMethod(methodName, args); } catch (MissingMethodException mme) { first = mme; } catch (GroovyRuntimeException gre) { Throwable th = unwrap(gre); if ((th instanceof MissingMethodException) && (methodName.equals(((MissingMethodException) th).getMethod()))) { first = (MissingMethodException) th; } else { throw gre; } } } if (invoke2 && (!invoke1 || o1 != o2)) { GroovyObject go = (GroovyObject) o2; try { return go.invokeMethod(methodName, args); } catch (MissingMethodException mme) { // patch needed here too, but we need a test case to trip it first if (first == null) first = mme; } catch (GroovyRuntimeException gre) { Throwable th = unwrap(gre); if (th instanceof MissingMethodException) { first = (MissingMethodException) th; } else { throw gre; } } } throw first; } private synchronized void initAttributes() { if (!attributes.isEmpty()) return; attributes.put("!", null); // just a dummy for later CachedField[] fieldArray = theCachedClass.getFields(); for (CachedField aFieldArray : fieldArray) { attributes.put(aFieldArray.getName(), aFieldArray); } attributeInitDone = !attributes.isEmpty(); } public synchronized void initialize() { if (!isInitialized()) { CachedMethod[] methodArray = theCachedClass.getMethods(); synchronized (theCachedClass) { for (final CachedMethod cachedMethod : methodArray) { if (!cachedMethod.getName().equals(CLOSURE_DO_CALL_METHOD)) continue; closureMethods.add(cachedMethod); } } assignMethodChooser(); initialized = true; } } private void assignMethodChooser() { if (closureMethods.size() == 1) { final MetaMethod doCall = (MetaMethod) closureMethods.get(0); final CachedClass[] c = doCall.getParameterTypes(); int length = c.length; if (length == 0) { // no arg method chooser = new MethodChooser() { public Object chooseMethod(Class[] arguments, boolean coerce) { if (arguments.length == 0) return doCall; return null; } }; } else { if (length == 1 && c[0].getTheClass() == Object.class) { // Object fits all, so simple dispatch rule here chooser = new MethodChooser() { public Object chooseMethod(Class[] arguments, boolean coerce) { // <2, because foo() is same as foo(null) if (arguments.length < 2) return doCall; return null; } }; } else { boolean allObject = true; for (int i = 0; i < c.length - 1; i++) { if (c[i].getTheClass() != Object.class) { allObject = false; break; } } if (allObject && c[c.length - 1].getTheClass() == Object.class) { // all arguments are object, so test only if argument number is correct chooser = new MethodChooser() { public Object chooseMethod(Class[] arguments, boolean coerce) { if (arguments.length == c.length) return doCall; return null; } }; } else { if (allObject && c[c.length - 1].getTheClass() == Object[].class) { // all arguments are Object but last, which is a vargs argument, that // will fit all, so just test if the number of argument is equal or // more than the parameters we have. final int minimumLength = c.length - 2; chooser = new MethodChooser() { public Object chooseMethod(Class[] arguments, boolean coerce) { if (arguments.length > minimumLength) return doCall; return null; } }; } else { // general case for single method chooser = new MethodChooser() { public Object chooseMethod(Class[] arguments, boolean coerce) { if (doCall.isValidMethod(arguments)) { return doCall; } return null; } }; } } } } } else if (closureMethods.size() == 2) { MetaMethod m0 = null, m1 = null; for (int i = 0; i != closureMethods.size(); ++i) { MetaMethod m = (MetaMethod) closureMethods.get(i); CachedClass[] c = m.getParameterTypes(); if (c.length == 0) { m0 = m; } else { if (c.length == 1 && c[0].getTheClass() == Object.class) { m1 = m; } } } if (m0 != null && m1 != null) { // standard closure (2 methods because "it" is with default null) chooser = new StandardClosureChooser(m0, m1); } } if (chooser == null) { // standard chooser for cases if it is not a single method and if it is // not the standard closure. chooser = new NormalMethodChooser(theClass, closureMethods); } } 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; return registry.getMetaClass(ownerClass); } MetaClass metaClass = InvokerHelper.getMetaClass(object); return metaClass; } @Override public List getMethods() { List answer = CLOSURE_METACLASS.getMetaMethods(); answer.addAll(closureMethods.toList()); return answer; } @Override public List getMetaMethods() { return CLOSURE_METACLASS.getMetaMethods(); } @Override public List getProperties() { return CLOSURE_METACLASS.getProperties(); } @Override public MetaMethod pickMethod(String name, Class[] argTypes) { if (argTypes == null) argTypes = MetaClassHelper.EMPTY_CLASS_ARRAY; if (name.equals(CLOSURE_CALL_METHOD) || name.equals(CLOSURE_DO_CALL_METHOD)) { return pickClosureMethod(argTypes); } return CLOSURE_METACLASS.getMetaMethod(name, argTypes); } public MetaMethod retrieveStaticMethod(String methodName, Class[] arguments) { return null; } protected boolean isInitialized() { return initialized; } public MetaMethod getStaticMetaMethod(String name, Object[] args) { return CLOSURE_METACLASS.getStaticMetaMethod(name, args); } public MetaMethod getStaticMetaMethod(String name, Class[] argTypes) { return CLOSURE_METACLASS.getStaticMetaMethod(name, argTypes); } public Object getProperty(Class sender, Object object, String name, boolean useSuper, boolean fromInsideClass) { if (object instanceof Class) { return getStaticMetaClass().getProperty(sender, object, name, useSuper, fromInsideClass); } else { return CLOSURE_METACLASS.getProperty(sender, object, name, useSuper, fromInsideClass); } } @Override public Object getAttribute(Class sender, Object object, String attribute, boolean useSuper, boolean fromInsideClass) { if (object instanceof Class) { return getStaticMetaClass().getAttribute(sender, object, attribute, useSuper); } else { if (!attributeInitDone) initAttributes(); CachedField mfp = attributes.get(attribute); if (mfp == null) { return CLOSURE_METACLASS.getAttribute(sender, object, attribute, useSuper); } else { return mfp.getProperty(object); } } } @Override public void setAttribute(Class sender, Object object, String attribute, Object newValue, boolean useSuper, boolean fromInsideClass) { if (object instanceof Class) { getStaticMetaClass().setAttribute(sender, object, attribute, newValue, useSuper, fromInsideClass); } else { if (!attributeInitDone) initAttributes(); CachedField mfp = attributes.get(attribute); if (mfp == null) { CLOSURE_METACLASS.setAttribute(sender, object, attribute, newValue, useSuper, fromInsideClass); } else { mfp.setProperty(object, newValue); } } } public Object invokeStaticMethod(Object object, String methodName, Object[] arguments) { return getStaticMetaClass().invokeMethod(Class.class, object, methodName, arguments, false, false); } public void setProperty(Class sender, Object object, String name, Object newValue, boolean useSuper, boolean fromInsideClass) { if (object instanceof Class) { getStaticMetaClass().setProperty(sender, object, name, newValue, useSuper, fromInsideClass); } else { CLOSURE_METACLASS.setProperty(sender, object, name, newValue, useSuper, fromInsideClass); } } public MetaMethod getMethodWithoutCaching(int index, Class sender, String methodName, Class[] arguments, boolean isCallToSuper) { throw new UnsupportedOperationException(); } public void setProperties(Object bean, Map map) { throw new UnsupportedOperationException(); } public void addMetaBeanProperty(MetaBeanProperty mp) { throw new UnsupportedOperationException(); } public void addMetaMethod(MetaMethod method) { throw new UnsupportedOperationException(); } public void addNewInstanceMethod(Method method) { throw new UnsupportedOperationException(); } public void addNewStaticMethod(Method method) { throw new UnsupportedOperationException(); } public Constructor retrieveConstructor(Class[] arguments) { throw new UnsupportedOperationException(); } public CallSite createPojoCallSite(CallSite site, Object receiver, Object[] args) { throw new UnsupportedOperationException(); } public CallSite createPogoCallSite(CallSite site, Object[] args) { return new PogoMetaClassSite(site, this); } public CallSite createPogoCallCurrentSite(CallSite site, Class sender, Object[] args) { return new PogoMetaClassSite(site, this); } public List respondsTo(Object obj, String name, Object[] argTypes) { loadMetaInfo(); return super.respondsTo(obj, name, argTypes); } public List respondsTo(final Object obj, final String name) { loadMetaInfo(); return super.respondsTo(obj, name); } private synchronized void loadMetaInfo() { if (metaMethodIndex.isEmpty()) { initialized = false; super.initialize(); initialized = true; } } protected void applyPropertyDescriptors(PropertyDescriptor[] propertyDescriptors) { // do nothing } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy