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 {
+    }
 }

Reply via email to