diff --git a/.idea/runConfigurations/run_tests.xml b/.idea/runConfigurations/run_tests.xml index 91d14d00b..ea56f5fb0 100644 --- a/.idea/runConfigurations/run_tests.xml +++ b/.idea/runConfigurations/run_tests.xml @@ -2,7 +2,7 @@ - diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java b/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java index 6efa42059..257f58e0e 100644 --- a/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java +++ b/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java @@ -28,17 +28,25 @@ import org.teavm.metaprogramming.impl.reflect.ReflectClassImpl; import org.teavm.metaprogramming.impl.reflect.ReflectContext; import org.teavm.metaprogramming.impl.reflect.ReflectFieldImpl; import org.teavm.metaprogramming.impl.reflect.ReflectMethodImpl; +import org.teavm.model.BasicBlock; import org.teavm.model.CallLocation; import org.teavm.model.ClassHolder; import org.teavm.model.ClassReaderSource; +import org.teavm.model.Instruction; import org.teavm.model.InstructionLocation; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; import org.teavm.model.Variable; +import org.teavm.model.instructions.DoubleConstantInstruction; import org.teavm.model.instructions.ExitInstruction; +import org.teavm.model.instructions.FloatConstantInstruction; +import org.teavm.model.instructions.IntegerConstantInstruction; import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.LongConstantInstruction; +import org.teavm.model.instructions.NullConstantInstruction; +import org.teavm.model.util.InstructionTransitionExtractor; public final class MetaprogrammingImpl { static ClassLoader classLoader; @@ -217,6 +225,62 @@ public final class MetaprogrammingImpl { agent.submitClass(cls); } + public static void close() { + InstructionTransitionExtractor transitionExtractor = new InstructionTransitionExtractor(); + BasicBlock block = generator.currentBlock(); + Instruction lastInstruction = block.getLastInstruction(); + if (lastInstruction != null) { + lastInstruction.acceptVisitor(transitionExtractor); + } + if (transitionExtractor.getTargets() != null) { + return; + } + + Variable var; + if (returnType instanceof ValueType.Void) { + var = null; + } else if (returnType instanceof ValueType.Primitive) { + var = generator.program.createVariable(); + switch (((ValueType.Primitive) returnType).getKind()) { + case BOOLEAN: + case BYTE: + case SHORT: + case CHARACTER: + case INTEGER: { + IntegerConstantInstruction constantInsn = new IntegerConstantInstruction(); + constantInsn.setReceiver(var); + generator.add(constantInsn); + break; + } + case LONG: { + LongConstantInstruction constantInsn = new LongConstantInstruction(); + constantInsn.setReceiver(var); + generator.add(constantInsn); + break; + } + case FLOAT: { + FloatConstantInstruction constantInsn = new FloatConstantInstruction(); + constantInsn.setReceiver(var); + generator.add(constantInsn); + break; + } + case DOUBLE: { + DoubleConstantInstruction constantInsn = new DoubleConstantInstruction(); + constantInsn.setReceiver(var); + generator.add(constantInsn); + break; + } + } + } else { + NullConstantInstruction constantInsn = new NullConstantInstruction(); + var = generator.program.createVariable(); + constantInsn.setReceiver(var); + generator.add(constantInsn); + } + + returnValue(var); + } + private static void unsupported() { throw new UnsupportedOperationException("This operation is only supported from TeaVM compile-time " + "environment"); diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/UsageGenerator.java b/core/src/main/java/org/teavm/metaprogramming/impl/UsageGenerator.java index a947285e5..0d200d119 100644 --- a/core/src/main/java/org/teavm/metaprogramming/impl/UsageGenerator.java +++ b/core/src/main/java/org/teavm/metaprogramming/impl/UsageGenerator.java @@ -146,6 +146,7 @@ class UsageGenerator { try { proxyMethod.invoke(null, proxyArgs); + MetaprogrammingImpl.close(); Program program = MetaprogrammingImpl.generator.getProgram(); //new BoxingEliminator().optimize(program); diff --git a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/InvocationHandler.java b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/InvocationHandler.java index 2fbbaf4fa..9bc3a81cf 100644 --- a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/InvocationHandler.java +++ b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/InvocationHandler.java @@ -18,5 +18,5 @@ package org.teavm.metaprogramming; import org.teavm.metaprogramming.reflect.ReflectMethod; public interface InvocationHandler { - void invoke(Value proxy, ReflectMethod method, Value[] args); + Computation invoke(Value proxy, ReflectMethod method, Value[] args); } diff --git a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/Metaprogramming.java b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/Metaprogramming.java index 69820d2ba..db7e4ec1d 100644 --- a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/Metaprogramming.java +++ b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/Metaprogramming.java @@ -71,8 +71,7 @@ public final class Metaprogramming { } public static ReflectClass arrayClass(ReflectClass componentType) { - unsupported(); - return null; + throw new UnsupportedOperationException(); } public static ReflectClass createClass(byte[] bytecode) { diff --git a/tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingGenerator2.java b/tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingGenerator2.java new file mode 100644 index 000000000..67b376f95 --- /dev/null +++ b/tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingGenerator2.java @@ -0,0 +1,27 @@ +/* + * 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.metaprogramming.test; + +import static org.teavm.metaprogramming.Metaprogramming.emit; +import org.teavm.metaprogramming.CompileTime; +import org.teavm.metaprogramming.Value; + +@CompileTime +public class MetaprogrammingGenerator2 { + public Value addParentheses(String value) { + return emit(() -> "[" + value + "]"); + } +} diff --git a/tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingTest.java b/tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingTest.java index adfcc3480..0ef43a586 100644 --- a/tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingTest.java +++ b/tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingTest.java @@ -16,7 +16,13 @@ package org.teavm.metaprogramming.test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.teavm.metaprogramming.Metaprogramming.arrayClass; +import static org.teavm.metaprogramming.Metaprogramming.emit; import static org.teavm.metaprogramming.Metaprogramming.exit; +import static org.teavm.metaprogramming.Metaprogramming.findClass; import org.junit.Test; import org.junit.runner.RunWith; import org.teavm.junit.SkipJVM; @@ -25,12 +31,15 @@ import org.teavm.metaprogramming.CompileTime; import org.teavm.metaprogramming.Meta; import org.teavm.metaprogramming.ReflectClass; import org.teavm.metaprogramming.Value; +import org.teavm.metaprogramming.reflect.ReflectField; +import org.teavm.metaprogramming.reflect.ReflectMethod; +import org.teavm.metaprogramming.test.subpackage.MetaprogrammingGenerator; @CompileTime @RunWith(TeaVMTestRunner.class) +@SkipJVM public class MetaprogrammingTest { @Test - @SkipJVM public void works() { assertEquals("java.lang.Object".length() + 2, classNameLength(Object.class, 2)); assertEquals("java.lang.Integer".length() + 3, classNameLength(Integer.valueOf(5).getClass(), 3)); @@ -42,4 +51,299 @@ public class MetaprogrammingTest { int length = cls.getName().length(); exit(() -> length + add.get()); } + + @Test + public void getsField() { + Context ctx = new Context(); + ctx.a = 2; + ctx.b = 3; + + assertEquals(2, getField(ctx.getClass(), ctx)); + } + + @Meta + private static native Object getField(Class cls, Object obj); + private static void getField(ReflectClass cls, Value obj) { + ReflectField field = cls.getField("a"); + exit(() -> field.get(obj)); + } + @Test + public void setsField() { + Context ctx = new Context(); + setField(ctx.getClass(), ctx, 3); + + assertEquals(3, ctx.a); + } + + @Meta + private static native void setField(Class cls, Object obj, Object value); + private static void setField(ReflectClass cls, Value obj, Value value) { + ReflectField field = cls.getField("a"); + emit(() -> field.set(obj, value)); + } + + @Test + public void methodInvoked() { + assertEquals("debug!", callDebug(A.class, new A())); + assertEquals("missing", callDebug(B.class, new B())); + assertEquals("missing", callDebug(A.class, new A(), "foo", 23)); + assertEquals("debug!foo:23", callDebug(B.class, new B(), "foo", 23)); + } + + @Meta + private static native String callDebug(Class cls, Object obj); + private static void callDebug(ReflectClass cls, Value obj) { + ReflectMethod method = cls.getMethod("debug"); + if (method == null) { + exit(() -> "missing"); + } else { + exit(() -> method.invoke(obj.get())); + } + } + + @Meta + private static native String callDebug(Class cls, Object obj, String a, int b); + private static void callDebug(ReflectClass cls, Value obj, Value a, Value b) { + ReflectClass stringClass = findClass(String.class); + ReflectClass intClass = findClass(int.class); + ReflectMethod method = cls.getMethod("debug", stringClass, intClass); + if (method == null) { + exit(() -> "missing"); + } else { + exit(() -> method.invoke(obj.get(), a.get(), b.get())); + } + } + + @Test + public void constructorInvoked() { + assertEquals(C.class.getName(), callConstructor(C.class).getClass().getName()); + assertNull(callConstructor(D.class)); + + assertNull(callConstructor(C.class, "foo", 23)); + + D instance = (D) callConstructor(D.class, "foo", 23); + assertEquals(D.class.getName(), instance.getClass().getName()); + assertEquals("foo", instance.a); + assertEquals(23, instance.b); + } + + @Meta + private static native Object callConstructor(Class type); + private static void callConstructor(ReflectClass type) { + ReflectMethod ctor = type.getMethod(""); + if (ctor != null) { + exit(() -> ctor.construct()); + } else { + exit(() -> null); + } + } + + @Meta + private static native Object callConstructor(Class type, String a, int b); + private static void callConstructor(ReflectClass type, Value a, Value b) { + ReflectClass stringClass = findClass(String.class); + ReflectClass intClass = findClass(int.class); + ReflectMethod ctor = type.getMethod("", stringClass, intClass); + if (ctor != null) { + exit(() -> ctor.construct(a, b)); + } else { + exit(() -> null); + } + } + + @Test + public void capturesArray() { + assertEquals("23:foo", captureArray(23, "foo")); + } + + @Meta + private static native String captureArray(int a, String b); + private static void captureArray(Value a, Value b) { + Value[] array = { a, emit(() -> ":"), b }; + exit(() -> String.valueOf(array[0].get()) + array[1].get() + array[2].get()); + } + + @Test + public void isInstanceWorks() { + assertTrue(isInstance("foo", String.class)); + assertFalse(isInstance(23, String.class)); + } + + @Meta + private static native boolean isInstance(Object obj, Class type); + private static void isInstance(Value obj, ReflectClass type) { + exit(() -> type.isInstance(obj.get())); + } + + @Test + public void capturesNull() { + assertEquals("foo:", captureArgument("foo")); + } + + @Meta + private static native String captureArgument(String a); + private static void captureArgument(Value a) { + exit(() -> a.get() + ":"); + } + + @Test + public void annotationsWork() { + assertEquals("" + + "foo:23:Object\n" + + "foo=!:42:String:int\n" + + "f=!:23\n", + readAnnotations(WithAnnotations.class, new WithAnnotations())); + } + + @Meta + private static native String readAnnotations(Class cls, Object obj); + private static void readAnnotations(ReflectClass cls, Value obj) { + StringBuilder sb = new StringBuilder(); + sb.append(describeAnnotation(cls.getAnnotation(TestAnnotation.class))).append('\n'); + for (ReflectMethod method : cls.getDeclaredMethods()) { + TestAnnotation annot = method.getAnnotation(TestAnnotation.class); + if (annot == null) { + continue; + } + sb.append(method.getName()).append('=').append(describeAnnotation(annot)).append('\n'); + } + for (ReflectField field : cls.getDeclaredFields()) { + TestAnnotation annot = field.getAnnotation(TestAnnotation.class); + if (annot == null) { + continue; + } + sb.append(field.getName()).append('=').append(describeAnnotation(annot)).append('\n'); + } + String result = sb.toString(); + exit(() -> result); + } + + private static String describeAnnotation(TestAnnotation annot) { + StringBuilder sb = new StringBuilder(); + sb.append(annot.a()).append(':').append(annot.b()); + for (Class cls : annot.c()) { + sb.append(':').append(cls.getSimpleName()); + } + return sb.toString(); + } + + @Test + public void compileTimeAnnotationRespectsPackage() { + assertEquals("(foo)", compileTimePackage(true)); + } + + @Meta + private static native String compileTimePackage(boolean ignoreMe); + private static void compileTimePackage(Value ignoreMe) { + Value result = new MetaprogrammingGenerator().addParentheses("foo"); + exit(() -> result.get()); + } + + @Test + public void compileTimeAnnotationRespectsClass() { + assertEquals("[foo]", compileTimeClass(true)); + } + + @Meta + private static native String compileTimeClass(boolean ignoreMe); + private static void compileTimeClass(Value ignoreMe) { + Value result = new MetaprogrammingGenerator2().addParentheses("foo"); + exit(() -> result.get()); + } + + @Test + public void compileTimeAnnotationRespectsNestedClass() { + assertEquals("{foo}", compileTimeNestedClass(true)); + } + + @Meta + private static native String compileTimeNestedClass(boolean ignoreMe); + private static void compileTimeNestedClass(Value ignoreMe) { + Value result = new MetaprogrammingGenerator3().addParentheses("foo"); + exit(() -> result.get()); + } + + @Test + public void emitsClassLiteralFromReflectClass() { + assertEquals(String[].class.getName(), emitClassLiteral(String.class)); + } + + @Meta + private static native String emitClassLiteral(Class cls); + private static void emitClassLiteral(ReflectClass cls) { + ReflectClass arrayClass = arrayClass(cls); + exit(() -> arrayClass.asJavaClass().getName()); + } + + @Test + public void createsArrayViaReflection() { + Object array = createArrayOfType(String.class, 10); + assertEquals(String[].class, array.getClass()); + assertEquals(10, ((String[]) array).length); + } + + @Meta + private static native Object createArrayOfType(Class cls, int size); + private static void createArrayOfType(ReflectClass cls, Value size) { + exit(() -> cls.createArray(size.get())); + } + + @Test + public void getsArrayElementViaReflection() { + assertEquals("foo", getArrayElement(String[].class, new String[] { "foo" }, 0)); + } + + @Meta + private static native Object getArrayElement(Class type, Object array, int index); + private static void getArrayElement(ReflectClass type, Value array, Value index) { + exit(() -> type.getArrayElement(array.get(), index.get())); + } + + static class Context { + public int a; + public int b; + } + + class A { + public String debug() { + return "debug!"; + } + } + + class B { + public String debug(String a, int b) { + return "debug!" + a + ":" + b; + } + } + static class C { + public C() { + } + } + + static class D { + String a; + int b; + + public D(String a, int b) { + this.a = a; + this.b = b; + } + } + + @TestAnnotation(a = "foo", c = Object.class) + static class WithAnnotations { + @TestAnnotation(c = {}) + int f; + + @TestAnnotation(b = 42, c = { String.class, int.class }) + int foo() { + return 0; + } + } + + static class MetaprogrammingGenerator3 { + public Value addParentheses(String value) { + return emit(() -> "{" + value + "}"); + } + } } diff --git a/tests/src/test/java/org/teavm/metaprogramming/test/TestAnnotation.java b/tests/src/test/java/org/teavm/metaprogramming/test/TestAnnotation.java new file mode 100644 index 000000000..94a1ed803 --- /dev/null +++ b/tests/src/test/java/org/teavm/metaprogramming/test/TestAnnotation.java @@ -0,0 +1,28 @@ +/* + * 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.metaprogramming.test; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface TestAnnotation { + String a() default "!"; + + int b() default 23; + + Class[] c(); +} diff --git a/tests/src/test/java/org/teavm/metaprogramming/test/subpackage/MetaprogrammingGenerator.java b/tests/src/test/java/org/teavm/metaprogramming/test/subpackage/MetaprogrammingGenerator.java new file mode 100644 index 000000000..b70f56140 --- /dev/null +++ b/tests/src/test/java/org/teavm/metaprogramming/test/subpackage/MetaprogrammingGenerator.java @@ -0,0 +1,25 @@ +/* + * 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.metaprogramming.test.subpackage; + +import static org.teavm.metaprogramming.Metaprogramming.emit; +import org.teavm.metaprogramming.Value; + +public class MetaprogrammingGenerator { + public Value addParentheses(String value) { + return emit(() -> "(" + value + ")"); + } +} diff --git a/tests/src/test/java/org/teavm/metaprogramming/test/subpackage/package-info.java b/tests/src/test/java/org/teavm/metaprogramming/test/subpackage/package-info.java new file mode 100644 index 000000000..6dee197cf --- /dev/null +++ b/tests/src/test/java/org/teavm/metaprogramming/test/subpackage/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@CompileTime +package org.teavm.metaprogramming.test.subpackage; + +import org.teavm.metaprogramming.CompileTime; \ No newline at end of file