Java – why can’t I Invokeexact() is here, even if the methodtype is OK?
For one of my projects, I have to make dynamic calls to constructors But since this is java 7 and uses the "classic" reflection API, I use Java lang.invoke.
Code:
@ParametersAreNonnullByDefault public class PathMatcherProvider { private static final MethodHandles.Lookup LOOKUP = MethodHandles.publicLookup(); private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class,String.class); private final Map<String,Class<? extends PathMatcher>> classMap = new HashMap<>(); private final Map<Class<? extends PathMatcher>,MethodHandle> handleMap = new HashMap<>(); public PathMatcherProvider() { registerPathMatcher("glob",GlobPathMatcher.class); registerPathMatcher("regex",RegexPathMatcher.class); } public final PathMatcher getPathMatcher(final String name,final String arg) { Objects.requireNonNull(name); Objects.requireNonNull(arg); final Class<? extends PathMatcher> c = classMap.get(name); if (c == null) throw new UnsupportedOperationException(); try { return c.cast(handleMap.get(c).invoke(arg)); } catch (Throwable throwable) { throw new RuntimeException("Unhandled exception",throwable); } } protected final void registerPathMatcher(@Nonnull final String name,@Nonnull final Class<? extends PathMatcher> matcherClass) { Objects.requireNonNull(name); Objects.requireNonNull(matcherClass); try { classMap.put(name,matcherClass); handleMap.put(matcherClass,findConstructor(matcherClass)); } catch (NoSuchMethodException | illegalaccessexception e) { throw new RuntimeException("cannot find constructor",e); } } private static <T extends PathMatcher> MethodHandle findConstructor( final Class<T> matcherClass) throws NoSuchMethodException,illegalaccessexception { Objects.requireNonNull(matcherClass); return LOOKUP.findConstructor(matcherClass,CONSTRUCTOR_TYPE); } public static void main(final String... args) { new PathMatcherProvider().getPathMatcher("regex","^a"); } }
OK, this job
My problem is this line:
return c.cast(handleMap.get(c).invoke(arg));
If I replace invoke with invokeexact, I get this stack trace:
Exception in thread "main" java.lang.RuntimeException: Unhandled exception at com.github.fge.filesystem.path.matchers.PathMatcherProvider.getPathMatcher(PathMatcherProvider.java:62) at com.github.fge.filesystem.path.matchers.PathMatcherProvider.main(PathMatcherProvider.java:89) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134) Caused by: java.lang.invoke.WrongMethodTypeException: expected (String)RegexPathMatcher but found (String)Object at java.lang.invoke.Invokers.newWrongMethodTypeException(Invokers.java:350) at java.lang.invoke.Invokers.checkExactType(Invokers.java:361) at com.github.fge.filesystem.path.matchers.PathMatcherProvider.getPathMatcher(PathMatcherProvider.java:60)
I don't quite understand that both globpathmatcher and regexpathmatcher use a constructor with a string as a parameter. Therefore, the methodtypes of both are in the constructor_ Defined in type If not, I can't "catch" methodhandles
But I got a wrong methodtypeexception Why?
Editor: This is the code after I read the answer. Now I don't need an intermediate map: I just need a map to map a string to a methodhandle:
@ParametersAreNonnullByDefault public class PathMatcherProvider { private static final MethodHandles.Lookup LOOKUP = MethodHandles.publicLookup(); private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class,final String arg) { Objects.requireNonNull(name); Objects.requireNonNull(arg); final MethodHandle handle = handleMap.get(name); if (handle == null) throw new UnsupportedOperationException(); try { return (PathMatcher) handle.invokeExact(arg); } catch (Throwable throwable) { throw new RuntimeException("Unhandled exception",@Nonnull final Class<? extends PathMatcher> matcherClass) { Objects.requireNonNull(name); Objects.requireNonNull(matcherClass); final MethodHandle handle; final MethodType type; try { handle = LOOKUP.findConstructor(matcherClass,CONSTRUCTOR_TYPE); type = handle.type().changeReturnType(PathMatcher.class); handleMap.put(name,handle.asType(type)); } catch (NoSuchMethodException | illegalaccessexception e) { throw new RuntimeException("cannot find constructor",e); } } }
Solution
When the compiler makes an invokeexact call, it records the object as the expected return type From methodhandle Javadoc (emphasize my):
At runtime, the method handle actually returned regexpathmatcher, so invokeexact failed with the wrong methodtypeexception
You need to explicitly specify the return type using (compile time) conversion:
return (RegexPathMatcher)handleMap.get(c).invokeExact(arg);
In addition to being implemented through different pathmatchers, you need to convert the method handle to return the pathmatcher using astype, and then use the pathmatcher as the expected return type
//in findConstructor MethodHandle h = LOOKUP.findConstructor(matcherClass,CONSTRUCTOR_TYPE); return h.asType(h.type().changeReturnType(PathMatcher.class)); //in getPathMatcher return (PathMatcher)handleMap.get(c).invokeExact(arg);