POLYGENE-120 - handle default interface methods. Article https://opencredo.com/dynamic-proxies-java-part-2/ contained the secret sauce of reflection construction of MethodHandles.Lookup instance.
Signed-off-by: niclas <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/polygene-java/repo Commit: http://git-wip-us.apache.org/repos/asf/polygene-java/commit/ee79f220 Tree: http://git-wip-us.apache.org/repos/asf/polygene-java/tree/ee79f220 Diff: http://git-wip-us.apache.org/repos/asf/polygene-java/diff/ee79f220 Branch: refs/heads/develop Commit: ee79f220a960f5cd484aa3163718da41ebeb55d9 Parents: 820f9d0 Author: niclas <[email protected]> Authored: Sat May 13 15:41:13 2017 +0800 Committer: niclas <[email protected]> Committed: Sun May 14 12:07:56 2017 +0800 ---------------------------------------------------------------------- .../composite/CompositeMethodsModel.java | 92 ++++++++++++++++---- 1 file changed, 77 insertions(+), 15 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/polygene-java/blob/ee79f220/core/runtime/src/main/java/org/apache/polygene/runtime/composite/CompositeMethodsModel.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/polygene/runtime/composite/CompositeMethodsModel.java b/core/runtime/src/main/java/org/apache/polygene/runtime/composite/CompositeMethodsModel.java index 8dc2495..3b130ed 100644 --- a/core/runtime/src/main/java/org/apache/polygene/runtime/composite/CompositeMethodsModel.java +++ b/core/runtime/src/main/java/org/apache/polygene/runtime/composite/CompositeMethodsModel.java @@ -20,10 +20,18 @@ package org.apache.polygene.runtime.composite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.LinkedHashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.polygene.api.composite.Composite; import org.apache.polygene.api.composite.MissingMethodException; import org.apache.polygene.api.structure.ModuleDescriptor; import org.apache.polygene.api.util.HierarchicalVisitor; @@ -37,6 +45,7 @@ import org.apache.polygene.runtime.injection.DependencyModel; public final class CompositeMethodsModel implements VisitableHierarchy<Object, Object>, Dependencies { + private final ConcurrentMap<Method, MethodCallHandler> methodHandleCache = new ConcurrentHashMap<>(); private final LinkedHashMap<Method, CompositeMethodModel> methods; private final MixinsModel mixinsModel; @@ -58,33 +67,57 @@ public final class CompositeMethodsModel Method method, Object[] args, ModuleDescriptor moduleInstance - ) + ) throws Throwable { CompositeMethodModel compositeMethod = methods.get( method ); if( compositeMethod == null ) { - if( method.getDeclaringClass().equals( Object.class ) ) + Class<?> declaringClass = method.getDeclaringClass(); + if( declaringClass.equals( Object.class ) ) { return mixins.invokeObject( proxy, args, method ); } // TODO: Figure out what was the intention of this code block, added by Rickard in 2009. It doesn't do anything useful. - if( !method.getDeclaringClass().isInterface() ) + // Update (niclas): My guess is that this is preparation for mixins in Objects. + if( !declaringClass.isInterface() ) { - compositeMethod = mixinsModel.mixinTypes().map( aClass -> { - try - { - Method realMethod = aClass.getMethod( method.getName(), method.getParameterTypes() ); - return methods.get( realMethod ); - } - catch( NoSuchMethodException | SecurityException e ) - { - - } - return null; - }).filter( model -> model != null ).findFirst().orElse( null ); + compositeMethod = mixinsModel.mixinTypes().map( aClass -> + { + try + { + Method realMethod = aClass.getMethod( method.getName(), method.getParameterTypes() ); + return methods.get( realMethod ); + } + catch( NoSuchMethodException | SecurityException e ) + { + + } + return null; + } ).filter( model -> model != null ).findFirst().orElse( null ); + } + if( method.isDefault() ) + { + if( proxy instanceof Composite ) + { + MethodCallHandler callHandler = forMethod( method ); + return callHandler.invoke( proxy, args ); + } + // Does this next line actually make any sense? Can we have a default method on an interface where the instance is not a Composite? Maybe... Let's try to trap a usecase by disallowing it. +// return method.invoke( proxy, args ); + String message = "We have detected a default method on an interface that is not backed by a Composite. " + + "Please report this to [email protected] together with the information below, " + + "that/those class(es) and the relevant assembly information. Thank you\nMethod:" + + method.toGenericString() + + "\nDeclaring Class:" + + method.getDeclaringClass().toGenericString() + + "\nTypes:" + + mixinsModel.mixinTypes() + .map( Class::toGenericString ) + .collect( Collectors.joining( "\n" ) ); + throw new UnsupportedOperationException( message ); } throw new MissingMethodException( "Method '" + method + "' is not implemented" ); } @@ -131,4 +164,33 @@ public final class CompositeMethodsModel { return methods().toString(); } + + private MethodCallHandler forMethod( Method method ) + { + return methodHandleCache.computeIfAbsent( method, this::createMethodCallHandler ); + } + + private MethodCallHandler createMethodCallHandler( Method method ) + { + Class<?> declaringClass = method.getDeclaringClass(); + try + { + Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor( Class.class, int.class ); + constructor.setAccessible( true ); + MethodHandles.Lookup lookup = constructor.newInstance( declaringClass, MethodHandles.Lookup.PRIVATE ); + MethodHandle handle = lookup.unreflectSpecial( method, declaringClass ); + return ( proxy, args ) -> handle.bindTo( proxy ).invokeWithArguments( args ); + } + catch( IllegalAccessException | NoSuchMethodException | InstantiationException | InvocationTargetException e ) + { + throw new RuntimeException( e ); + } + } + + @FunctionalInterface + private interface MethodCallHandler + { + Object invoke( Object proxy, Object[] args ) + throws Throwable; + } }
