This is an automated email from the ASF dual-hosted git repository. emilles pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 63bbe21978676e3449f76953aedda59ac983698d Author: Eric Milles <[email protected]> AuthorDate: Sun Nov 30 14:16:26 2025 -0600 minor refactor --- src/main/java/groovy/lang/ExpandoMetaClass.java | 337 +++++++++------------ src/main/java/groovy/lang/MetaClassImpl.java | 39 ++- .../codehaus/groovy/reflection/CachedClass.java | 29 +- .../runtime/metaclass/ClosureMetaMethod.java | 7 +- .../runtime/metaclass/MetaClassRegistryImpl.java | 3 +- .../groovy/runtime/metaclass/MetaMethodIndex.java | 7 +- src/test/groovy/bugs/Groovy3873Bug.groovy | 37 --- .../lang/ExpandoMetaClassCreationHandleTest.groovy | 311 +++++++++---------- 8 files changed, 352 insertions(+), 418 deletions(-) diff --git a/src/main/java/groovy/lang/ExpandoMetaClass.java b/src/main/java/groovy/lang/ExpandoMetaClass.java index fa3c51d51f..51e4c322a0 100644 --- a/src/main/java/groovy/lang/ExpandoMetaClass.java +++ b/src/main/java/groovy/lang/ExpandoMetaClass.java @@ -252,51 +252,44 @@ import static org.codehaus.groovy.runtime.MetaClassHelper.EMPTY_TYPE_ARRAY; * therefore synchronized as well. Any method call done through this metaclass will first check if the it is * synchronized. Should this happen during a modification, then the method cannot be selected or called unless the * modification is completed. - * <p> * * @since 1.5 */ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { - private static final String META_CLASS = "metaClass"; - private static final String CLASS = "class"; + private static final String CLASS_PROPERTY = "class"; + public static final String CONSTRUCTOR = "constructor"; + private static final String GROOVY_CONSTRUCTOR = "<init>"; + private static final String META_CLASS_PROPERTY = "metaClass"; private static final String META_METHODS = "metaMethods"; private static final String METHODS = "methods"; private static final String PROPERTIES = "properties"; - public static final String STATIC_QUALIFIER = "static"; - public static final String CONSTRUCTOR = "constructor"; - - private static final String CLASS_PROPERTY = "class"; - private static final String META_CLASS_PROPERTY = "metaClass"; - private static final String GROOVY_CONSTRUCTOR = "<init>"; + public static final String STATIC_QUALIFIER = "static"; - // These two properties are used when no ExpandoMetaClassCreationHandle is present - - private MetaClass myMetaClass; + private final boolean allowChangesAfterInit; private volatile boolean initialized; + private volatile boolean initCalled; + public boolean inRegistry; private volatile boolean modified; - private boolean initCalled; - private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); - private final Lock readLock = rwl.readLock(); - private final Lock writeLock = rwl.writeLock(); - - private final boolean allowChangesAfterInit; - public boolean inRegistry; - - private final Set<MetaMethod> inheritedMetaMethods = new HashSet<MetaMethod>(); - private final Map<String, MetaProperty> beanPropertyCache = new ConcurrentHashMap<String, MetaProperty>(16, 0.75f, 1); - private final Map<String, MetaProperty> staticBeanPropertyCache = new ConcurrentHashMap<String, MetaProperty>(16, 0.75f, 1); - private final Map<MethodKey, MetaMethod> expandoMethods = new ConcurrentHashMap<MethodKey, MetaMethod>(16, 0.75f, 1); + private MetaClass myMetaClass; - public Collection getExpandoSubclassMethods() { - return expandoSubclassMethods.values(); + private final Lock readLock; + private final Lock writeLock; + { + var rwl = new ReentrantReadWriteLock(); + readLock = rwl.readLock(); + writeLock = rwl.writeLock(); } - private final ConcurrentHashMap expandoSubclassMethods = new ConcurrentHashMap(16, 0.75f, 1); - private final Map<String, MetaProperty> expandoProperties = new ConcurrentHashMap<String, MetaProperty>(16, 0.75f, 1); + private final Set<MetaMethod> inheritedMetaMethods = new HashSet<>(); + private final Map<String, MetaProperty> beanPropertyCache = new ConcurrentHashMap<>(16, 0.75f, 1); + private final Map<String, MetaProperty> staticBeanPropertyCache = new ConcurrentHashMap<>(16, 0.75f, 1); + private final Map<MethodKey, MetaMethod> expandoMethods = new ConcurrentHashMap<>(16, 0.75f, 1); + private final Map<String, Object> expandoSubclassMethods = new ConcurrentHashMap<>(16, 0.75f, 1); + private final Map<String, MetaProperty> expandoProperties = new ConcurrentHashMap<>(16, 0.75f, 1); private ClosureStaticMetaMethod invokeStaticMethodMethod; - private final Set<MixinInMetaClass> mixinClasses = new LinkedHashSet<MixinInMetaClass>(); + private final Set<MixinInMetaClass> mixinClasses = new LinkedHashSet<>(); public ExpandoMetaClass(Class theClass, boolean register, boolean allowChangesAfterInit, MetaMethod[] add) { this(GroovySystem.getMetaClassRegistry(), theClass, register, allowChangesAfterInit, add); @@ -315,11 +308,11 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { * @param theClass The class that the MetaClass applies to */ public ExpandoMetaClass(Class theClass) { - this(theClass,false,false,null); + this(theClass, false, false, null); } public ExpandoMetaClass(Class theClass, MetaMethod [] add) { - this(theClass,false,false,add); + this(theClass, false, false, add); } /** @@ -330,7 +323,7 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { * @param register True if the MetaClass should be registered inside the MetaClassRegistry. This defaults to true and ExpandoMetaClass will affect all instances if changed */ public ExpandoMetaClass(Class theClass, boolean register) { - this(theClass,register,false,null); + this(theClass, register, false, null); } public ExpandoMetaClass(Class theClass, boolean register, MetaMethod [] add) { @@ -355,14 +348,13 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { final CachedClass mixinClass = mixin.getMixinClass(); MetaClass metaClass = mixinClass.classInfo.getMetaClassForClass(); if (metaClass == null) { - metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(mixinClass.getTheClass()); + metaClass = registry.getMetaClass(mixinClass.getTheClass()); } MetaMethod metaMethod = metaClass.pickMethod(methodName, arguments); - if (metaMethod == null && metaClass instanceof MetaClassImpl) { - MetaClassImpl mc = (MetaClassImpl) metaClass; - for (CachedClass cl = mc.getTheCachedClass().getCachedSuperClass(); cl != null; cl = cl.getCachedSuperClass()) { - metaMethod = mc.getMethodWithoutCaching(cl.getTheClass(), methodName, arguments, false); + if (metaMethod == null && metaClass instanceof MetaClassImpl mc) { + for (CachedClass cc = mc.getTheCachedClass().getCachedSuperClass(); cc != null; cc = cc.getCachedSuperClass()) { + metaMethod = mc.getMethodWithoutCaching(cc.getTheClass(), methodName, arguments, false); if (metaMethod != null) break; } @@ -432,19 +424,17 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { public void registerSubclassInstanceMethod(MetaMethod metaMethod) { modified = true; - final String name = metaMethod.getName(); + String name = metaMethod.getName(); Object methodOrList = expandoSubclassMethods.get(name); if (methodOrList == null) { expandoSubclassMethods.put(name, metaMethod); + } else if (methodOrList instanceof MetaMethod) { + FastArray arr = new FastArray(2); + arr.add(methodOrList); + arr.add(metaMethod); + expandoSubclassMethods.put(name, arr); } else { - if (methodOrList instanceof MetaMethod) { - FastArray arr = new FastArray(2); - arr.add(methodOrList); - arr.add(metaMethod); - expandoSubclassMethods.put(name, arr); - } else { - ((FastArray) methodOrList).add(metaMethod); - } + ((FastArray) methodOrList).add(metaMethod); } } @@ -459,19 +449,13 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { public Object castToMixedType(Object obj, Class type) { for (MixinInMetaClass mixin : mixinClasses) { - if (type.isAssignableFrom(mixin.getMixinClass().getTheClass())) + if (type.isAssignableFrom(mixin.getMixinClass().getTheClass())) { return mixin.getMixinInstance(obj); + } } return null; } - /** - * For simulating closures in Java - */ - private interface Callable { - void call(); - } - /** * Call to enable global use of ExpandoMetaClass within the registry. * This has the advantage that inheritance will function correctly and @@ -531,22 +515,19 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { } private void addSuperMethodIfNotOverridden(final MetaMethod metaMethodFromSuper) { - performOperationOnMetaClass(new Callable() { + performOperationOnMetaClass(new Runnable() { @Override - public void call() { - + public void run() { MetaMethod existing = null; try { existing = pickMethod(metaMethodFromSuper.getName(), metaMethodFromSuper.getNativeParameterTypes()); } catch ( GroovyRuntimeException e) { // ignore, this happens with overlapping method definitions } - if (existing == null) { addMethodWithKey(metaMethodFromSuper); } else { boolean isGroovyMethod = getMetaMethods().contains(existing); - if (isGroovyMethod) { addMethodWithKey(metaMethodFromSuper); } else if (inheritedMetaMethods.contains(existing)) { @@ -558,18 +539,15 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { private void addMethodWithKey(final MetaMethod metaMethodFromSuper) { inheritedMetaMethods.add(metaMethodFromSuper); - if (metaMethodFromSuper instanceof ClosureMetaMethod) { - ClosureMetaMethod closureMethod = (ClosureMetaMethod)metaMethodFromSuper; - String name = metaMethodFromSuper.getName(); - final Class declaringClass = metaMethodFromSuper.getDeclaringClass().getTheClass(); + if (metaMethodFromSuper instanceof ClosureMetaMethod closureMethod) { ClosureMetaMethod localMethod = ClosureMetaMethod.copy(closureMethod); addMetaMethod(localMethod); - MethodKey key = new DefaultCachedMethodKey(declaringClass, name, localMethod.getParameterTypes(), false); + Class<?> declaringClass = closureMethod.getDeclaringClass().getTheClass(); + String name = closureMethod.getName(); - checkIfGroovyObjectMethod(localMethod); + var key = new DefaultCachedMethodKey(declaringClass, name, localMethod.getParameterTypes(), false); expandoMethods.put(key, localMethod); - } } }); @@ -612,43 +590,40 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { } private void registerIfClosure(Object arg, boolean replace) { - if (arg instanceof Closure) { + if (arg instanceof Closure callable) { if (propertyName.equals(CONSTRUCTOR)) { propertyName = GROOVY_CONSTRUCTOR; } - Closure callable = (Closure) arg; final List<MetaMethod> list = ClosureMetaMethod.createMethodList(propertyName, theClass, callable); if (list.isEmpty() && this.isStatic) { - Class[] paramTypes = callable.getParameterTypes(); - registerStatic(callable, replace, paramTypes); + registerStatic(callable, replace, callable.getParameterTypes()); return; } for (MetaMethod method : list) { - Class[] paramTypes = method.getNativeParameterTypes(); if (this.isStatic) { - registerStatic(callable, replace, paramTypes); + registerStatic(callable, replace, method.getNativeParameterTypes()); } else { - registerInstance(method, replace, paramTypes); + registerInstance(method, replace, method.getNativeParameterTypes()); } } } } - private void registerStatic(Closure callable, boolean replace, Class[] paramTypes) { + private void registerStatic(Closure<?> callable, boolean replace, Class<?>[] paramTypes) { Method foundMethod = checkIfMethodExists(theClass, propertyName, paramTypes, true); if (foundMethod != null && !replace) throw new GroovyRuntimeException("Cannot add new static method [" + propertyName + "] for arguments [" + DefaultGroovyMethods.inspect(paramTypes) + "]. It already exists!"); registerStaticMethod(propertyName, callable, paramTypes); } - private void registerInstance(MetaMethod method, boolean replace, Class[] paramTypes) { + private void registerInstance(MetaMethod method, boolean replace, Class<?>[] paramTypes) { Method foundMethod = checkIfMethodExists(theClass, propertyName, paramTypes, false); if (foundMethod != null && !replace) throw new GroovyRuntimeException("Cannot add new method [" + propertyName + "] for arguments [" + DefaultGroovyMethods.inspect(paramTypes) + "]. It already exists!"); registerInstanceMethod(method); } - private Method checkIfMethodExists(Class methodClass, String methodName, Class[] paramTypes, boolean staticMethod) { + private Method checkIfMethodExists(Class<?> methodClass, String methodName, Class<?>[] paramTypes, boolean staticMethod) { Method foundMethod = null; Method[] methods = methodClass.getMethods(); for (Method method : methods) { @@ -663,18 +638,17 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { } /* (non-Javadoc) - * @see groovy.lang.GroovyObjectSupport#getProperty(java.lang.String) - */ - + * @see groovy.lang.GroovyObjectSupport#getProperty(java.lang.String) + */ @Override public Object getProperty(String property) { this.propertyName = property; return this; } - /* (non-Javadoc) - * @see groovy.lang.GroovyObjectSupport#setProperty(java.lang.String, java.lang.Object) - */ + /* (non-Javadoc) + * @see groovy.lang.GroovyObjectSupport#setProperty(java.lang.String, java.lang.Object) + */ @Override public void setProperty(String property, Object newValue) { this.propertyName = property; @@ -683,17 +657,15 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { } /* (non-Javadoc) - * @see groovy.lang.MetaClassImpl#invokeConstructor(java.lang.Object[]) - */ - + * @see groovy.lang.MetaClassImpl#invokeConstructor(java.lang.Object[]) + */ @Override public Object invokeConstructor(Object[] arguments) { - // TODO This is the only area where this MetaClass needs to do some interception because Groovy's current // MetaClass uses hard coded references to the java.lang.reflect.Constructor class so you can't simply // inject Constructor like you can do properties, methods and fields. When Groovy's MetaClassImpl is // refactored we can fix this - Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); + Class<?>[] argClasses = MetaClassHelper.convertToTypeArray(arguments); MetaMethod method = pickMethod(GROOVY_CONSTRUCTOR, argClasses); if (method != null && method.getParameterTypes().length == arguments.length) { return method.invoke(theClass, arguments); @@ -709,8 +681,8 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { if (c != null) { final List<MetaMethod> list = ClosureMetaMethod.createMethodList(GROOVY_CONSTRUCTOR, theClass, c); for (MetaMethod method : list) { - Class[] paramTypes = method.getNativeParameterTypes(); - Constructor ctor = retrieveConstructor(paramTypes); + Class<?>[] paramTypes = method.getNativeParameterTypes(); + Constructor<?> ctor = retrieveConstructor(paramTypes); if (ctor != null) throw new GroovyRuntimeException("Cannot add new constructor for arguments [" + DefaultGroovyMethods.inspect(paramTypes) + "]. It already exists!"); @@ -723,18 +695,16 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { } /* (non-Javadoc) - * @see groovy.lang.GroovyObject#getMetaClass() - */ - + * @see groovy.lang.GroovyObject#getMetaClass() + */ @Override public MetaClass getMetaClass() { return myMetaClass; } /* (non-Javadoc) - * @see groovy.lang.GroovyObject#getProperty(java.lang.String) - */ - + * @see groovy.lang.GroovyObject#getProperty(java.lang.String) + */ @Override public Object getProperty(String property) { if (isValidExpandoProperty(property)) { @@ -742,25 +712,29 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { return new ExpandoMetaProperty(property, true); } else if (property.equals(CONSTRUCTOR)) { return new ExpandoMetaConstructor(); - } else { - if (myMetaClass.hasProperty(this, property) == null) - return new ExpandoMetaProperty(property); - else - return myMetaClass.getProperty(this, property); + } else if (myMetaClass.hasProperty(this, property) == null) { + return new ExpandoMetaProperty(property); } - } else { - return myMetaClass.getProperty(this, property); } + return myMetaClass.getProperty(this, property); } public static boolean isValidExpandoProperty(String property) { - return !(property.equals(META_CLASS) || property.equals(CLASS) || property.equals(META_METHODS) || property.equals(METHODS) || property.equals(PROPERTIES)); + switch (property) { + case META_CLASS_PROPERTY: + case CLASS_PROPERTY: + case META_METHODS: + case METHODS: + case PROPERTIES: + return false; + default: + return true; + } } /* (non-Javadoc) - * @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object) - */ - + * @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object) + */ @Override public Object invokeMethod(String name, Object args) { final Object[] argsArr = args instanceof Object[] ? (Object[]) args : new Object[]{args}; @@ -773,17 +747,17 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { return metaMethod.doMethodInvoke(this, argsArr); } - if (argsArr.length == 2 && argsArr[0] instanceof Class && argsArr[1] instanceof Closure) { - if (argsArr[0] == theClass) - registerInstanceMethod(name, (Closure) argsArr[1]); + if (argsArr.length == 2 && argsArr[0] instanceof Class clazz && argsArr[1] instanceof Closure closure) { + if (clazz == theClass) + registerInstanceMethod(name, closure); else { - registerSubclassInstanceMethod(name, (Class) argsArr[0], (Closure) argsArr[1]); + registerSubclassInstanceMethod(name, clazz, closure); } return null; } - if (argsArr.length == 1 && argsArr[0] instanceof Closure) { - registerInstanceMethod(name, (Closure) argsArr[0]); + if (argsArr.length == 1 && argsArr[0] instanceof Closure closure) { + registerInstanceMethod(name, closure); return null; } @@ -791,31 +765,20 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { } /* (non-Javadoc) - * @see groovy.lang.GroovyObject#setMetaClass(groovy.lang.MetaClass) - */ - + * @see groovy.lang.GroovyObject#setMetaClass(groovy.lang.MetaClass) + */ @Override public void setMetaClass(MetaClass metaClass) { this.myMetaClass = metaClass; } /* (non-Javadoc) - * @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object) - */ - + * @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object) + */ @Override public void setProperty(String property, Object newValue) { - if (newValue instanceof Closure) { - if (property.equals(CONSTRUCTOR)) { - property = GROOVY_CONSTRUCTOR; - } - Closure callable = (Closure) newValue; - final List<MetaMethod> list = ClosureMetaMethod.createMethodList(property, theClass, callable); - for (MetaMethod method : list) { - // here we don't care if the method exists or not we assume the - // developer is responsible and wants to override methods where necessary - registerInstanceMethod(method); - } + if (newValue instanceof Closure closure) { + registerInstanceMethod(property, closure); } else { registerBeanProperty(property, newValue); } @@ -834,15 +797,14 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { return this; } - protected synchronized void performOperationOnMetaClass(Callable c) { + protected synchronized void performOperationOnMetaClass(Runnable runner) { try { writeLock.lock(); if (allowChangesAfterInit) { setInitialized(false); } - c.call(); - } - finally { + runner.run(); + } finally { if (initCalled) { setInitialized(true); } @@ -864,14 +826,14 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { } /** - * Registers a new bean property + * Registers a new bean property. * - * @param property The property name - * @param newValue The properties initial value + * @param property the property name + * @param newValue the properties initial value */ public void registerBeanProperty(final String property, final Object newValue) { performOperationOnMetaClass(() -> { - Class type = newValue == null ? Object.class : newValue.getClass(); + Class<?> type = newValue == null ? Object.class : newValue.getClass(); MetaBeanProperty mbp = newValue instanceof MetaBeanProperty ? (MetaBeanProperty) newValue : new ThreadManagedMetaBeanProperty(theClass, property, type, newValue); @@ -892,48 +854,48 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { } /** - * Registers a new instance method for the given method name and closure on this MetaClass + * Registers a new instance method. * - * @param metaMethod + * @param name the method name or {@code "constructor"} for a constructor + * @param closure the implementation + */ + public void registerInstanceMethod(final String name, final Closure closure) { + String methodName = (name.equals(CONSTRUCTOR) ? GROOVY_CONSTRUCTOR : name); + List<MetaMethod> methodList = ClosureMetaMethod.createMethodList(methodName, theClass, closure); + for (MetaMethod method : methodList) { + registerInstanceMethod(method); + } + } + + /** + * Registers a new instance method. */ public void registerInstanceMethod(final MetaMethod metaMethod) { - final boolean inited = this.initCalled; + final boolean postInit = initCalled; performOperationOnMetaClass(() -> { - String methodName = metaMethod.getName(); - checkIfGroovyObjectMethod(metaMethod); - MethodKey key = new DefaultCachedMethodKey(theClass, methodName, metaMethod.getParameterTypes(), false); - if (isInitialized()) { throw new RuntimeException("Already initialized, cannot add new method: " + metaMethod); } - // we always add meta methods to class itself addMetaMethodToIndex(metaMethod, metaMethodIndex.getHeader(theClass)); - + String methodName = metaMethod.getName(); dropMethodCache(methodName); - expandoMethods.put(key, metaMethod); - if (inited && isGetter(methodName, metaMethod.getParameterTypes())) { - String propertyName = getPropertyForGetter(methodName); - registerBeanPropertyForMethod(metaMethod, propertyName, true, false); + var key = new DefaultCachedMethodKey(theClass, methodName, metaMethod.getParameterTypes(), false); + expandoMethods.put(key, metaMethod); - } else if (inited && isSetter(methodName, metaMethod.getParameterTypes())) { - String propertyName = getPropertyForSetter(methodName); - registerBeanPropertyForMethod(metaMethod, propertyName, false, false); + if (postInit) { + if (isGetter(methodName, metaMethod.getParameterTypes())) { + String propertyName = getPropertyForGetter(methodName); + registerBeanPropertyForMethod(metaMethod, propertyName, true, false); + } else if (isSetter(methodName, metaMethod.getParameterTypes())) { + String propertyName = getPropertyForSetter(methodName); + registerBeanPropertyForMethod(metaMethod, propertyName, false, false); + } } performRegistryCallbacks(); }); } - public void registerInstanceMethod(String name, Closure closure) { - if (name.equals(CONSTRUCTOR)) { - name = GROOVY_CONSTRUCTOR; - } - final List<MetaMethod> list = ClosureMetaMethod.createMethodList(name, theClass, closure); - for (MetaMethod method : list) { - registerInstanceMethod(method); - } - } - /** * Overrides the behavior of parent getMethods() method to make MetaClass aware of added Expando methods * @@ -942,21 +904,19 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { */ @Override public List<MetaMethod> getMethods() { - List<MetaMethod> methodList = new ArrayList<MetaMethod>(); - methodList.addAll(this.expandoMethods.values()); + List<MetaMethod> methodList = new ArrayList<>(); + methodList.addAll(expandoMethods.values()); methodList.addAll(super.getMethods()); return methodList; } @Override public List<MetaProperty> getProperties() { - List<MetaProperty> propertyList = new ArrayList<MetaProperty>(super.getProperties()); + List<MetaProperty> propertyList = new ArrayList<>(super.getProperties()); return propertyList; } - private void performRegistryCallbacks() { - MetaClassRegistry registry = GroovySystem.getMetaClassRegistry(); incVersion(); if (!modified) { modified = true; @@ -965,21 +925,21 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { // saying this EMC will be a hard reference in the registry. As we're only // going have a small number of classes that have modified EMC this is ok if (inRegistry) { - MetaClass currMetaClass = registry.getMetaClass(theClass); - if (!(currMetaClass instanceof ExpandoMetaClass) && currMetaClass instanceof AdaptingMetaClass) { - ((AdaptingMetaClass) currMetaClass).setAdaptee(this); + MetaClass metaClass = registry.getMetaClass(theClass); + if (metaClass instanceof AdaptingMetaClass adaptingMC + && !(metaClass instanceof ExpandoMetaClass)) { + adaptingMC.setAdaptee(this); } else { registry.setMetaClass(theClass, this); } } - } } private void registerBeanPropertyForMethod(MetaMethod metaMethod, String propertyName, boolean getter, boolean isStatic) { Map<String, MetaProperty> propertyCache = isStatic ? staticBeanPropertyCache : beanPropertyCache; MetaBeanProperty beanProperty = (MetaBeanProperty) propertyCache.get(propertyName); - if (beanProperty==null) { + if (beanProperty == null) { MetaProperty metaProperty = super.getMetaProperty(propertyName); if (metaProperty instanceof MetaBeanProperty && isStatic == metaProperty.isStatic()) { beanProperty = (MetaBeanProperty) metaProperty; @@ -995,7 +955,7 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { } else { if (getter) { MetaMethod setterMethod = beanProperty.getSetter(); - Class type = setterMethod != null ? setterMethod.getParameterTypes()[0].getTheClass() : Object.class; + Class<?> type = setterMethod != null ? setterMethod.getParameterTypes()[0].getTheClass() : Object.class; beanProperty = new MetaBeanProperty(propertyName, type, metaMethod, setterMethod); propertyCache.put(propertyName, beanProperty); } else { @@ -1098,7 +1058,7 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { if (metaMethod.isStatic()) { if (superExpando.getTheClass() != getTheClass()) continue; // don't inherit static methods except our own - registerStaticMethod(metaMethod.getName(), (Closure) ((ClosureStaticMetaMethod) metaMethod).getClosure().clone()); + registerStaticMethod(metaMethod.getName(), (Closure<?>) ((ClosureStaticMetaMethod) metaMethod).getClosure().clone()); } else addSuperMethodIfNotOverridden(metaMethod); } @@ -1120,6 +1080,9 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { return Collections.unmodifiableList(DefaultGroovyMethods.toList(expandoMethods.values())); } + public Collection getExpandoSubclassMethods() { + return Collections.unmodifiableCollection(expandoSubclassMethods.values()); + } /** * Returns a list of MetaBeanProperty instances added to this ExpandoMetaClass @@ -1354,7 +1317,7 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { @Override public MetaMethod retrieveConstructor(Object[] args) { - Class[] params = MetaClassHelper.convertToTypeArray(args); + Class<?>[] params = MetaClassHelper.convertToTypeArray(args); MetaMethod method = pickMethod(GROOVY_CONSTRUCTOR, params); if (method!=null) return method; return super.retrieveConstructor(args); @@ -1362,7 +1325,7 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { @Override public CallSite createConstructorSite(CallSite site, Object[] args) { - Class[] params = MetaClassHelper.convertToTypeArray(args); + Class<?>[] params = MetaClassHelper.convertToTypeArray(args); MetaMethod method = pickMethod(GROOVY_CONSTRUCTOR, params); if (method != null && method.getParameterTypes().length == args.length) { if (method.getDeclaringClass().getTheClass().equals(getTheClass())) { @@ -1374,9 +1337,9 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { } private class SubClassDefiningClosure extends GroovyObjectSupport { - private final Class klazz; + private final Class<?> klazz; - private SubClassDefiningClosure(Class klazz) { + private SubClassDefiningClosure(Class<?> klazz) { this.klazz = klazz; } @@ -1384,8 +1347,8 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { public Object invokeMethod(String name, Object obj) { if (obj instanceof Object[]) { Object[] args = (Object[]) obj; - if (args.length == 1 && args[0] instanceof Closure) { - registerSubclassInstanceMethod(name, klazz, (Closure) args[0]); + if (args.length == 1 && args[0] instanceof Closure closure) { + registerSubclassInstanceMethod(name, klazz, closure); return null; } } @@ -1425,7 +1388,7 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { if (obj instanceof Object[]) { if (STATIC_QUALIFIER.equals(name)) { final StaticDefiningClosure staticDef = new StaticDefiningClosure(); - Closure c = (Closure) ((Object[]) obj)[0]; + var c = (Closure<?>) ((Object[]) obj)[0]; c.setDelegate(staticDef); c.setResolveStrategy(Closure.DELEGATE_ONLY); c.call((Object)null); @@ -1433,9 +1396,9 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { } Object[] args = (Object[]) obj; if (args.length == 1 && args[0] instanceof Closure) { - registerInstanceMethod(name, (Closure) args[0]); - } else if (args.length == 2 && args[0] instanceof Class && args[1] instanceof Closure) - registerSubclassInstanceMethod(name, (Class) args[0], (Closure) args[1]); + registerInstanceMethod(name, (Closure<?>) args[0]); + } else if (args.length == 2 && args[0] instanceof Class clazz && args[1] instanceof Closure closure) + registerSubclassInstanceMethod(name, clazz, closure); else ExpandoMetaClass.this.setProperty(name, ((Object[]) obj)[0]); @@ -1472,8 +1435,8 @@ public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject { public Object invokeMethod(String name, Object obj) { if (obj instanceof Object[]) { final Object[] args = (Object[]) obj; - if (args.length == 1 && args[0] instanceof Closure) { - registerStaticMethod(name, (Closure) args[0]); + if (args.length == 1 && args[0] instanceof Closure closure) { + registerStaticMethod(name, closure); return null; } } diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java index c6d3bd94e0..9946b7be6b 100644 --- a/src/main/java/groovy/lang/MetaClassImpl.java +++ b/src/main/java/groovy/lang/MetaClassImpl.java @@ -431,9 +431,9 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass { } private MetaMethod[] getNewMetaMethods(final CachedClass c) { - if (theCachedClass != c) + if (c != theCachedClass) { return c.getNewMetaMethods(); - + } return myNewMetaMethods; } @@ -597,28 +597,27 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass { private void inheritInterfaceNewMetaMethods(final Set<CachedClass> interfaces) { Method[] theClassMethods = null; - // add methods declared by DGM for interfaces + // add methods declared by extension 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 && !GroovyObject.class.isAssignableFrom(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; + mm: for (MetaMethod mm : getNewMetaMethods(face)) { + if (mm instanceof GeneratedMetaMethod) { + // skip DGM methods on an interface if the class already has the method + // but do not skip GroovyObject-related methods as it breaks things :-( + if (!GroovyObject.class.isAssignableFrom(mm.getDeclaringClass().getTheClass())) { + String generatedMethodName = mm.getName(); + CachedClass[] generatedMethodParameterTypes = mm.getParameterTypes(); + for (Method m : (theClassMethods == null ? 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())) { + continue mm; + } } } } - if (!skip) { - newGroovyMethodsSet.add(method); - addMetaMethodToIndex(method, mainClassMethodHeader); + if (newGroovyMethodsSet.add(mm)) { + addMetaMethodToIndex(mm, mainClassMethodHeader); } } } diff --git a/src/main/java/org/codehaus/groovy/reflection/CachedClass.java b/src/main/java/org/codehaus/groovy/reflection/CachedClass.java index 5a89b1a0fc..2dbe6cf8a3 100644 --- a/src/main/java/org/codehaus/groovy/reflection/CachedClass.java +++ b/src/main/java/org/codehaus/groovy/reflection/CachedClass.java @@ -360,30 +360,31 @@ public class CachedClass { } public MetaMethod[] getNewMetaMethods() { - List<MetaMethod> arr = new ArrayList<>(Arrays.asList(classInfo.newMetaMethods)); + List<MetaMethod> metaMethods = new ArrayList<>(); - final MetaClass metaClass = classInfo.getStrongMetaClass(); - if (metaClass instanceof ExpandoMetaClass) { - arr.addAll(((ExpandoMetaClass)metaClass).getExpandoMethods()); + Collections.addAll(metaMethods, classInfo.newMetaMethods); + + if (classInfo.getStrongMetaClass() instanceof ExpandoMetaClass emc) { + List<MetaMethod> expandoMethods = emc.getExpandoMethods(); + metaMethods.addAll(expandoMethods); } if (isInterface) { MetaClass mc = ReflectionCache.OBJECT_CLASS.classInfo.getStrongMetaClass(); - addSubclassExpandos(arr, mc); - } - else { - for (CachedClass cls = this; cls != null; cls = cls.getCachedSuperClass()) { - MetaClass mc = cls.classInfo.getStrongMetaClass(); - addSubclassExpandos(arr, mc); + addSubclassExpandos(metaMethods, mc); + } else { + for (CachedClass cc = this; cc != null; cc = cc.getCachedSuperClass()) { + MetaClass mc = cc.classInfo.getStrongMetaClass(); + addSubclassExpandos(metaMethods, mc); } } - for (CachedClass inf : getInterfaces()) { - MetaClass mc = inf.classInfo.getStrongMetaClass(); - addSubclassExpandos(arr, mc); + for (CachedClass cc : getInterfaces()) { // includes this if interface + MetaClass mc = cc.classInfo.getStrongMetaClass(); + addSubclassExpandos(metaMethods, mc); } - return arr.toArray(MetaMethod.EMPTY_ARRAY); + return metaMethods.toArray(MetaMethod[]::new); } private void addSubclassExpandos(List<MetaMethod> arr, MetaClass mc) { diff --git a/src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaMethod.java b/src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaMethod.java index 3af6d3242d..efe6915ea0 100644 --- a/src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaMethod.java +++ b/src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaMethod.java @@ -155,7 +155,7 @@ public class ClosureMetaMethod extends MetaMethod implements ClosureInvokingMeth } } - static class AnonymousMetaMethod extends MetaMethod { + static class AnonymousMetaMethod extends MetaMethod implements ClosureInvokingMethod { private final Closure closure; private final String name; private final Class declaringClass; @@ -167,6 +167,11 @@ public class ClosureMetaMethod extends MetaMethod implements ClosureInvokingMeth this.declaringClass = declaringClass; } + @Override + public Closure getClosure() { + return closure; + } + @Override public int getModifiers() { return Modifier.PUBLIC; diff --git a/src/main/java/org/codehaus/groovy/runtime/metaclass/MetaClassRegistryImpl.java b/src/main/java/org/codehaus/groovy/runtime/metaclass/MetaClassRegistryImpl.java index a875070556..2c60bb4762 100644 --- a/src/main/java/org/codehaus/groovy/runtime/metaclass/MetaClassRegistryImpl.java +++ b/src/main/java/org/codehaus/groovy/runtime/metaclass/MetaClassRegistryImpl.java @@ -160,7 +160,6 @@ public class MetaClassRegistryImpl implements MetaClassRegistry { } catch (Throwable e) { //DO NOTHING } - } }); } @@ -290,7 +289,7 @@ public class MetaClassRegistryImpl implements MetaClassRegistry { } finally { info.unlock(); } - if ((oldMC == null && mc != newMC) || (oldMC != null && mc != newMC && mc != oldMC)) { + if ((oldMC == null || oldMC != mc) && (newMC != mc)) { fireConstantMetaClassUpdate(null, theClass, mc, newMC); } } diff --git a/src/main/java/org/codehaus/groovy/runtime/metaclass/MetaMethodIndex.java b/src/main/java/org/codehaus/groovy/runtime/metaclass/MetaMethodIndex.java index ae20307f30..99de9e2fb3 100644 --- a/src/main/java/org/codehaus/groovy/runtime/metaclass/MetaMethodIndex.java +++ b/src/main/java/org/codehaus/groovy/runtime/metaclass/MetaMethodIndex.java @@ -18,6 +18,7 @@ */ package org.codehaus.groovy.runtime.metaclass; +import groovy.lang.ClosureInvokingMethod; import groovy.lang.MetaMethod; import org.codehaus.groovy.ast.tools.GeneralUtils; import org.codehaus.groovy.reflection.CachedClass; @@ -233,11 +234,9 @@ public class MetaMethodIndex { private static boolean isNonRealMethod(final MetaMethod method) { return method instanceof NewMetaMethod - || method instanceof ClosureMetaMethod || method instanceof GeneratedMetaMethod - || method instanceof ClosureStaticMetaMethod - || method instanceof MixinInstanceMetaMethod - || method instanceof ClosureMetaMethod.AnonymousMetaMethod; + || method instanceof ClosureInvokingMethod + || method instanceof MixinInstanceMetaMethod; } private static boolean isMatchingMethod(final MetaMethod method1, final MetaMethod method2) { diff --git a/src/test/groovy/bugs/Groovy3873Bug.groovy b/src/test/groovy/bugs/Groovy3873Bug.groovy deleted file mode 100644 index d67c06a14a..0000000000 --- a/src/test/groovy/bugs/Groovy3873Bug.groovy +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 bugs - -import groovy.test.GroovyTestCase - -class Groovy3873Bug extends GroovyTestCase { - void testAddingMethodsToMetaClassOfInterface() { - try { - ExpandoMetaClass.enableGlobally() - List.metaClass.methodMissing = { String name, args -> - true - } - def list = [] - assert list.noSuchMethod() - } finally { - List.metaClass = null - ExpandoMetaClass.disableGlobally() - } - } -} diff --git a/src/test/groovy/groovy/lang/ExpandoMetaClassCreationHandleTest.groovy b/src/test/groovy/groovy/lang/ExpandoMetaClassCreationHandleTest.groovy index 159bd4d0d0..1f6085cdf9 100644 --- a/src/test/groovy/groovy/lang/ExpandoMetaClassCreationHandleTest.groovy +++ b/src/test/groovy/groovy/lang/ExpandoMetaClassCreationHandleTest.groovy @@ -18,82 +18,126 @@ */ package groovy.lang -import groovy.test.GroovyTestCase; +import org.codehaus.groovy.reflection.ClassInfo +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test -class ExpandoMetaClassCreationHandleTest extends GroovyTestCase { - def registry = GroovySystem.metaClassRegistry - MetaClass savedStringMeta - MetaClass savedObjectMeta +final class ExpandoMetaClassCreationHandleTest { - void setUp() { - savedStringMeta = registry.getMetaClass(String) - savedObjectMeta = registry.getMetaClass(Object) + @BeforeAll + static void setUp() { ExpandoMetaClass.enableGlobally() } - void tearDown() { + @AfterAll + static void tearDown() { ExpandoMetaClass.disableGlobally() - registry.setMetaClass(String, savedStringMeta) - registry.setMetaClass(Object, savedObjectMeta) + + ClassInfo.onAllClassInfo { info -> + if (info.getMetaClassForClass()) { + info.lock() + try { + info.setStrongMetaClass(null) + } finally { + info.unlock() + } + } + } + } + + private MetaClass expand(Class type) { + MetaClassRegistry registry = GroovySystem.getMetaClassRegistry() + MetaClass metaClass = registry.getMetaClass(type) + assert metaClass instanceof ExpandoMetaClass + return metaClass + } + + private void reset(Class<?>... types) { + types.each(GroovySystem.getMetaClassRegistry().&removeMetaClass) } - void testInheritWithExistingMetaClass() { - registry.removeMetaClass(String) - registry.removeMetaClass(Object) + //-------------------------------------------------------------------------- - String foo = "hello" - assertEquals "HELLO", foo.toUpperCase() + @Test + void testExpandoCreationHandle() { + reset(URL) - Object.metaClass.doStuff = { -> delegate.toString().toUpperCase() } + expand(URL).toStringUC = { -> delegate.toString().toUpperCase() } - assertEquals "HELLO", foo.doStuff() + def url = new URL('http://grails.org') + assert url.toString() == 'http://grails.org' + assert url.toStringUC() == 'HTTP://GRAILS.ORG' } - void testInheritFromInterfaceHierarchy() { - registry.removeMetaClass(IBar) - registry.removeMetaClass(Foo) - registry.removeMetaClass(Test1) + @Test + void testInheritFromSuperClass() { + reset(Object, String) + + String string = 'hello' + assert string.toUpperCase() == 'HELLO' + + expand(Object).doStuff = { -> delegate.toString().toUpperCase() } + + assert string.doStuff() == 'HELLO' + } + + @Test + void testInheritFromSuperClass2() { + reset(Object, String, URI) + + expand(Object).toFoo = { -> 'foo' } + + def uri = new URI('http://bar.com') + def s = 'bar' - def metaClass = registry.getMetaClass(IBar) - assertTrue(metaClass instanceof ExpandoMetaClass) + assert uri.toFoo() == 'foo' + assert s.toFoo() == 'foo' - metaClass.helloWorld = { -> "goodbye!" } + expand(Object).toBar = { -> 'bar' } - def t = new Test1() - assertEquals "goodbye!", t.helloWorld() + assert uri.toBar() == 'bar' + assert s.toBar() == 'bar' } - void testExpandoInterfaceInheritanceWithOverrideDGM() { - registry.removeMetaClass(Foo) - registry.removeMetaClass(Test1) + @Test + void testInheritFromSuperInterface() { + reset(Bar, Foo, Tester) - def metaClass = registry.getMetaClass(Foo) - assertTrue(metaClass instanceof ExpandoMetaClass) + expand(Bar).helloWorld = { -> 'goodbye!' } + + assert new Tester().helloWorld() == 'goodbye!' + } + + @Test + void testOverrideGetAndPutAtViaInterface() { + reset(Bar, Foo, Tester) + + assert metaClass instanceof ExpandoMetaClass def map = [:] - metaClass.getAt = { Integer i -> map[i] } - metaClass.putAt = { Integer i, val -> map[i] = val } + MetaClass metaClass = expand(Foo) + metaClass.getAt = { Integer idx -> map[idx] } + metaClass.putAt = { Integer idx, val -> map[idx] = val } - def t = new Test1() - //assertEquals 2, t.metaClass.getExpandoMethods().size() - //assert t.metaClass.getExpandoMethods().find { it.name == 'putAt' } + def t = new Tester() + //assert t.getMetaClass().getExpandoMethods().size() == 2 + //assert t.getMetaClass().getExpandoMethods().find { it.name == 'putAt' } - t[0] = "foo" + t[0] = 'foo' assert map.size() == 1 - assertEquals "foo", t[0] + assert t[0] == 'foo' } + @Test void testOverrideSetPropertyViaInterface() { - registry.removeMetaClass(Foo) - registry.removeMetaClass(Test1) - - def metaClass = registry.getMetaClass(Foo) + reset(Bar, Foo, Tester) def testValue = null - metaClass.setProperty = { String name, value -> - def mp = delegate.metaClass.getMetaProperty(name) + expand(Foo).setProperty = { String name, value -> + def mp = delegate.getMetaClass().getMetaProperty(name) if (mp) { mp.setProperty(delegate, value) } else { @@ -101,165 +145,126 @@ class ExpandoMetaClassCreationHandleTest extends GroovyTestCase { } } - def t = new Test1() + def t = new Tester() - t.name = "Bob" - assertEquals "Bob", t.name + t.name = 'Bob' + assert t.name == 'Bob' - t.foo = "bar" - assertEquals "bar", testValue + t.xxxx = 'foo bar' + assert testValue == 'foo bar' } + @Test void testOverrideGetPropertyViaInterface() { - registry.removeMetaClass(Foo) - registry.removeMetaClass(Test1) - - def metaClass = registry.getMetaClass(Foo) + reset(Bar, Foo, Tester) - metaClass.getProperty = { String name -> - def mp = delegate.metaClass.getMetaProperty(name) - mp ? mp.getProperty(delegate) : "foo $name" + expand(Foo).getProperty = { String name -> + def mp = delegate.getMetaClass().getMetaProperty(name) + mp ? mp.getProperty(delegate) : "fizz $name" } - def t = new Test1() + def t = new Tester() - assertEquals "Fred", t.getProperty("name") - assertEquals "Fred", t.name - assertEquals "foo bar", t.getProperty("bar") - assertEquals "foo bar", t.bar + assert t.getProperty('name') == 'Fred' + assert t.name == 'Fred' + assert t.getProperty('buzz') == 'fizz buzz' + assert t.buzz == 'fizz buzz' } + @Test void testOverrideInvokeMethodViaInterface() { - registry.removeMetaClass(Foo) - registry.removeMetaClass(Object) - registry.removeMetaClass(IBar) - registry.removeMetaClass(Test1) + reset(Bar, Foo, Tester) - def metaClass = registry.getMetaClass(Foo) - - metaClass.invokeMethod = { String name, args -> + expand(Foo).invokeMethod = { String name, args -> def mm = delegate.metaClass.getMetaMethod(name, args) - mm ? mm.invoke(delegate, args) : "bar!!" + mm ? mm.invoke(delegate, args) : 'bar!' } - def t = new Test1() + def t = new Tester() - assertEquals "bar!!", t.doStuff() - assertEquals "foo", t.invokeMe() + assert t.invokeMe() == 'foo' + assert t.whatever() == 'bar!' } - void testInterfaceMethodInheritance() { - registry.removeMetaClass(List) - registry.removeMetaClass(ArrayList) - - def metaClass = registry.getMetaClass(List) - assertTrue(metaClass instanceof ExpandoMetaClass) - - metaClass.sizeDoubled = { -> delegate.size() * 2 } - metaClass.isFull = { -> false } - - def list = new ArrayList() - - list << 1 - list << 2 - - assertEquals 4, list.sizeDoubled() - - list = new ArrayList() - - assert !list.isFull() - assert !list.full - } + // GROOVY-3873 + @Test + void testOverrideMethodMissingViaInterface() { + reset(List, ArrayList) - void testExpandoCreationHandle() { - def metaClass = registry.getMetaClass(URL) - if (!(metaClass instanceof ExpandoMetaClass)) { - registry.removeMetaClass(URL) + expand(List).methodMissing = { String name, args -> + true } - def url = new URL("http://grails.org") - metaClass = registry.getMetaClass(url.getClass()) - assertTrue(metaClass instanceof ExpandoMetaClass) - - metaClass.toUpperString = { -> - delegate.toString().toUpperCase() - } + def list = new ArrayList() - assertEquals "http://grails.org", url.toString() - assertEquals "HTTP://GRAILS.ORG", url.toUpperString() + assert list.noSuchMethod() } - void testExpandoInheritance() { - registry.removeMetaClass(String) + @Test + void testInterfaceMethodInheritance() { + reset(List, ArrayList) - def metaClass = registry.getMetaClass(Object) - if (!(metaClass instanceof ExpandoMetaClass)) { - registry.removeMetaClass(Object) - metaClass = registry.getMetaClass(Object) + expand(List).with { + sizeDoubled = { -> delegate.size() * 2 } + isFull = { -> false } } - metaClass.toFoo = { -> "foo" } - - def uri = new URI("http://bar.com") - def s = "bar" - - assertEquals "foo", uri.toFoo() - assertEquals "foo", s.toFoo() - - metaClass.toBar = { -> "bar" } + def list = new ArrayList() + list << 1 + list << 2 - assertEquals "bar", uri.toBar() - assertEquals "bar", s.toBar() + assert list.sizeDoubled() == 4 + assert !list.isFull() + //assert !list.full -- tries [1.full,2.full] } + @Test void testAddMethodToChildThenParent() { - registry.removeMetaClass(Test1) - registry.removeMetaClass(EMCInheritTest) + reset(Bar, Foo, Tester, EMCInheritTest) - EMCInheritTest.metaClass.foo = { -> "hello!" } + expand(EMCInheritTest).foo = { -> 'hello!' } def emc = new EMCInheritTest() - assertEquals "hello!", emc.foo() + assert emc.foo() == 'hello!' + + expand(Tester).foo = { -> 'goodbye!' } - Test1.metaClass.foo = { -> "uck" } emc = new EMCInheritTest() - // make sure original foo wasn't overridden - assertEquals "hello!", emc.foo() + + assert emc.foo() == 'hello!' : 'original foo was replaced' } + @Test void testAddMethodMissingToChildThenParent() { - registry.removeMetaClass(Test1) - registry.removeMetaClass(EMCInheritTest) - registry.removeMetaClass(Foo) - registry.removeMetaClass(IBar) - registry.removeMetaClass(Object) + reset(Bar, Foo, Tester, EMCInheritTest) - EMCInheritTest.metaClass.methodMissing = { String name, args -> "hello!" } + expand(EMCInheritTest).methodMissing = { String name, args -> 'hello!' } def emc = new EMCInheritTest() - assertEquals "hello!", emc.foo() + assert emc.foo() == 'hello!' - Test1.metaClass.methodMissing = { String name, args -> "uck" } - emc = new EMCInheritTest() - // make sure original foo wasn't overridden - assertEquals "hello!", emc.bar() - } -} + expand(Tester).methodMissing = { String name, args -> 'goodbye!' } -interface IBar {} + emc = new EMCInheritTest() -interface Foo extends IBar { + assert emc.bar() == 'hello!' : 'original methodMissing was replaced' + } -} + //-------------------------------------------------------------------------- -class Test1 implements Foo { - String name = "Fred" + interface Bar { + } - def invokeMe() { "foo" } -} + interface Foo extends Bar { + } -class EMCInheritTest extends Test1 { + static class Tester implements Foo { + String name = 'Fred' + def invokeMe() { 'foo' } + } + static class EMCInheritTest extends Tester { + } }
