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: 5.0.0-alpha-11
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.Closure;
import groovy.lang.ExpandoMetaClass;
import groovy.lang.GroovyInterceptable;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.MetaBeanProperty;
import groovy.lang.MetaClass;
import groovy.lang.MetaClassImpl;
import groovy.lang.MetaClassRegistry;
import groovy.lang.MetaMethod;
import groovy.lang.MetaProperty;
import groovy.lang.MissingMethodException;
import groovy.lang.MissingPropertyException;
import groovy.lang.ProxyMetaClass;
import org.codehaus.groovy.reflection.CachedClass;
import org.codehaus.groovy.reflection.CachedField;
import org.codehaus.groovy.reflection.CachedMethod;
import org.codehaus.groovy.reflection.ParameterTypes;
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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A metaclass 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! * * @since 1.5 */ public final class ClosureMetaClass extends MetaClassImpl { private volatile boolean initialized, attributeInitDone; private final FastArray closureMethods = new FastArray(3); private final Map attributes = new HashMap<>(); private MethodChooser chooser; private static MetaClassImpl classMetaClass; private static MetaClassImpl CLOSURE_METACLASS; 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(final MetaMethod m0, final MetaMethod m1) { doCall0 = m0; doCall1 = m1; } @Override public Object chooseMethod(final Class[] arguments, final 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(final Class theClass, final FastArray methods) { this.theClass = theClass; this.methods = methods; } @Override public Object chooseMethod(final Class[] arguments, final 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 Object[] data = methods.getArray(); for (int i = 0, n = methods.size(); i < n; i += 1) { Object method = data[i]; // making this false helps find matches if (((ParameterTypes) method).isValidMethod(arguments)) { matchingMethods.add(method); } } int size = matchingMethods.size(); if (0 == size) { return null; } else if (1 == size) { return matchingMethods.get(0); } return chooseMostSpecificParams(CLOSURE_DO_CALL_METHOD, matchingMethods, arguments); } } private Object chooseMostSpecificParams(final String name, final List matchingMethods, final Class[] arguments) { return doChooseMostSpecificParams(theClass.getName(), name, matchingMethods, arguments, true); } } //-------------------------------------------------------------------------- public ClosureMetaClass(final MetaClassRegistry registry, final Class theClass) { super(registry, theClass); } @Override public MetaProperty getMetaProperty(final String name) { return CLOSURE_METACLASS.getMetaProperty(name); } private static void unwrap(final Object[] arguments) { for (int i = 0; i != arguments.length; i++) { if (arguments[i] instanceof Wrapper) { arguments[i] = ((Wrapper) arguments[i]).unwrap(); } } } private MetaMethod pickClosureMethod(final Class[] argClasses) { Object answer = chooser.chooseMethod(argClasses, false); return (MetaMethod) answer; } private MetaMethod getDelegateMethod(final Closure closure, final Object delegate, final String methodName, final 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 if (delegate instanceof GroovyInterceptable) { MetaClass delegateMetaClass = lookupObjectMetaClass(delegate); // GROOVY-3015: must route calls through GroovyObject#invokeMethod(String,Object) MetaMethod interceptMethod = delegateMetaClass.pickMethod("invokeMethod", new Class[]{String.class, Object.class}); return new TransformMetaMethod(interceptMethod) { @Override public Object invoke(final Object object, final Object[] arguments) { return super.invoke(object, new Object[]{methodName, arguments}); } }; } 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; } } @Override public Object invokeMethod(final Class sender, final Object object, final String methodName, final Object[] originalArguments, final boolean isCallToSuper, final 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; final int resolveStrategy = closure.getResolveStrategy(); 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 = createTransformMetaMethod(pickClosureMethod(newArgClasses)); } if (method == null) throw new MissingMethodException(methodName, theClass, arguments, false); } boolean shouldDefer = resolveStrategy == 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; final Object owner = closure.getOwner(); final Object delegate = closure.getDelegate(); 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) { invokeOnDelegate = delegate != closure; invokeOnOwner = owner != closure; ownerFirst = false; } break; default: // Closure.OWNER_FIRST: method = getDelegateMethod(closure, owner, methodName, argClasses); callObject = owner; if (method == null) { invokeOnDelegate = delegate != closure; invokeOnOwner = owner != closure; ownerFirst = true; } break; } 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 { // no method was found; try to find a closure defined as a field of the class and run it Object value = null; try { value = getProperty(sender, object, methodName, isCallToSuper, fromInsideClass); } 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); } } throw last != null ? last : new MissingMethodException(methodName, theClass, arguments, false); } private static boolean isInternalMethod(final String methodName) { switch (methodName) { case "curry": case "ncurry": case "rcurry": case "leftShift": case "rightShift": return true; default: return false; } } private static Object[] makeArguments(final Object[] arguments, final String methodName) { if (arguments == null) return EMPTY_ARGUMENTS; return arguments; } private static Throwable unwrap(final 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(final boolean invoke1, final Object o1, final boolean invoke2, final Object o2, final String methodName, final Object[] args) { MissingMethodException first = null; if (invoke1) { try { return InvokerHelper.invokeMethod(o1, methodName, args); } catch (MissingMethodException mme) { first = mme; } catch (GroovyRuntimeException gre) { Throwable t = unwrap(gre); if (t instanceof MissingMethodException && methodName.equals(((MissingMethodException) t).getMethod())) { first = (MissingMethodException) t; } else { throw gre; } } } if (invoke2 && (!invoke1 || o1 != o2)) { try { return InvokerHelper.invokeMethod(o2, 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 t = unwrap(gre); if (t instanceof MissingMethodException) { first = (MissingMethodException) t; } 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(); } @Override 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(); setInitialized(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 = (arguments, 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 = (arguments, 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 = (arguments, 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 = (arguments, coerce) -> { if (arguments.length > minimumLength) return doCall; return null; }; } else { // general case for single method chooser = (arguments, 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(final 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(final String methodName, final Class[] argumentTypes) { if (CLOSURE_CALL_METHOD.equals(methodName) || CLOSURE_DO_CALL_METHOD.equals(methodName)) { return pickClosureMethod(argumentTypes != null ? argumentTypes : MetaClassHelper.EMPTY_CLASS_ARRAY); } return CLOSURE_METACLASS.getMetaMethod(methodName, argumentTypes); } public MetaMethod retrieveStaticMethod(final String methodName, final Class[] arguments) { return null; } @Override protected boolean isInitialized() { return initialized; } @Override protected void setInitialized(final boolean initialized) { this.initialized = initialized; } @Override public MetaMethod getStaticMetaMethod(final String name, final Object[] args) { return CLOSURE_METACLASS.getStaticMetaMethod(name, args); } public MetaMethod getStaticMetaMethod(final String name, final Class[] argTypes) { return CLOSURE_METACLASS.getStaticMetaMethod(name, argTypes); } @Override public Object getProperty(final Class sender, final Object object, final String name, final boolean useSuper, final 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(final Class sender, final Object object, final String attribute, final boolean useSuper, final 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(final Class sender, final Object object, final String attribute, final Object newValue, final boolean useSuper, final 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); } } } @Override public Object invokeStaticMethod(final Object object, final String methodName, final Object[] arguments) { return getStaticMetaClass().invokeMethod(Class.class, object, methodName, arguments, false, false); } @Override public void setProperty(final Class sender, final Object object, final String name, final Object newValue, final boolean useSuper, final 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(final int index, final Class sender, final String methodName, final Class[] arguments, final boolean isCallToSuper) { throw new UnsupportedOperationException(); } @Override public void setProperties(final Object bean, final Map map) { throw new UnsupportedOperationException(); } @Override public void addMetaBeanProperty(final MetaBeanProperty mp) { throw new UnsupportedOperationException(); } @Override public void addMetaMethod(final MetaMethod method) { throw new UnsupportedOperationException(); } @Override public void addNewInstanceMethod(final Method method) { throw new UnsupportedOperationException(); } @Override public void addNewStaticMethod(final Method method) { throw new UnsupportedOperationException(); } @Override public Constructor retrieveConstructor(final Class[] arguments) { throw new UnsupportedOperationException(); } @Override public CallSite createPojoCallSite(final CallSite site, final Object receiver, final Object[] args) { throw new UnsupportedOperationException(); } @Override public CallSite createPogoCallSite(final CallSite site, final Object[] args) { return new PogoMetaClassSite(site, this); } @Override public CallSite createPogoCallCurrentSite(final CallSite site, final Class sender, final Object[] args) { return new PogoMetaClassSite(site, this); } @Override public List respondsTo(final Object obj, final String name, final Object[] argTypes) { loadMetaInfo(); return super.respondsTo(obj, name, argTypes); } @Override public List respondsTo(final Object obj, final String name) { loadMetaInfo(); return super.respondsTo(obj, name); } private synchronized void loadMetaInfo() { if (metaMethodIndex.isEmpty()) { reinitialize(); } } @Override protected void applyPropertyDescriptors(final PropertyDescriptor[] propertyDescriptors) { // do nothing } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy