From 669594fc9c2451c478c2b9f9c9a6a4b3adf0eb94 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sat, 22 Oct 2016 22:19:20 +0300 Subject: [PATCH] JS: add reflection support for methods --- .../impl/ReflectionDependencyListener.java | 43 ++++- .../impl/reflection/JSMethodMember.java | 3 + .../classlib/java/lang/ClassGenerator.java | 40 +++++ .../org/teavm/classlib/java/lang/TClass.java | 166 ++++++++++++++++- .../classlib/java/lang/reflect/TMethod.java | 141 +++++++++++++++ .../java/lang/reflect/ConstructorTest.java | 16 +- .../java/lang/reflect/MethodTest.java | 169 ++++++++++++++++++ 7 files changed, 566 insertions(+), 12 deletions(-) create mode 100644 classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TMethod.java create mode 100644 tests/src/test/java/org/teavm/classlib/java/lang/reflect/MethodTest.java diff --git a/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java b/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java index d9f60a034..80af233e0 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java @@ -17,6 +17,7 @@ package org.teavm.classlib.impl; import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -46,12 +47,17 @@ public class ReflectionDependencyListener extends AbstractDependencyListener { private MethodReference fieldSet = new MethodReference(Field.class, "set", Object.class, Object.class, void.class); private MethodReference newInstance = new MethodReference(Constructor.class, "newInstance", Object[].class, Object.class); + private MethodReference invokeMethod = new MethodReference(Method.class, "invoke", Object.class, Object[].class, + Object.class); private MethodReference getFields = new MethodReference(Class.class, "getDeclaredFields", Field[].class); private MethodReference getConstructors = new MethodReference(Class.class, "getDeclaredConstructors", Constructor[].class); + private MethodReference getMethods = new MethodReference(Class.class, "getDeclaredMethods", + Method[].class); private boolean fieldGetHandled; private boolean fieldSetHandled; private boolean newInstanceHandled; + private boolean invokeHandled; private Map> accessibleFieldCache = new LinkedHashMap<>(); private Map> accessibleMethodCache = new LinkedHashMap<>(); private Set classesWithReflectableFields = new LinkedHashSet<>(); @@ -85,13 +91,15 @@ public class ReflectionDependencyListener extends AbstractDependencyListener { handleFieldSet(agent, method, location); } else if (method.getReference().equals(newInstance)) { handleNewInstance(agent, method, location); + } else if (method.getReference().equals(invokeMethod)) { + handleInvoke(agent, method, location); } else if (method.getReference().equals(getFields)) { method.getVariable(0).getClassValueNode().addConsumer(type -> { if (!type.getName().startsWith("[")) { classesWithReflectableFields.add(type.getName()); } }); - } else if (method.getReference().equals(getConstructors)) { + } else if (method.getReference().equals(getConstructors) || method.getReference().equals(getMethods)) { method.getVariable(0).getClassValueNode().addConsumer(type -> { if (!type.getName().startsWith("[")) { classesWithReflectableMethods.add(type.getName()); @@ -171,8 +179,41 @@ public class ReflectionDependencyListener extends AbstractDependencyListener { linkClassIfNecessary(agent, calledMethod, location); } }); + classValueNode.connect(method.getResult()); } + private void handleInvoke(DependencyAgent agent, MethodDependency method, CallLocation location) { + if (invokeHandled) { + return; + } + invokeHandled = true; + + DependencyNode classValueNode = agent.linkMethod(getMethods, location).getVariable(0).getClassValueNode(); + classValueNode.addConsumer(reflectedType -> { + if (reflectedType.getName().startsWith("[")) { + return; + } + + Set accessibleMethods = getAccessibleMethods(agent, reflectedType.getName()); + ClassReader cls = agent.getClassSource().get(reflectedType.getName()); + for (MethodDescriptor methodDescriptor : accessibleMethods) { + MethodReader calledMethod = cls.getMethod(methodDescriptor); + MethodDependency calledMethodDep = agent.linkMethod(calledMethod.getReference(), location); + calledMethodDep.use(); + for (int i = 0; i < calledMethod.parameterCount(); ++i) { + propagateSet(agent, methodDescriptor.parameterType(i), method.getVariable(2).getArrayItem(), + calledMethodDep.getVariable(i + 1), location); + } + propagateSet(agent, ValueType.object(reflectedType.getName()), method.getVariable(1), + calledMethodDep.getVariable(0), location); + propagateGet(agent, calledMethod.getResultType(), calledMethodDep.getResult(), + method.getResult(), location); + linkClassIfNecessary(agent, calledMethod, location); + } + }); + } + + private void linkClassIfNecessary(DependencyAgent agent, MemberReader member, CallLocation location) { if (member.hasModifier(ElementModifier.STATIC)) { agent.linkClass(member.getOwnerName(), location).initClass(location); diff --git a/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSMethodMember.java b/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSMethodMember.java index 13669bca4..784002172 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSMethodMember.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSMethodMember.java @@ -23,6 +23,9 @@ public interface JSMethodMember extends JSMember { @JSProperty PlatformSequence getParameterTypes(); + @JSProperty + PlatformClass getReturnType(); + @JSProperty JSCallable getCallable(); } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java index 847827d71..8db418c6a 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java @@ -70,9 +70,45 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin { case "newEmptyInstance": method.getVariable(0).getClassValueNode().connect(method.getResult()); break; + case "getSuperclass": + reachGetSuperclass(agent, method); + break; + case "getInterfaces": + reachGetInterfaces(agent, method); + break; } } + private void reachGetSuperclass(DependencyAgent agent, MethodDependency method) { + method.getVariable(0).getClassValueNode().addConsumer(type -> { + String className = type.getName(); + if (className.startsWith("[")) { + return; + } + + ClassReader cls = agent.getClassSource().get(className); + if (cls != null && cls.getParent() != null) { + method.getResult().getClassValueNode().propagate(agent.getType(cls.getParent())); + } + }); + } + + private void reachGetInterfaces(DependencyAgent agent, MethodDependency method) { + method.getVariable(0).getClassValueNode().addConsumer(type -> { + String className = type.getName(); + if (className.startsWith("[")) { + return; + } + + ClassReader cls = agent.getClassSource().get(className); + if (cls != null) { + for (String iface : cls.getInterfaces()) { + method.getResult().getClassValueNode().propagate(agent.getType(iface)); + } + } + }); + } + private void generateCreateMetadata(GeneratorContext context, SourceWriter writer) throws IOException { ReflectionDependencyListener reflection = context.getService(ReflectionDependencyListener.class); for (String className : reflection.getClassesWithReflectableFields()) { @@ -142,6 +178,10 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin { writer.append(']'); }); + appendProperty(writer, "returnType", false, () -> { + writer.append(context.typeToClassString(method.getResultType())); + }); + appendProperty(writer, "callable", false, () -> { if (accessibleMethods != null && accessibleMethods.contains(method.getDescriptor())) { renderCallable(writer, method); diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java index 0a133b31a..b453ee394 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.teavm.backend.javascript.spi.GeneratedBy; import org.teavm.backend.javascript.spi.InjectedBy; @@ -33,13 +34,13 @@ import org.teavm.classlib.impl.reflection.JSField; import org.teavm.classlib.impl.reflection.JSMethodMember; import org.teavm.classlib.java.lang.annotation.TAnnotation; import org.teavm.classlib.java.lang.reflect.TAnnotatedElement; +import org.teavm.classlib.java.lang.reflect.TConstructor; +import org.teavm.classlib.java.lang.reflect.TField; +import org.teavm.classlib.java.lang.reflect.TMethod; +import org.teavm.classlib.java.lang.reflect.TModifier; import org.teavm.dependency.PluggableDependency; import org.teavm.interop.Address; import org.teavm.interop.DelegateTo; -import org.teavm.classlib.java.lang.reflect.TConstructor; -import org.teavm.classlib.java.lang.reflect.TField; -import org.teavm.classlib.java.lang.reflect.TModifier; -import org.teavm.dependency.PluggableDependency; import org.teavm.jso.core.JSArray; import org.teavm.platform.Platform; import org.teavm.platform.PlatformClass; @@ -58,6 +59,7 @@ public class TClass extends TObject implements TAnnotatedElement { private TField[] declaredFields; private TField[] fields; private TConstructor[] declaredConstructors; + private TMethod[] declaredMethods; private static boolean reflectionInitialized; private TClass(PlatformClass platformClass) { @@ -146,6 +148,9 @@ public class TClass extends TObject implements TAnnotatedElement { } public TField[] getDeclaredFields() throws TSecurityException { + if (isPrimitive() || isArray()) { + return new TField[0]; + } if (declaredFields == null) { initReflection(); JSClass jsClass = (JSClass) getPlatformClass().getMetadata(); @@ -158,7 +163,7 @@ public class TClass extends TObject implements TAnnotatedElement { jsField.getSetter()); } } - return declaredFields; + return declaredFields.clone(); } private static void initReflection() { @@ -172,6 +177,10 @@ public class TClass extends TObject implements TAnnotatedElement { private static native void createMetadata(); public TField[] getFields() throws TSecurityException { + if (isPrimitive() || isArray()) { + return new TField[0]; + } + if (fields == null) { List fieldList = new ArrayList<>(); TClass cls = this; @@ -191,7 +200,7 @@ public class TClass extends TObject implements TAnnotatedElement { fields = fieldList.toArray(new TField[fieldList.size()]); } - return fields; + return fields.clone(); } public TField getDeclaredField(String name) throws TNoSuchFieldError { @@ -246,6 +255,10 @@ public class TClass extends TObject implements TAnnotatedElement { @SuppressWarnings({ "raw", "unchecked" }) public TConstructor[] getDeclaredConstructors() throws TSecurityException { + if (isPrimitive() || isArray()) { + return new TConstructor[0]; + } + if (declaredConstructors == null) { initReflection(); JSClass jsClass = (JSClass) getPlatformClass().getMetadata(); @@ -267,7 +280,7 @@ public class TClass extends TObject implements TAnnotatedElement { } declaredConstructors = Arrays.copyOf(declaredConstructors, count); } - return declaredConstructors; + return declaredConstructors.clone(); } public TConstructor[] getConstructors() throws TSecurityException { @@ -325,6 +338,143 @@ public class TClass extends TObject implements TAnnotatedElement { } } + public TMethod[] getDeclaredMethods() { + if (isPrimitive() || isArray()) { + return new TMethod[0]; + } + if (declaredMethods == null) { + initReflection(); + JSClass jsClass = (JSClass) getPlatformClass().getMetadata(); + JSArray jsMethods = jsClass.getMethods(); + declaredMethods = new TMethod[jsMethods.getLength()]; + int count = 0; + for (int i = 0; i < jsMethods.getLength(); ++i) { + JSMethodMember jsMethod = jsMethods.get(i); + if (jsMethod.getName().equals("") || jsMethod.getName().equals("")) { + continue; + } + PlatformSequence jsParameterTypes = jsMethod.getParameterTypes(); + TClass[] parameterTypes = new TClass[jsParameterTypes.getLength()]; + for (int j = 0; j < parameterTypes.length; ++j) { + parameterTypes[j] = getClass(jsParameterTypes.get(j)); + } + TClass returnType = getClass(jsMethod.getReturnType()); + declaredMethods[count++] = new TMethod(this, jsMethod.getName(), jsMethod.getModifiers(), + jsMethod.getAccessLevel(), returnType, parameterTypes, jsMethod.getCallable()); + } + declaredMethods = Arrays.copyOf(declaredMethods, count); + } + return declaredMethods.clone(); + } + + public TMethod getDeclaredMethod(String name, TClass... parameterTypes) throws TNoSuchMethodException, + TSecurityException { + TMethod bestFit = null; + for (TMethod method : getDeclaredMethods()) { + if (method.getName().equals(name) && Arrays.equals(method.getParameterTypes(), parameterTypes)) { + if (bestFit == null || bestFit.getReturnType().isAssignableFrom(method.getReturnType())) { + bestFit = method; + } + } + } + if (bestFit == null) { + throw new TNoSuchMethodException(); + } + return bestFit; + } + + public TMethod[] getMethods() throws TSecurityException { + Map methods = new HashMap<>(); + findMethods(this, methods); + return methods.values().toArray(new TMethod[methods.size()]); + } + + public TMethod getMethod(String name, TClass... parameterTypes) throws TNoSuchMethodException, + TSecurityException { + TMethod method = findMethod(this, null, name, parameterTypes); + if (method == null) { + throw new TNoSuchMethodException(); + } + return method; + } + + private static void findMethods(TClass cls, Map methods) { + for (TMethod method : cls.getDeclaredMethods()) { + if (TModifier.isPublic(method.getModifiers())) { + MethodSignature signature = new MethodSignature(method.getName(), method.getParameterTypes(), + method.getReturnType()); + if (!methods.containsKey(signature)) { + methods.put(signature, method); + } + } + } + + if (!cls.isInterface()) { + TClass superclass = cls.getSuperclass(); + if (superclass != null) { + findMethods(superclass, methods); + } + } + + for (TClass iface : cls.getInterfaces()) { + findMethods(iface, methods); + } + } + + private static TMethod findMethod(TClass cls, TMethod current, String name, TClass[] parameterTypes) { + for (TMethod method : cls.getDeclaredMethods()) { + if (TModifier.isPublic(method.getModifiers()) && method.getName().equals(name) + && Arrays.equals(method.getParameterTypes(), parameterTypes)) { + if (current == null || current.getReturnType().isAssignableFrom(method.getReturnType())) { + current = method; + } + } + } + + if (!cls.isInterface()) { + TClass superclass = cls.getSuperclass(); + if (superclass != null) { + current = findMethod(superclass, current, name, parameterTypes); + } + } + + for (TClass iface : cls.getInterfaces()) { + current = findMethod(iface, current, name, parameterTypes); + } + + return current; + } + + private static final class MethodSignature { + private final String name; + private final TClass[] parameterTypes; + private final TClass returnType; + + MethodSignature(String name, TClass[] parameterTypes, TClass returnType) { + this.name = name; + this.parameterTypes = parameterTypes; + this.returnType = returnType; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MethodSignature)) { + return false; + } + MethodSignature that = (MethodSignature) o; + return Objects.equals(name, that.name) && Arrays.equals(parameterTypes, that.parameterTypes) + && Objects.equals(returnType, that.returnType); + } + + @Override + public int hashCode() { + return Objects.hash(name, Arrays.hashCode(parameterTypes), returnType); + } + } + public int getModifiers() { int flags = platformClass.getMetadata().getFlags(); int accessLevel = platformClass.getMetadata().getAccessLevel(); @@ -336,11 +486,13 @@ public class TClass extends TObject implements TAnnotatedElement { } @SuppressWarnings("unchecked") + @PluggableDependency(ClassGenerator.class) public TClass getSuperclass() { return (TClass) getClass(platformClass.getMetadata().getSuperclass()); } @SuppressWarnings("unchecked") + @PluggableDependency(ClassGenerator.class) public TClass[] getInterfaces() { PlatformSequence supertypes = platformClass.getMetadata().getSupertypes(); diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TMethod.java b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TMethod.java new file mode 100644 index 000000000..98ada3477 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TMethod.java @@ -0,0 +1,141 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang.reflect; + +import org.teavm.classlib.impl.reflection.Converter; +import org.teavm.classlib.impl.reflection.Flags; +import org.teavm.classlib.impl.reflection.JSCallable; +import org.teavm.classlib.java.lang.TClass; +import org.teavm.classlib.java.lang.TIllegalAccessException; +import org.teavm.classlib.java.lang.TIllegalArgumentException; +import org.teavm.classlib.java.lang.TObject; +import org.teavm.platform.Platform; +import org.teavm.platform.PlatformObject; +import org.teavm.platform.PlatformSequence; + +public class TMethod extends TAccessibleObject implements TMember { + private TClass declaringClass; + private String name; + private int flags; + private int accessLevel; + private TClass returnType; + private TClass[] parameterTypes; + private JSCallable callable; + + public TMethod(TClass declaringClass, String name, int flags, int accessLevel, TClass returnType, + TClass[] parameterTypes, JSCallable callable) { + this.declaringClass = declaringClass; + this.name = name; + this.flags = flags; + this.accessLevel = accessLevel; + this.returnType = returnType; + this.parameterTypes = parameterTypes; + this.callable = callable; + } + + @Override + public TClass getDeclaringClass() { + return declaringClass; + } + + @Override + public String getName() { + return name; + } + + @Override + public int getModifiers() { + return Flags.getModifiers(flags, accessLevel); + } + + public TClass getReturnType() { + return returnType; + } + + public TClass[] getParameterTypes() { + return parameterTypes.clone(); + } + + public int getParameterCount() { + return parameterTypes.length; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(TModifier.toString(getModifiers())); + if (sb.length() > 0) { + sb.append(' '); + } + sb.append(returnType.getName()).append(' ').append(declaringClass.getName()).append('.') + .append(name).append('('); + if (parameterTypes.length > 0) { + sb.append(parameterTypes[0].getName()); + for (int i = 1; i < parameterTypes.length; ++i) { + sb.append(',').append(parameterTypes[i].getName()); + } + } + sb.append(')'); + + return sb.toString(); + } + + public Object invoke(Object obj, Object... args) throws TIllegalAccessException, TIllegalArgumentException, + TInvocationTargetException { + if (callable == null) { + throw new TIllegalAccessException(); + } + + if (args.length != parameterTypes.length) { + throw new TIllegalArgumentException(); + } + + if ((flags & Flags.STATIC) == 0) { + if (!declaringClass.isInstance((TObject) obj)) { + throw new TIllegalArgumentException(); + } + } else { + Platform.initClass(declaringClass.getPlatformClass()); + } + + for (int i = 0; i < args.length; ++i) { + if (!parameterTypes[i].isPrimitive() && args[i] != null + && !parameterTypes[i].isInstance((TObject) args[i])) { + throw new TIllegalArgumentException(); + } + if (parameterTypes[i].isPrimitive() && args[i] == null) { + throw new TIllegalArgumentException(); + } + } + + PlatformSequence jsArgs = Converter.arrayFromJava(args); + PlatformObject result = callable.call(Platform.getPlatformObject(obj), jsArgs); + return Converter.toJava(result); + } + + public boolean isBridge() { + return (flags & Flags.BRIDGE) != 0; + } + + @Override + public boolean isSynthetic() { + return (flags & Flags.SYNTHETIC) != 0; + } + + public boolean isVarArgs() { + return (flags & Flags.VARARGS) != 0; + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/reflect/ConstructorTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/reflect/ConstructorTest.java index b81a46a3d..85f94b3de 100644 --- a/tests/src/test/java/org/teavm/classlib/java/lang/reflect/ConstructorTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/lang/reflect/ConstructorTest.java @@ -74,8 +74,8 @@ public class ConstructorTest { InstantiationException { Constructor constructor = ReflectableType.class.getDeclaredConstructor(); ReflectableType instance = constructor.newInstance(); - assertEquals(0, instance.a); - assertNull(instance.b); + assertEquals(0, instance.getA()); + assertNull(instance.getB()); } @Test @@ -84,8 +84,8 @@ public class ConstructorTest { Constructor constructor = ReflectableType.class .getDeclaredConstructor(int.class, Object.class); ReflectableType instance = constructor.newInstance(23, "42"); - assertEquals(23, instance.a); - assertEquals("42", instance.b); + assertEquals(23, instance.getA()); + assertEquals("42", instance.getB()); } static class ReflectableType { @@ -107,5 +107,13 @@ public class ConstructorTest { this.a = a; this.b = b; } + + public int getA() { + return a; + } + + public Object getB() { + return b; + } } } diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/reflect/MethodTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/reflect/MethodTest.java new file mode 100644 index 000000000..11590e043 --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/lang/reflect/MethodTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang.reflect; + +import static org.junit.Assert.assertEquals; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.classlib.support.Reflectable; +import org.teavm.junit.TeaVMTestRunner; + +@RunWith(TeaVMTestRunner.class) +public class MethodTest { + @Test + public void methodsEnumerated() { + callMethods(); + + String text = collectMethods(Foo.class.getDeclaredMethods()); + + assertEquals("" + + "java.lang.Object Foo.baz();" + + "public void Foo.bar(java.lang.Object);", + text); + } + + @Test + public void publicMethodsEnumerated() { + callMethods(); + + String text = collectMethods(Foo.class.getMethods()); + + assertEquals("public void Foo.bar(java.lang.Object);", text); + } + + @Test + public void inheritedPublicMethodEnumerated() { + callMethods(); + + String text = collectMethods(SubClass.class.getMethods()); + + assertEquals("public void SubClass.g();public void SuperClass.f();", text); + } + + @Test + public void bridgeMethodNotFound() throws NoSuchMethodException { + callMethods(); + + Method method = SubClassWithBridge.class.getMethod("f"); + + assertEquals(String.class, method.getReturnType()); + } + + @Test + public void methodInvoked() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Foo foo = new Foo(); + Method method = foo.getClass().getMethod("bar", Object.class); + method.invoke(foo, "23"); + assertEquals("23", foo.baz()); + } + + @Test + public void methodInvoked2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Foo foo = new Foo(); + foo.bar("42"); + Method method = foo.getClass().getDeclaredMethod("baz"); + assertEquals("42", method.invoke(foo)); + } + + @Test + public void staticInitializerCalled() throws NoSuchMethodException, InvocationTargetException, + IllegalAccessException { + Method method = WithInitializer.class.getMethod("f"); + method.invoke(null); + assertEquals("init;f();", WithInitializer.log); + } + + private void callMethods() { + new Foo().bar(null); + new Foo().baz(); + new SuperClass().f(); + new SubClass().g(); + new SuperClassWithBridge().f(); + new SubClassWithBridge().f(); + } + + private String collectMethods(Method[] methods) { + List lines = new ArrayList<>(); + for (Method method : methods) { + if (!method.getDeclaringClass().equals(Object.class)) { + lines.add(method + ";"); + } + } + StringBuilder sb = new StringBuilder(); + Collections.sort(lines); + for (String line : lines) { + sb.append(line); + } + return sb.toString().replace("org.teavm.classlib.java.lang.reflect.MethodTest$", ""); + } + + static class Foo { + Object value; + + @Reflectable + public void bar(Object value) { + this.value = value; + } + + @Reflectable + Object baz() { + return value; + } + } + + static class SuperClass { + @Reflectable + public void f() { + } + } + + static class SubClass extends SuperClass { + @Reflectable + public void g() { + } + } + + static class SuperClassWithBridge { + @Reflectable + public Object f() { + return null; + } + } + + static class SubClassWithBridge { + @Reflectable + public String f() { + return null; + } + } + + static class WithInitializer { + static String log = ""; + + static { + log += "init;"; + } + + @Reflectable public static void f() { + log += "f();"; + } + } +}