diff --git a/classlib/teavm-classlib.iml b/classlib/teavm-classlib.iml
index e0ac077cc..ead5bc4ab 100644
--- a/classlib/teavm-classlib.iml
+++ b/classlib/teavm-classlib.iml
@@ -35,6 +35,7 @@
+
diff --git a/core/pom.xml b/core/pom.xml
index 92c112da5..663895ed5 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -32,6 +32,11 @@
junit
provided
+
+ org.teavm
+ teavm-metaprogramming-api
+ ${project.version}
+
commons-io
commons-io
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/ActionImpl.java b/core/src/main/java/org/teavm/metaprogramming/impl/ActionImpl.java
new file mode 100644
index 000000000..6b3472a85
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/ActionImpl.java
@@ -0,0 +1,35 @@
+/*
+ * 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.impl;
+
+import java.util.List;
+import org.teavm.metaprogramming.Action;
+import org.teavm.model.MethodReference;
+
+public class ActionImpl extends Fragment implements Action {
+ public ActionImpl(List capturedValues, MethodReference method) {
+ super(capturedValues, method);
+ }
+
+ @Override
+ public void run() {
+ throw new IllegalStateException("Don't call this method directly");
+ }
+
+ public static ActionImpl create(List capturedValues, MethodReference method) {
+ return new ActionImpl(capturedValues, method);
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/CapturedValue.java b/core/src/main/java/org/teavm/metaprogramming/impl/CapturedValue.java
new file mode 100644
index 000000000..3f2d08e5c
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/CapturedValue.java
@@ -0,0 +1,26 @@
+/*
+ * 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.impl;
+
+public class CapturedValue {
+ public Object obj;
+ public boolean primitive;
+
+ public CapturedValue(Object obj, boolean primitive) {
+ this.obj = obj;
+ this.primitive = primitive;
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/ComputationImpl.java b/core/src/main/java/org/teavm/metaprogramming/impl/ComputationImpl.java
new file mode 100644
index 000000000..e76805ac2
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/ComputationImpl.java
@@ -0,0 +1,35 @@
+/*
+ * 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.impl;
+
+import java.util.List;
+import org.teavm.metaprogramming.Computation;
+import org.teavm.model.MethodReference;
+
+public class ComputationImpl extends Fragment implements Computation {
+ public ComputationImpl(List capturedValues, MethodReference method) {
+ super(capturedValues, method);
+ }
+
+ @Override
+ public T compute() {
+ throw new IllegalStateException("Don't call this method directly");
+ }
+
+ public static ComputationImpl create(List capturedValues, MethodReference method) {
+ return new ComputationImpl<>(capturedValues, method);
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/Fragment.java b/core/src/main/java/org/teavm/metaprogramming/impl/Fragment.java
new file mode 100644
index 000000000..ff71da2e9
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/Fragment.java
@@ -0,0 +1,29 @@
+/*
+ * 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.impl;
+
+import java.util.List;
+import org.teavm.model.MethodReference;
+
+public class Fragment {
+ List capturedValues;
+ MethodReference method;
+
+ public Fragment(List capturedValues, MethodReference method) {
+ this.capturedValues = capturedValues;
+ this.method = method;
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingClassLoader.java b/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingClassLoader.java
new file mode 100644
index 000000000..6b00fd20c
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingClassLoader.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.metaprogramming.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.io.IOUtils;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.teavm.metaprogramming.CompileTime;
+
+public class MetaprogrammingClassLoader extends ClassLoader {
+ private MetaprogrammingInstrumentation instrumentation = new MetaprogrammingInstrumentation();
+ private Map compileTimeClasses = new HashMap<>();
+ private Map compileTimePackages = new HashMap<>();
+
+ public MetaprogrammingClassLoader(ClassLoader parent) {
+ super(parent);
+ }
+
+ @Override
+ protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ if (!isCompileTimeClass(name)) {
+ return super.loadClass(name, resolve);
+ } else {
+ try (InputStream input = getResourceAsStream(name.replace('.', '/') + ".class")) {
+ byte[] array = instrumentation.instrument(IOUtils.toByteArray(input));
+ return defineClass(name, array, 0, array.length);
+ } catch (IOException e) {
+ throw new ClassNotFoundException("Error reading bytecode of class " + name, e);
+ }
+ }
+ }
+
+ public boolean isCompileTimeClass(String name) {
+ return compileTimeClasses.computeIfAbsent(name, n -> checkIfCompileTime(n));
+ }
+
+ private boolean checkIfCompileTime(String name) {
+ String packageName = name;
+ while (true) {
+ int index = packageName.lastIndexOf('.');
+ if (index < 0) {
+ break;
+ }
+ packageName = packageName.substring(0, index);
+ if (isCompileTimePackage(packageName)) {
+ return true;
+ }
+ }
+
+ String outerName = name;
+ while (true) {
+ int index = outerName.lastIndexOf('$');
+ if (index < 0) {
+ break;
+ }
+ outerName = outerName.substring(0, index);
+ if (isCompileTimeClass(outerName)) {
+ return true;
+ }
+ }
+
+ CompileTimeClassVisitor visitor = new CompileTimeClassVisitor();
+ try (InputStream input = getResourceAsStream(name.replace('.', '/') + ".class")) {
+ if (input == null) {
+ return false;
+ }
+ new ClassReader(input).accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
+ } catch (IOException e) {
+ return false;
+ }
+ if (visitor.compileTime) {
+ return true;
+ }
+
+ if (visitor.parent != null && !visitor.parent.equals(name)) {
+ return isCompileTimeClass(visitor.parent);
+ }
+
+ return false;
+ }
+
+ private boolean isCompileTimePackage(String name) {
+ return compileTimePackages.computeIfAbsent(name, n -> checkIfCompileTimePackage(n));
+ }
+
+ private boolean checkIfCompileTimePackage(String name) {
+ CompileTimeClassVisitor visitor = new CompileTimeClassVisitor();
+ try (InputStream input = getResourceAsStream(name.replace('.', '/') + "/package-info.class")) {
+ if (input == null) {
+ return false;
+ }
+ new ClassReader(input).accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
+ } catch (IOException e) {
+ return false;
+ }
+ return visitor.compileTime;
+ }
+
+ static class CompileTimeClassVisitor extends ClassVisitor {
+ String parent;
+ boolean compileTime;
+
+ CompileTimeClassVisitor() {
+ super(Opcodes.ASM5, null);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName,
+ String[] interfaces) {
+ this.parent = superName != null ? superName.replace('/', '.') : null;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (desc.equals(Type.getDescriptor(CompileTime.class))) {
+ compileTime = true;
+ }
+ return null;
+ }
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingDependencyListener.java b/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingDependencyListener.java
new file mode 100644
index 000000000..ddca66320
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingDependencyListener.java
@@ -0,0 +1,128 @@
+/*
+ * 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.impl;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.teavm.dependency.AbstractDependencyListener;
+import org.teavm.dependency.DependencyAgent;
+import org.teavm.dependency.MethodDependency;
+import org.teavm.dependency.MethodDependencyInfo;
+import org.teavm.metaprogramming.impl.model.MethodDescriber;
+import org.teavm.metaprogramming.impl.model.MethodModel;
+import org.teavm.model.CallLocation;
+import org.teavm.model.MethodReference;
+import org.teavm.model.ValueType;
+import org.teavm.model.emit.ProgramEmitter;
+import org.teavm.model.emit.StringChooseEmitter;
+import org.teavm.model.emit.ValueEmitter;
+
+public class MetaprogrammingDependencyListener extends AbstractDependencyListener {
+ private MethodDescriber describer;
+ private Set installedProxies = new HashSet<>();
+ private MetaprogrammingClassLoader proxyClassLoader;
+
+ @Override
+ public void started(DependencyAgent agent) {
+ proxyClassLoader = new MetaprogrammingClassLoader(agent.getClassLoader());
+ describer = new MethodDescriber(agent.getDiagnostics(), agent.getClassSource());
+ }
+
+ @Override
+ public void methodReached(DependencyAgent agent, MethodDependency methodDep, CallLocation location) {
+ MethodModel proxy = describer.getMethod(methodDep.getReference());
+ if (proxy != null && installedProxies.add(proxy)) {
+ new UsageGenerator(agent, proxy, methodDep, location, proxyClassLoader).installProxyEmitter();
+ }
+ }
+
+ @Override
+ public void completing(DependencyAgent agent) {
+ for (MethodModel model : describer.getKnownMethods()) {
+ ProgramEmitter pe = ProgramEmitter.create(model.getMethod().getDescriptor(), agent.getClassSource());
+
+ ValueEmitter[] paramVars = new ValueEmitter[model.getMetaParameterCount()];
+ for (int i = 0; i < paramVars.length; ++i) {
+ paramVars[i] = pe.var(i, model.getMetaParameterType(i));
+ }
+
+ if (model.getUsages().size() == 1) {
+ emitSingleUsage(model, pe, agent, paramVars);
+ } else if (model.getUsages().isEmpty()) {
+ if (model.getMethod().getReturnType() == ValueType.VOID) {
+ pe.exit();
+ } else {
+ pe.constantNull(Object.class).returnValue();
+ }
+ } else {
+ emitMultipleUsage(model, pe, agent, paramVars);
+ }
+ }
+ }
+
+ private void emitSingleUsage(MethodModel model, ProgramEmitter pe, DependencyAgent agent,
+ ValueEmitter[] paramVars) {
+ MethodReference usage = model.getUsages().values().stream().findFirst().get();
+ ValueEmitter result = pe.invoke(usage, paramVars);
+ if (usage.getReturnType() == ValueType.VOID) {
+ pe.exit();
+ } else {
+ assert result != null : "Expected non-null result at " + model.getMethod();
+ result.returnValue();
+ }
+ agent.submitMethod(model.getMethod(), pe.getProgram());
+ }
+
+ private void emitMultipleUsage(MethodModel model, ProgramEmitter pe, DependencyAgent agent,
+ ValueEmitter[] paramVars) {
+ MethodDependencyInfo methodDep = agent.getMethod(model.getMethod());
+ ValueEmitter paramVar = paramVars[model.getMetaClassParameterIndex()];
+ ValueEmitter tag = paramVar.invokeVirtual("getClass", Class.class).invokeVirtual("getName", String.class);
+
+ StringChooseEmitter choice = pe.stringChoice(tag);
+ for (Map.Entry usageEntry : model.getUsages().entrySet()) {
+ ValueType type = usageEntry.getKey();
+ String typeName = type instanceof ValueType.Object
+ ? ((ValueType.Object) type).getClassName()
+ : type.toString();
+ choice.option(typeName, () -> {
+ MethodReference implMethod = usageEntry.getValue();
+ ValueEmitter[] castParamVars = new ValueEmitter[paramVars.length];
+ for (int i = 0; i < castParamVars.length; ++i) {
+ castParamVars[i] = paramVars[i].cast(implMethod.parameterType(i));
+ }
+ ValueEmitter result = pe.invoke(implMethod, castParamVars);
+ if (implMethod.getReturnType() == ValueType.VOID) {
+ pe.exit();
+ } else {
+ assert result != null : "Expected non-null result at " + model.getMethod();
+ result.returnValue();
+ }
+ });
+ }
+
+ choice.otherwise(() -> {
+ if (methodDep.getReference().getReturnType() == ValueType.VOID) {
+ pe.exit();
+ } else {
+ pe.constantNull(Object.class).returnValue();
+ }
+ });
+
+ agent.submitMethod(model.getMethod(), pe.getProgram());
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java b/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java
new file mode 100644
index 000000000..9cca79a31
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java
@@ -0,0 +1,108 @@
+/*
+ * 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.impl;
+
+import org.teavm.metaprogramming.Action;
+import org.teavm.metaprogramming.Computation;
+import org.teavm.metaprogramming.InvocationHandler;
+import org.teavm.metaprogramming.LazyComputation;
+import org.teavm.metaprogramming.ReflectClass;
+import org.teavm.metaprogramming.Scope;
+import org.teavm.metaprogramming.Value;
+import org.teavm.metaprogramming.impl.reflect.ReflectClassImpl;
+import org.teavm.metaprogramming.impl.reflect.ReflectContext;
+import org.teavm.model.ValueType;
+
+public final class MetaprogrammingImpl {
+ static ClassLoader classLoader;
+ static ReflectContext reflectContext;
+
+ private MetaprogrammingImpl() {
+ }
+
+ public static Value emit(Computation computation) {
+ unsupported();
+ return null;
+ }
+
+ public static void emit(Action action) {
+ unsupported();
+ }
+
+ public static Value lazyFragment(LazyComputation computation) {
+ unsupported();
+ return null;
+ }
+
+ public static Value lazy(Computation computation) {
+ unsupported();
+ return null;
+ }
+
+ public static Scope currentScope() {
+ unsupported();
+ return null;
+ }
+
+ public static void location(String fileName, int lineNumber) {
+ unsupported();
+ }
+
+ public static void defaultLocation() {
+ unsupported();
+ }
+
+ public static ReflectClass> findClass(String name) {
+ return reflectContext.findClass(name);
+ }
+
+ public static ReflectClass findClass(Class cls) {
+ return reflectContext.findClass(cls);
+ }
+
+ static ReflectClass> findClass(ValueType type) {
+ return reflectContext.getClass(type);
+ }
+
+ public static ClassLoader getClassLoader() {
+ return classLoader;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static ReflectClass arrayClass(ReflectClass componentType) {
+ ReflectClassImpl componentTypeImpl = (ReflectClassImpl) componentType;
+ return (ReflectClassImpl) reflectContext.getClass(ValueType.arrayOf(componentTypeImpl.type));
+ }
+
+ public static ReflectClass> createClass(byte[] bytecode) {
+ unsupported();
+ return null;
+ }
+
+ public static Value proxy(Class type, InvocationHandler handler) {
+ return proxy(findClass(type), handler);
+ }
+
+ public static Value proxy(ReflectClass type, InvocationHandler handler) {
+ unsupported();
+ return null;
+ }
+
+ 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/MetaprogrammingInstrumentation.java b/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingInstrumentation.java
new file mode 100644
index 000000000..3ea257e9f
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingInstrumentation.java
@@ -0,0 +1,222 @@
+/*
+ * 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.impl;
+
+import java.lang.invoke.LambdaMetafactory;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.teavm.metaprogramming.Action;
+import org.teavm.metaprogramming.Computation;
+import org.teavm.metaprogramming.Metaprogramming;
+import org.teavm.model.MethodReference;
+
+public class MetaprogrammingInstrumentation {
+ private static String lambdaMetafactory = LambdaMetafactory.class.getName().replace('.', '/');
+ private static String proxyHelperType = Type.getInternalName(RuntimeHelper.class);
+ private static String listDesc = Type.getDescriptor(List.class);
+
+ public byte[] instrument(byte[] bytecode) {
+ ClassReader reader = new ClassReader(bytecode);
+ ClassWriter writer = new ClassWriter(0);
+ ClassTransformer transformer = new ClassTransformer(writer);
+ reader.accept(transformer, 0);
+ return writer.toByteArray();
+ }
+
+ class ClassTransformer extends ClassVisitor {
+ ClassTransformer(ClassVisitor cv) {
+ super(Opcodes.ASM5, cv);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ MethodVisitor innerVisitor = super.visitMethod(access, name, desc, signature, exceptions);
+ return new MethodTransformer(innerVisitor);
+ }
+ }
+
+ class MethodTransformer extends MethodVisitor {
+ private boolean instrumented;
+
+ MethodTransformer(MethodVisitor mv) {
+ super(Opcodes.ASM5, mv);
+ }
+
+ @Override
+ public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
+ if (!bsm.getOwner().equals(lambdaMetafactory) || bsm.getName().equals("metafactory")) {
+ Type returnType = Type.getReturnType(desc);
+ if (returnType.getSort() == Type.OBJECT) {
+ if (returnType.getClassName().equals(Action.class.getName())) {
+ instrumented = true;
+ transformAction(desc, bsmArgs);
+ return;
+ } else if (returnType.getClassName().equals(Computation.class.getName())) {
+ instrumented = true;
+ transformComputation(desc, bsmArgs);
+ return;
+ }
+ }
+ }
+ super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
+ }
+
+ private void transformAction(String desc, Object[] bsmArgs) {
+ transformArguments(desc);
+ transformMethod((Handle) bsmArgs[1]);
+ super.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(ActionImpl.class), "create",
+ "(" + Type.getDescriptor(List.class) + Type.getDescriptor(MethodReference.class) + ")"
+ + Type.getDescriptor(ActionImpl.class), false);
+ }
+
+ private void transformComputation(String desc, Object[] bsmArgs) {
+ transformArguments(desc);
+ transformMethod((Handle) bsmArgs[1]);
+ super.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(ComputationImpl.class), "create",
+ "(" + Type.getDescriptor(List.class) + Type.getDescriptor(MethodReference.class) + ")"
+ + Type.getDescriptor(ComputationImpl.class), false);
+ }
+
+ private void transformArguments(String desc) {
+ Type[] argTypes = Type.getArgumentTypes(desc);
+ String arrayListType = Type.getInternalName(ArrayList.class);
+
+ super.visitTypeInsn(Opcodes.NEW, arrayListType);
+ super.visitInsn(Opcodes.DUP);
+ super.visitIntInsn(Opcodes.SIPUSH, argTypes.length);
+ super.visitMethodInsn(Opcodes.INVOKESPECIAL, arrayListType, "", "(I)V", false);
+
+ for (int i = argTypes.length - 1; i >= 0; --i) {
+ transformArgument(argTypes[i]);
+ }
+
+ super.visitInsn(Opcodes.DUP);
+ super.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Collections.class), "reverse",
+ "(" + listDesc + ")V", false);
+ }
+
+ private void transformArgument(Type type) {
+ switch (type.getSort()) {
+ case Type.BOOLEAN:
+ case Type.BYTE:
+ case Type.SHORT:
+ case Type.CHAR:
+ case Type.INT:
+ transformArgument("I");
+ break;
+ case Type.LONG:
+ transformArgument("L");
+ break;
+ case Type.FLOAT:
+ transformArgument("F");
+ break;
+ case Type.DOUBLE:
+ transformArgument("D");
+ break;
+ default:
+ transformArgument("Ljava/lang/Object;");
+ break;
+ }
+ }
+
+ private void transformArgument(String desc) {
+ super.visitMethodInsn(Opcodes.INVOKESTATIC, proxyHelperType, "add",
+ "(" + desc + listDesc + ")" + listDesc, false);
+ }
+
+ private void transformMethod(Handle handle) {
+ super.visitTypeInsn(Opcodes.NEW, Type.getInternalName(MethodReference.class));
+ super.visitInsn(Opcodes.DUP);
+
+ Type ownerType = Type.getType("L" + handle.getOwner() + ";");
+ super.visitLdcInsn(ownerType);
+ super.visitLdcInsn(handle.getName());
+
+ Type[] argTypes = Type.getArgumentTypes(handle.getDesc());
+ Type resultType = Type.getReturnType(handle.getDesc());
+ super.visitIntInsn(Opcodes.SIPUSH, argTypes.length + 1);
+ super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class");
+ for (int i = 0; i < argTypes.length; ++i) {
+ super.visitInsn(Opcodes.DUP);
+ super.visitIntInsn(Opcodes.SIPUSH, i);
+ emitClassLiteral(argTypes[i]);
+ super.visitInsn(Opcodes.AASTORE);
+ }
+ super.visitInsn(Opcodes.DUP);
+ super.visitIntInsn(Opcodes.SIPUSH, argTypes.length);
+ emitClassLiteral(resultType);
+ super.visitInsn(Opcodes.AASTORE);
+
+ super.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(MethodReference.class),
+ "", "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)V", false);
+ }
+
+ private void emitClassLiteral(Type type) {
+ switch (type.getSort()) {
+ case Type.BOOLEAN:
+ super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;");
+ break;
+ case Type.BYTE:
+ super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Byte", "TYPE", "Ljava/lang/Class;");
+ break;
+ case Type.SHORT:
+ super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Short", "TYPE", "Ljava/lang/Class;");
+ break;
+ case Type.CHAR:
+ super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Character", "TYPE", "Ljava/lang/Class;");
+ break;
+ case Type.INT:
+ super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Integer", "TYPE", "Ljava/lang/Class;");
+ break;
+ case Type.LONG:
+ super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Long", "TYPE", "Ljava/lang/Class;");
+ break;
+ case Type.FLOAT:
+ super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Float", "TYPE", "Ljava/lang/Class;");
+ break;
+ case Type.DOUBLE:
+ super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Double", "TYPE", "Ljava/lang/Class;");
+ break;
+ case Type.VOID:
+ super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
+ break;
+ default:
+ super.visitLdcInsn(type);
+ }
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ if (opcode == Opcodes.INVOKESTATIC && owner.equals(Type.getInternalName(Metaprogramming.class))) {
+ owner = Type.getInternalName(MetaprogrammingImpl.class);
+ }
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
+ }
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ super.visitMaxs(instrumented ? maxStack + 9 : maxStack, maxLocals);
+ }
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/RuntimeHelper.java b/core/src/main/java/org/teavm/metaprogramming/impl/RuntimeHelper.java
new file mode 100644
index 000000000..a6e86f292
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/RuntimeHelper.java
@@ -0,0 +1,48 @@
+/*
+ * 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.impl;
+
+import java.util.List;
+
+public class RuntimeHelper {
+ private RuntimeHelper() {
+ }
+
+ public static List add(int value, List args) {
+ args.add(new CapturedValue(value, true));
+ return args;
+ }
+
+ public static List add(long value, List args) {
+ args.add(new CapturedValue(value, true));
+ return args;
+ }
+
+ public static List add(float value, List args) {
+ args.add(new CapturedValue(value, true));
+ return args;
+ }
+
+ public static List add(double value, List args) {
+ args.add(new CapturedValue(value, true));
+ return args;
+ }
+
+ public static List add(Object value, List args) {
+ args.add(new CapturedValue(value, false));
+ return args;
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/UsageGenerator.java b/core/src/main/java/org/teavm/metaprogramming/impl/UsageGenerator.java
new file mode 100644
index 000000000..0fbe5347a
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/UsageGenerator.java
@@ -0,0 +1,293 @@
+/*
+ * 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.impl;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.WeakHashMap;
+import org.teavm.dependency.DependencyAgent;
+import org.teavm.dependency.DependencyType;
+import org.teavm.dependency.MethodDependency;
+import org.teavm.diagnostics.Diagnostics;
+import org.teavm.metaprogramming.CompileTime;
+import org.teavm.metaprogramming.impl.model.MethodModel;
+import org.teavm.model.AccessLevel;
+import org.teavm.model.BasicBlock;
+import org.teavm.model.CallLocation;
+import org.teavm.model.ClassHolder;
+import org.teavm.model.ElementModifier;
+import org.teavm.model.MethodHolder;
+import org.teavm.model.MethodReference;
+import org.teavm.model.Program;
+import org.teavm.model.ValueType;
+import org.teavm.model.Variable;
+import org.teavm.model.instructions.InvocationType;
+import org.teavm.model.instructions.InvokeInstruction;
+
+public class UsageGenerator {
+ private static Map suffixGenerator = new WeakHashMap<>();
+ DependencyAgent agent;
+ MethodModel model;
+ MethodDependency methodDep;
+ CallLocation location;
+ Diagnostics diagnostics;
+ Method proxyMethod;
+ private MetaprogrammingClassLoader classLoader;
+ private boolean annotationErrorReported;
+
+ UsageGenerator(DependencyAgent agent, MethodModel model, MethodDependency methodDep, CallLocation location,
+ MetaprogrammingClassLoader classLoader) {
+ this.agent = agent;
+ this.diagnostics = agent.getDiagnostics();
+ this.model = model;
+ this.methodDep = methodDep;
+ this.location = location;
+ this.classLoader = classLoader;
+ }
+
+ public void installProxyEmitter() {
+ Diagnostics diagnostics = agent.getDiagnostics();
+
+ MethodDependency getClassDep = agent.linkMethod(new MethodReference(Object.class, "getClass", Class.class),
+ location);
+ getClassDep.getThrown().connect(methodDep.getThrown());
+
+ try {
+ proxyMethod = getJavaMethod(classLoader, model.getMetaMethod());
+ proxyMethod.setAccessible(true);
+ } catch (ReflectiveOperationException e) {
+ StringWriter stackTraceWriter = new StringWriter();
+ e.printStackTrace(new PrintWriter(stackTraceWriter));
+ diagnostics.error(location, "Error accessing proxy method {{m0}}: " + stackTraceWriter.getBuffer(),
+ model.getMetaMethod());
+ return;
+ }
+
+ if (model.getClassParameterIndex() >= 0) {
+ int index = (model.isStatic() ? 0 : 1) + model.getClassParameterIndex();
+ methodDep.getVariable(index).addConsumer(type -> consumeType(type, getClassDep));
+ } else {
+ emitPermutation(null, getClassDep);
+ }
+
+ installAdditionalDependencies(getClassDep);
+ }
+
+ private void installAdditionalDependencies(MethodDependency getClassDep) {
+ MethodDependency nameDep = agent.linkMethod(new MethodReference(Class.class, "getName", String.class),
+ location);
+ getClassDep.getResult().connect(nameDep.getVariable(0));
+ nameDep.getThrown().connect(methodDep.getThrown());
+ nameDep.use();
+
+ MethodDependency equalsDep = agent.linkMethod(new MethodReference(String.class, "equals", Object.class,
+ boolean.class), location);
+ nameDep.getResult().connect(equalsDep.getVariable(0));
+ equalsDep.getVariable(1).propagate(agent.getType("java.lang.String"));
+ equalsDep.getThrown().connect(methodDep.getThrown());
+ equalsDep.use();
+
+ MethodDependency hashCodeDep = agent.linkMethod(new MethodReference(String.class, "hashCode", int.class),
+ location);
+ nameDep.getResult().connect(hashCodeDep.getVariable(0));
+ hashCodeDep.getThrown().connect(methodDep.getThrown());
+ hashCodeDep.use();
+ }
+
+ private void consumeType(DependencyType type, MethodDependency getClassDep) {
+ emitPermutation(findClass(type.getName()), getClassDep);
+ }
+
+ private void emitPermutation(ValueType type, MethodDependency getClassDep) {
+ if (!classLoader.isCompileTimeClass(model.getMetaMethod().getClassName()) && !annotationErrorReported) {
+ annotationErrorReported = true;
+ diagnostics.error(location, "Metaprogramming method should be within class marked with "
+ + "{{c0}} annotation", CompileTime.class.getName());
+ return;
+ }
+
+ MethodReference implRef = model.getUsages().get(type);
+ if (implRef != null) {
+ return;
+ }
+
+ implRef = buildMethodReference(type);
+ model.getUsages().put(type, implRef);
+ //emitter = new EmitterImpl<>(emitterContext, model.getProxyMethod(), model.getMethod().getReturnType());
+
+ /*for (int i = 0; i <= model.getParameters().size(); ++i) {
+ emitter.generator.getProgram().createVariable();
+ }
+ */
+
+ Object[] proxyArgs = new Object[model.getMetaParameterCount()];
+ for (int i = 0; i < proxyArgs.length; ++i) {
+ if (i == model.getMetaClassParameterIndex()) {
+ proxyArgs[i] = MetaprogrammingImpl.findClass(type);
+ } else {
+ proxyArgs[i] = new ValueImpl<>(null, null, model.getMetaParameterType(i));
+ }
+ }
+
+ try {
+ proxyMethod.invoke(null, proxyArgs);
+ //emitter.close();
+ Program program = null; //emitter.generator.getProgram();
+ //new BoxingEliminator().optimize(program);
+
+ ClassHolder cls = new ClassHolder(implRef.getClassName());
+ cls.setLevel(AccessLevel.PUBLIC);
+ cls.setParent("java.lang.Object");
+
+ MethodHolder method = new MethodHolder(implRef.getDescriptor());
+ method.setLevel(AccessLevel.PUBLIC);
+ method.getModifiers().add(ElementModifier.STATIC);
+ method.setProgram(program);
+ cls.addMethod(method);
+
+ agent.submitClass(cls);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ StringWriter writer = new StringWriter();
+ e.printStackTrace(new PrintWriter(writer));
+ diagnostics.error(location, "Error calling proxy method {{m0}}: " + writer.toString(),
+ model.getMetaMethod());
+ }
+
+ MethodDependency implMethod = agent.linkMethod(implRef, location);
+ for (int i = 0; i < implRef.parameterCount(); ++i) {
+ methodDep.getVariable(i + 1).connect(implMethod.getVariable(i + 1));
+ }
+
+ if (model.getClassParameterIndex() >= 0) {
+ implMethod.getVariable(model.getClassParameterIndex() + 1).connect(getClassDep.getVariable(0));
+ }
+
+ if (implMethod.getResult() != null) {
+ implMethod.getResult().connect(methodDep.getResult());
+ }
+ implMethod.getThrown().connect(methodDep.getThrown());
+ implMethod.use();
+
+ agent.linkClass(implRef.getClassName(), location);
+ }
+
+ private ValueType findClass(String name) {
+ // TODO: dirty hack due to bugs somewhere in TeaVM
+ if (name.startsWith("[")) {
+ ValueType type = ValueType.parseIfPossible(name);
+ if (type != null) {
+ return type;
+ }
+
+ int degree = 0;
+ while (name.charAt(degree) == '[') {
+ ++degree;
+ }
+ type = ValueType.object(name.substring(degree));
+
+ while (degree-- > 0) {
+ type = ValueType.arrayOf(type);
+ }
+ return type;
+ } else {
+ return ValueType.object(name);
+ }
+ }
+
+ private MethodReference buildMethodReference(ValueType type) {
+ if (model.getClassParameterIndex() < 0) {
+ return new MethodReference(model.getMethod().getClassName() + "$PROXY$" + getSuffix(),
+ model.getMethod().getDescriptor());
+ }
+
+ int i = 0;
+ ValueType[] signature = new ValueType[model.getMetaParameterCount()];
+ for (i = 0; i < signature.length; ++i) {
+ signature[i] = model.getMetaParameterType(i);
+ }
+ signature[i] = model.getMethod().getReturnType();
+
+ return new MethodReference(model.getMethod().getClassName() + "$PROXY$" + getSuffix(),
+ model.getMethod().getName(), signature);
+ }
+
+ private int getSuffix() {
+ int suffix = suffixGenerator.getOrDefault(agent, 0);
+ suffixGenerator.put(agent, suffix + 1);
+ return suffix;
+ }
+
+ private Method getJavaMethod(ClassLoader classLoader, MethodReference ref) throws ReflectiveOperationException {
+ Class> cls = Class.forName(ref.getClassName(), true, classLoader);
+ Class>[] parameterTypes = new Class>[ref.parameterCount()];
+ for (int i = 0; i < parameterTypes.length; ++i) {
+ parameterTypes[i] = getJavaType(classLoader, ref.parameterType(i));
+ }
+ return cls.getDeclaredMethod(ref.getName(), parameterTypes);
+ }
+
+ private Class> getJavaType(ClassLoader classLoader, ValueType type) throws ReflectiveOperationException {
+ if (type instanceof ValueType.Primitive) {
+ switch (((ValueType.Primitive) type).getKind()) {
+ case BOOLEAN:
+ return boolean.class;
+ case BYTE:
+ return byte.class;
+ case SHORT:
+ return short.class;
+ case CHARACTER:
+ return char.class;
+ case INTEGER:
+ return int.class;
+ case LONG:
+ return long.class;
+ case FLOAT:
+ return float.class;
+ case DOUBLE:
+ return double.class;
+ }
+ } else if (type instanceof ValueType.Array) {
+ Class> componentType = getJavaType(classLoader, ((ValueType.Array) type).getItemType());
+ return Array.newInstance(componentType, 0).getClass();
+ } else if (type instanceof ValueType.Object) {
+ String className = ((ValueType.Object) type).getClassName();
+ return Class.forName(className, true, classLoader);
+ } else if (type instanceof ValueType.Void) {
+ return void.class;
+ }
+ throw new AssertionError("Don't know how to map type: " + type);
+ }
+
+
+ private Variable box(Variable var, Class> boxed, Class> primitive) {
+ Program program = null; //emitter.generator.getProgram();
+ BasicBlock block = program.basicBlockAt(0);
+
+ InvokeInstruction insn = new InvokeInstruction();
+ insn.setType(InvocationType.SPECIAL);
+ insn.setMethod(new MethodReference(boxed, "valueOf", primitive, boxed));
+ insn.getArguments().add(var);
+ var = program.createVariable();
+ insn.setReceiver(var);
+
+ block.getInstructions().add(insn);
+ return var;
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/ValueImpl.java b/core/src/main/java/org/teavm/metaprogramming/impl/ValueImpl.java
new file mode 100644
index 000000000..f0b925886
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/ValueImpl.java
@@ -0,0 +1,37 @@
+/*
+ * 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.impl;
+
+import org.teavm.metaprogramming.Value;
+import org.teavm.model.ValueType;
+import org.teavm.model.Variable;
+
+public class ValueImpl implements Value {
+ Variable innerValue;
+ VariableContext context;
+ ValueType type;
+
+ public ValueImpl(Variable innerValue, VariableContext context, ValueType type) {
+ this.innerValue = innerValue;
+ this.context = context;
+ this.type = type;
+ }
+
+ @Override
+ public T get() {
+ throw new IllegalStateException("Can only read this value in emitter domain");
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/VariableContext.java b/core/src/main/java/org/teavm/metaprogramming/impl/VariableContext.java
new file mode 100644
index 000000000..eb0a3eab1
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/VariableContext.java
@@ -0,0 +1,33 @@
+/*
+ * 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.impl;
+
+import org.teavm.model.CallLocation;
+import org.teavm.model.Variable;
+
+public abstract class VariableContext {
+ private VariableContext parent;
+
+ public VariableContext(VariableContext parent) {
+ this.parent = parent;
+ }
+
+ public VariableContext getParent() {
+ return parent;
+ }
+
+ public abstract Variable emitVariable(ValueImpl> value, CallLocation location);
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/model/MethodDescriber.java b/core/src/main/java/org/teavm/metaprogramming/impl/model/MethodDescriber.java
new file mode 100644
index 000000000..ebb5ea9cf
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/model/MethodDescriber.java
@@ -0,0 +1,112 @@
+/*
+ * 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.impl.model;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.teavm.diagnostics.Diagnostics;
+import org.teavm.metaprogramming.Meta;
+import org.teavm.metaprogramming.ReflectClass;
+import org.teavm.metaprogramming.Value;
+import org.teavm.model.CallLocation;
+import org.teavm.model.ClassReader;
+import org.teavm.model.ClassReaderSource;
+import org.teavm.model.ElementModifier;
+import org.teavm.model.MethodReader;
+import org.teavm.model.MethodReference;
+import org.teavm.model.ValueType;
+
+public class MethodDescriber {
+ private Diagnostics diagnostics;
+ private ClassReaderSource classSource;
+ private Map cache = new HashMap<>();
+
+ public MethodDescriber(Diagnostics diagnostics, ClassReaderSource classSource) {
+ this.diagnostics = diagnostics;
+ this.classSource = classSource;
+ }
+
+ public MethodModel getMethod(MethodReference method) {
+ return cache.computeIfAbsent(method, this::describeMethod);
+ }
+
+ public MethodModel getKnownMethod(MethodReference method) {
+ return cache.get(method);
+ }
+
+ public Iterable getKnownMethods() {
+ return cache.values();
+ }
+
+ private MethodModel describeMethod(MethodReference methodRef) {
+ MethodReader method = classSource.resolve(methodRef);
+ if (method == null) {
+ return null;
+ }
+ if (method.getAnnotations().get(Meta.class.getName()) == null) {
+ return null;
+ }
+ CallLocation location = new CallLocation(methodRef);
+
+ MethodModel proxyMethod = findMetaMethod(method);
+ if (proxyMethod == null) {
+ diagnostics.error(location, "Corresponding meta method was not found");
+ return null;
+ }
+
+ return proxyMethod;
+ }
+
+ private MethodModel findMetaMethod(MethodReader method) {
+ ClassReader cls = classSource.get(method.getOwnerName());
+ for (MethodReader meta : cls.getMethods()) {
+ if (meta == method
+ || !meta.hasModifier(ElementModifier.STATIC)
+ || !meta.getName().equals(method.getName())
+ || meta.getResultType() != ValueType.VOID
+ || meta.parameterCount() != method.parameterCount() + 1) {
+ continue;
+ }
+
+ int paramOffset = 0;
+ boolean isStatic = method.hasModifier(ElementModifier.STATIC);
+ if (!isStatic) {
+ if (meta.parameterCount() == 0 || meta.parameterType(0).isObject(Value.class)) {
+ return null;
+ }
+ paramOffset++;
+ }
+
+ int classParamIndex = -1;
+ for (int i = 0; i < method.parameterCount(); ++i) {
+ ValueType proxyParam = meta.parameterType(i + paramOffset);
+ ValueType param = method.parameterType(i);
+ if (proxyParam.isObject(ReflectClass.class)) {
+ if (classParamIndex == -1) {
+ classParamIndex = i;
+ } else {
+ return null;
+ }
+ } else if (!proxyParam.isObject(Value.class)) {
+ return null;
+ }
+ }
+
+ return new MethodModel(method.getReference(), meta.getReference(), classParamIndex, isStatic);
+ }
+ return null;
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/model/MethodModel.java b/core/src/main/java/org/teavm/metaprogramming/impl/model/MethodModel.java
new file mode 100644
index 000000000..a7d856905
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/model/MethodModel.java
@@ -0,0 +1,79 @@
+/*
+ * 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.impl.model;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.teavm.model.MethodReference;
+import org.teavm.model.ValueType;
+
+public class MethodModel {
+ private MethodReference method;
+ private MethodReference metaMethod;
+ private int classParameterIndex;
+ private boolean isStatic;
+ private Map usages = new HashMap<>();
+
+ MethodModel(MethodReference method, MethodReference metaMethod, int classParameterIndex, boolean isStatic) {
+ this.method = method;
+ this.metaMethod = metaMethod;
+ this.classParameterIndex = classParameterIndex;
+ this.isStatic = isStatic;
+ }
+
+ public MethodReference getMethod() {
+ return method;
+ }
+
+ public MethodReference getMetaMethod() {
+ return metaMethod;
+ }
+
+ public boolean isStatic() {
+ return isStatic;
+ }
+
+ public int getClassParameterIndex() {
+ return classParameterIndex;
+ }
+
+ public Map getUsages() {
+ return usages;
+ }
+
+ public int getMetaParameterCount() {
+ return (isStatic ? 0 : 1) + method.parameterCount();
+ }
+
+ public ValueType getMetaParameterType(int index) {
+ if (!isStatic) {
+ if (index == 0) {
+ return ValueType.object(method.getClassName());
+ } else {
+ --index;
+ }
+ }
+ return method.parameterType(index);
+ }
+
+ public int getMetaClassParameterIndex() {
+ return classParameterIndex >= 0 ? mapParameterIndex(classParameterIndex) : -1;
+ }
+
+ public int mapParameterIndex(int index) {
+ return isStatic ? index : index + 1;
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/reflect/AnnotationProxy.java b/core/src/main/java/org/teavm/metaprogramming/impl/reflect/AnnotationProxy.java
new file mode 100644
index 000000000..dcbceb289
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/reflect/AnnotationProxy.java
@@ -0,0 +1,153 @@
+/*
+ * 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.impl.reflect;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.teavm.model.AnnotationReader;
+import org.teavm.model.AnnotationValue;
+import org.teavm.model.ClassReader;
+import org.teavm.model.ClassReaderSource;
+import org.teavm.model.FieldReference;
+import org.teavm.model.MethodDescriptor;
+import org.teavm.model.MethodReader;
+import org.teavm.model.ValueType;
+
+class AnnotationProxy implements InvocationHandler {
+ private ClassLoader classLoader;
+ private ClassReaderSource classSource;
+ private AnnotationReader reader;
+ private Class> annotationType;
+ private Map cache = new HashMap<>();
+
+ AnnotationProxy(ClassLoader classLoader, ClassReaderSource classSource, AnnotationReader reader,
+ Class> annotationType) {
+ this.classLoader = classLoader;
+ this.classSource = classSource;
+ this.reader = reader;
+ this.annotationType = annotationType;
+ }
+
+ AnnotationProxy(AnnotationReader reader) {
+ this.reader = reader;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (method.getName().equals("annotationType")) {
+ return annotationType;
+ } else {
+ ClassReader cls = classSource.get(reader.getType());
+ return cache.computeIfAbsent(method.getName(), name -> {
+ MethodDescriptor desc = new MethodDescriptor(name, ValueType.parse(method.getReturnType()));
+ MethodReader methodReader = cls.getMethod(desc);
+ AnnotationValue value = reader.getValue(name);
+ if (value == null) {
+ value = methodReader.getAnnotationDefault();
+ }
+ try {
+ return convertValue(value, desc.getResultType());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+ }
+
+ private Object convertValue(AnnotationValue value, ValueType type) throws Exception {
+ if (type instanceof ValueType.Primitive) {
+ switch (((ValueType.Primitive) type).getKind()) {
+ case BOOLEAN:
+ return value.getBoolean();
+ case BYTE:
+ return value.getByte();
+ case SHORT:
+ return value.getShort();
+ case INTEGER:
+ return value.getInt();
+ case LONG:
+ return value.getLong();
+ case FLOAT:
+ return value.getFloat();
+ case DOUBLE:
+ return value.getDouble();
+ case CHARACTER:
+ break;
+ }
+ } else if (type.isObject(String.class)) {
+ return value.getString();
+ } else if (type instanceof ValueType.Array) {
+ List array = value.getList();
+ ValueType itemType = ((ValueType.Array) type).getItemType();
+ Class> componentType = convertClass(itemType);
+ Object result = Array.newInstance(componentType, array.size());
+ for (int i = 0; i < array.size(); ++i) {
+ Array.set(result, i, convertValue(array.get(i), itemType));
+ }
+ return result;
+ } else if (type.isObject(Class.class)) {
+ return convertClass(value.getJavaClass());
+ } else if (classSource.isSuperType(ValueType.parse(Enum.class), type).orElse(false)) {
+ FieldReference fieldRef = value.getEnumValue();
+ Class> enumClass = Class.forName(fieldRef.getClassName(), true, classLoader);
+ return enumClass.getField(fieldRef.getFieldName()).get(null);
+ } else if (classSource.isSuperType(ValueType.parse(Annotation.class), type).orElse(false)) {
+ Class> annotType = convertClass(type);
+ AnnotationProxy handler = new AnnotationProxy(classLoader, classSource, value.getAnnotation(), annotType);
+ return Proxy.newProxyInstance(classLoader, new Class>[] { annotType }, handler);
+ }
+
+ throw new AssertionError("Unsupported type: " + type);
+ }
+
+ private Class> convertClass(ValueType type) throws Exception {
+ if (type instanceof ValueType.Primitive) {
+ switch (((ValueType.Primitive) type).getKind()) {
+ case BOOLEAN:
+ return boolean.class;
+ case BYTE:
+ return byte.class;
+ case SHORT:
+ return short.class;
+ case CHARACTER:
+ return char.class;
+ case INTEGER:
+ return int.class;
+ case LONG:
+ return long.class;
+ case FLOAT:
+ return float.class;
+ case DOUBLE:
+ return double.class;
+ }
+ } else if (type instanceof ValueType.Array) {
+ Class> componentType = convertClass(((ValueType.Array) type).getItemType());
+ return Array.newInstance(componentType, 0).getClass();
+ } else if (type == ValueType.VOID) {
+ return void.class;
+ } else if (type instanceof ValueType.Object) {
+ String name = ((ValueType.Object) type).getClassName();
+ return Class.forName(name, true, classLoader);
+ }
+ throw new AssertionError("Unsupported type: " + type);
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectAnnotatedElementImpl.java b/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectAnnotatedElementImpl.java
new file mode 100644
index 000000000..fa4876920
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectAnnotatedElementImpl.java
@@ -0,0 +1,54 @@
+/*
+ * 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.impl.reflect;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Proxy;
+import java.util.HashMap;
+import java.util.Map;
+import org.teavm.metaprogramming.reflect.ReflectAnnotatedElement;
+import org.teavm.model.AnnotationContainerReader;
+import org.teavm.model.AnnotationReader;
+
+public class ReflectAnnotatedElementImpl implements ReflectAnnotatedElement {
+ private ReflectContext context;
+ private AnnotationContainerReader annotationContainer;
+ private Map, Annotation> annotations = new HashMap<>();
+
+ public ReflectAnnotatedElementImpl(ReflectContext context, AnnotationContainerReader annotationContainer) {
+ this.context = context;
+ this.annotationContainer = annotationContainer;
+ }
+
+ @Override
+ public S getAnnotation(Class type) {
+ @SuppressWarnings("unchecked")
+ S result = (S) annotations.computeIfAbsent(type, t -> {
+ if (annotationContainer == null) {
+ return null;
+ }
+ AnnotationReader annot = annotationContainer.get(t.getName());
+ if (annot == null) {
+ return null;
+ }
+
+ AnnotationProxy handler = new AnnotationProxy(context.getClassLoader(), context.getClassSource(),
+ annot, t);
+ return (Annotation) Proxy.newProxyInstance(context.getClassLoader(), new Class>[] { t }, handler);
+ });
+ return result;
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectClassImpl.java b/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectClassImpl.java
new file mode 100644
index 000000000..dc3bef838
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectClassImpl.java
@@ -0,0 +1,395 @@
+/*
+ * 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.impl.reflect;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.teavm.metaprogramming.ReflectClass;
+import org.teavm.metaprogramming.reflect.ReflectField;
+import org.teavm.metaprogramming.reflect.ReflectMethod;
+import org.teavm.model.AccessLevel;
+import org.teavm.model.ClassReader;
+import org.teavm.model.ElementModifier;
+import org.teavm.model.FieldReader;
+import org.teavm.model.MethodDescriptor;
+import org.teavm.model.MethodReader;
+import org.teavm.model.ValueType;
+
+public class ReflectClassImpl implements ReflectClass {
+ public final ValueType type;
+ private ReflectContext context;
+ private ClassReader classReader;
+ private boolean resolved;
+ private Class> cls;
+ private Map declaredFields = new HashMap<>();
+ private ReflectField[] fieldsCache;
+ private Map methods = new HashMap<>();
+ private Map declaredMethods = new HashMap<>();
+ private ReflectMethod[] methodsCache;
+ private ReflectAnnotatedElementImpl annotations;
+
+ ReflectClassImpl(ValueType type, ReflectContext context) {
+ this.type = type;
+ this.context = context;
+ }
+
+ public ReflectContext getReflectContext() {
+ return context;
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return type instanceof ValueType.Primitive;
+ }
+
+ @Override
+ public boolean isInterface() {
+ resolve();
+ return classReader != null && classReader.readModifiers().contains(ElementModifier.INTERFACE);
+ }
+
+ @Override
+ public boolean isArray() {
+ return type instanceof ValueType.Array;
+ }
+
+ @Override
+ public boolean isAnnotation() {
+ resolve();
+ return classReader != null && classReader.readModifiers().contains(ElementModifier.ANNOTATION);
+ }
+
+ @Override
+ public boolean isEnum() {
+ resolve();
+ return classReader != null && classReader.readModifiers().contains(ElementModifier.ENUM);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T[] getEnumConstants() {
+ resolve();
+ if (classReader == null) {
+ return null;
+ }
+ if (cls == null) {
+ try {
+ cls = Class.forName(classReader.getName(), true, context.getClassLoader());
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+ return (T[]) cls.getEnumConstants();
+ }
+
+ @Override
+ public int getModifiers() {
+ resolve();
+ if (classReader == null) {
+ return 0;
+ }
+ return ReflectContext.getModifiers(classReader);
+ }
+
+ @Override
+ public ReflectClass> getComponentType() {
+ if (!(type instanceof ValueType.Array)) {
+ return null;
+ }
+ ValueType componentType = ((ValueType.Array) type).getItemType();
+ return context.getClass(componentType);
+ }
+
+ @Override
+ public String getName() {
+ if (type instanceof ValueType.Object) {
+ return ((ValueType.Object) type).getClassName();
+ } else if (type instanceof ValueType.Void) {
+ return "void";
+ } else if (type instanceof ValueType.Primitive) {
+ switch (((ValueType.Primitive) type).getKind()) {
+ case BOOLEAN:
+ return "boolean";
+ case BYTE:
+ return "byte";
+ case SHORT:
+ return "short";
+ case CHARACTER:
+ return "char";
+ case INTEGER:
+ return "int";
+ case LONG:
+ return "long";
+ case FLOAT:
+ return "float";
+ case DOUBLE:
+ return "double";
+ default:
+ return "";
+ }
+ } else if (type instanceof ValueType.Array) {
+ return type.toString().replace('/', '.');
+ } else {
+ return "";
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public ReflectClass super T> getSuperclass() {
+ resolve();
+ if (classReader == null || classReader.getParent() == null
+ || classReader.getName().equals(classReader.getParent())) {
+ return null;
+ }
+ return (ReflectClass super T>) context.getClass(new ValueType.Object(classReader.getParent()));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public ReflectClass super T>[] getInterfaces() {
+ resolve();
+ if (classReader == null) {
+ return (ReflectClass super T>[]) Array.newInstance(ReflectClassImpl.class, 0);
+ }
+ return classReader.getInterfaces().stream()
+ .map(iface -> context.getClass(new ValueType.Object(iface)))
+ .toArray(sz -> (ReflectClass super T>[]) Array.newInstance(ReflectClassImpl.class, sz));
+ }
+
+ @Override
+ public boolean isInstance(Object obj) {
+ throw new IllegalStateException("Don't call this method from compile domain");
+ }
+
+ @Override
+ public T cast(Object obj) {
+ throw new IllegalStateException("Don't call this method from compile domain");
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public ReflectClass asSubclass(Class cls) {
+ ReflectClass reflectClass = context.findClass(cls);
+ if (!reflectClass.isAssignableFrom(this)) {
+ throw new IllegalArgumentException(cls.getName() + " is not subclass of " + getName());
+ }
+ return (ReflectClass) this;
+ }
+
+ @Override
+ public ReflectMethod[] getDeclaredMethods() {
+ resolve();
+ if (classReader == null) {
+ return new ReflectMethod[0];
+ }
+ return classReader.getMethods().stream()
+ .filter(method -> !method.getName().equals(""))
+ .map(method -> getDeclaredMethod(method.getDescriptor()))
+ .toArray(ReflectMethod[]::new);
+ }
+
+ @Override
+ public ReflectMethod[] getMethods() {
+ resolve();
+ if (classReader == null) {
+ return new ReflectMethod[0];
+ }
+ if (methodsCache == null) {
+ Set visited = new HashSet<>();
+ methodsCache = context.getClassSource().getAncestors(classReader.getName())
+ .flatMap(cls -> cls.getMethods().stream())
+ .filter(method -> !method.getName().equals(""))
+ .filter(method -> visited.add(method.getDescriptor().toString()))
+ .map(method -> context.getClass(ValueType.object(method.getOwnerName()))
+ .getDeclaredMethod(method.getDescriptor()))
+ .filter(Objects::nonNull)
+ .toArray(ReflectMethod[]::new);
+ }
+ return methodsCache.clone();
+ }
+
+ @Override
+ public ReflectMethod getDeclaredMethod(String name, ReflectClass>... parameterTypes) {
+ resolve();
+ if (classReader == null) {
+ return null;
+ }
+
+ ValueType[] internalParameterTypes = Arrays.stream(parameterTypes)
+ .map(type -> ((ReflectClassImpl>) type).type)
+ .toArray(ValueType[]::new);
+ String key = name + "(" + ValueType.manyToString(internalParameterTypes) + ")";
+ return declaredMethods.computeIfAbsent(key, k -> {
+ MethodReader candidate = null;
+ for (MethodReader method : classReader.getMethods()) {
+ if (!method.getName().equals(name)) {
+ continue;
+ }
+ if (!Arrays.equals(method.getParameterTypes(), internalParameterTypes)) {
+ continue;
+ }
+ if (candidate == null) {
+ candidate = method;
+ } else {
+ boolean moreSpecial = context.getClassSource()
+ .isSuperType(candidate.getResultType(), method.getResultType())
+ .orElse(false);
+ if (moreSpecial) {
+ candidate = method;
+ }
+ }
+ }
+
+ return candidate != null ? getDeclaredMethod(candidate.getDescriptor()) : null;
+ });
+ }
+
+ public ReflectMethodImpl getDeclaredMethod(MethodDescriptor method) {
+ resolve();
+ return methods.computeIfAbsent(method, m -> {
+ MethodReader methodReader = classReader.getMethod(m);
+ return methodReader != null ? new ReflectMethodImpl(this, methodReader) : null;
+ });
+ }
+
+ @Override
+ public ReflectMethod getMethod(String name, ReflectClass>... parameterTypes) {
+ resolve();
+ if (classReader == null) {
+ return null;
+ }
+
+ Iterable ancestors = () -> context.getClassSource().getAncestors(classReader.getName())
+ .iterator();
+ for (ClassReader cls : ancestors) {
+ ReflectClassImpl> reflectClass = context.getClass(ValueType.object(cls.getName()));
+ ReflectMethod method = reflectClass.getDeclaredMethod(name, parameterTypes);
+ if (method != null && Modifier.isPublic(method.getModifiers())) {
+ return method;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public ReflectField[] getDeclaredFields() {
+ resolve();
+ if (classReader == null) {
+ return new ReflectField[0];
+ }
+ return classReader.getFields().stream()
+ .map(fld -> getDeclaredField(fld.getName()))
+ .toArray(ReflectField[]::new);
+ }
+
+ @Override
+ public ReflectField[] getFields() {
+ if (fieldsCache == null) {
+ Set visited = new HashSet<>();
+ fieldsCache = context.getClassSource()
+ .getAncestors(classReader.getName())
+ .flatMap(cls -> cls.getFields().stream().filter(fld -> fld.getLevel() == AccessLevel.PUBLIC))
+ .filter(fld -> visited.add(fld.getName()))
+ .map(fld -> context.getClass(ValueType.object(fld.getOwnerName()))
+ .getDeclaredField(fld.getName()))
+ .toArray(ReflectField[]::new);
+ }
+ return fieldsCache.clone();
+ }
+
+ @Override
+ public ReflectField getDeclaredField(String name) {
+ resolve();
+ return declaredFields.computeIfAbsent(name, n -> {
+ FieldReader fld = classReader.getField(n);
+ return fld != null ? new ReflectFieldImpl(this, fld) : null;
+ });
+ }
+
+ @Override
+ public ReflectField getField(String name) {
+ resolve();
+ if (classReader == null) {
+ return null;
+ }
+ FieldReader fieldReader = classReader.getField(name);
+ return fieldReader != null && fieldReader.getLevel() == AccessLevel.PUBLIC
+ ? getDeclaredField(name)
+ : null;
+ }
+
+ @Override
+ public S getAnnotation(Class type) {
+ resolve();
+ if (classReader == null) {
+ return null;
+ }
+ if (annotations == null) {
+ annotations = new ReflectAnnotatedElementImpl(context, classReader.getAnnotations());
+ }
+ return annotations.getAnnotation(type);
+ }
+
+ private void resolve() {
+ if (resolved) {
+ return;
+ }
+ resolved = true;
+ if (!(type instanceof ValueType.Object)) {
+ return;
+ }
+
+ String className = ((ValueType.Object) type).getClassName();
+ classReader = context.getClassSource().get(className);
+ }
+
+ @Override
+ public String toString() {
+ if (isArray()) {
+ return getComponentType().toString() + "[]";
+ } else {
+ return getName();
+ }
+ }
+
+ @Override
+ public T[] createArray(int size) {
+ throw new IllegalStateException("Don't call this method from compile domain");
+ }
+
+ @Override
+ public T getArrayElement(Object array, int index) {
+ throw new IllegalStateException("Don't call this method from compile domain");
+ }
+
+ @Override
+ public int getArrayLength(Object array) {
+ throw new IllegalStateException("Don't call this method from compile domain");
+ }
+
+ @Override
+ public Class asJavaClass() {
+ throw new IllegalStateException("Don't call this method from compile domain");
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectContext.java b/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectContext.java
new file mode 100644
index 000000000..5901257b0
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectContext.java
@@ -0,0 +1,110 @@
+/*
+ * 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.impl.reflect;
+
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.teavm.model.ClassReaderSource;
+import org.teavm.model.ElementModifier;
+import org.teavm.model.ElementReader;
+import org.teavm.model.ValueType;
+
+/**
+ *
+ * @author Alexey Andreev
+ */
+public class ReflectContext {
+ private ClassReaderSource classSource;
+ private Map> classes = new HashMap<>();
+ private ClassLoader classLoader;
+
+ public ReflectContext(ClassReaderSource classSource, ClassLoader classLoader) {
+ this.classSource = classSource;
+ this.classLoader = classLoader;
+ }
+
+ public ClassLoader getClassLoader() {
+ return classLoader;
+ }
+
+ public ClassReaderSource getClassSource() {
+ return classSource;
+ }
+
+ public ReflectClassImpl> getClass(ValueType type) {
+ return classes.computeIfAbsent(type, t -> new ReflectClassImpl<>(type, this));
+ }
+
+ @SuppressWarnings("unchecked")
+ public ReflectClassImpl findClass(Class cls) {
+ return (ReflectClassImpl) getClass(ValueType.parse(cls));
+ }
+
+ public ReflectClassImpl> findClass(String name) {
+ if (classSource.get(name) == null) {
+ return null;
+ }
+ return getClass(ValueType.object(name));
+ }
+
+ public static int getModifiers(ElementReader element) {
+ int modifiers = 0;
+ switch (element.getLevel()) {
+ case PUBLIC:
+ modifiers |= Modifier.PUBLIC;
+ break;
+ case PROTECTED:
+ modifiers |= Modifier.PROTECTED;
+ break;
+ case PRIVATE:
+ modifiers |= Modifier.PRIVATE;
+ break;
+ case PACKAGE_PRIVATE:
+ break;
+ }
+ Set modifierSet = element.readModifiers();
+ if (modifierSet.contains(ElementModifier.ABSTRACT)) {
+ modifiers |= Modifier.ABSTRACT;
+ }
+ if (modifierSet.contains(ElementModifier.FINAL)) {
+ modifiers |= Modifier.FINAL;
+ }
+ if (modifierSet.contains(ElementModifier.INTERFACE)) {
+ modifiers |= Modifier.INTERFACE;
+ }
+ if (modifierSet.contains(ElementModifier.NATIVE)) {
+ modifiers |= Modifier.NATIVE;
+ }
+ if (modifierSet.contains(ElementModifier.STATIC)) {
+ modifiers |= Modifier.STATIC;
+ }
+ if (modifierSet.contains(ElementModifier.STRICT)) {
+ modifiers |= Modifier.STRICT;
+ }
+ if (modifierSet.contains(ElementModifier.SYNCHRONIZED)) {
+ modifiers |= Modifier.SYNCHRONIZED;
+ }
+ if (modifierSet.contains(ElementModifier.TRANSIENT)) {
+ modifiers |= Modifier.TRANSIENT;
+ }
+ if (modifierSet.contains(ElementModifier.VOLATILE)) {
+ modifiers |= Modifier.VOLATILE;
+ }
+ return modifiers;
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectFieldImpl.java b/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectFieldImpl.java
new file mode 100644
index 000000000..37e20fd08
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectFieldImpl.java
@@ -0,0 +1,90 @@
+/*
+ * 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.impl.reflect;
+
+import java.lang.annotation.Annotation;
+import org.teavm.metaprogramming.ReflectClass;
+import org.teavm.metaprogramming.reflect.ReflectField;
+import org.teavm.model.ElementModifier;
+import org.teavm.model.FieldReader;
+
+/**
+ *
+ * @author Alexey Andreev
+ */
+public class ReflectFieldImpl implements ReflectField {
+ private ReflectContext context;
+ private ReflectClassImpl> declaringClass;
+ public final FieldReader field;
+ private ReflectClassImpl> type;
+ private ReflectAnnotatedElementImpl annotations;
+
+ public ReflectFieldImpl(ReflectClassImpl> declaringClass, FieldReader field) {
+ context = declaringClass.getReflectContext();
+ this.declaringClass = declaringClass;
+ this.field = field;
+ }
+
+ @Override
+ public ReflectClass> getDeclaringClass() {
+ return declaringClass;
+ }
+
+ @Override
+ public String getName() {
+ return field.getName();
+ }
+
+ @Override
+ public int getModifiers() {
+ return ReflectContext.getModifiers(field);
+ }
+
+ @Override
+ public boolean isEnumConstant() {
+ return field.readModifiers().contains(ElementModifier.ENUM);
+ }
+
+ @Override
+ public ReflectClass> getType() {
+ if (type == null) {
+ type = context.getClass(field.getType());
+ }
+ return type;
+ }
+
+ @Override
+ public Object get(Object target) {
+ throw new IllegalStateException("Don't call this method from compile domain");
+ }
+
+ @Override
+ public void set(Object target, Object value) {
+ throw new IllegalStateException("Don't call this method from compile domain");
+ }
+
+ public FieldReader getBackingField() {
+ return field;
+ }
+
+ @Override
+ public S getAnnotation(Class type) {
+ if (annotations == null) {
+ annotations = new ReflectAnnotatedElementImpl(context, field.getAnnotations());
+ }
+ return annotations.getAnnotation(type);
+ }
+}
diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectMethodImpl.java b/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectMethodImpl.java
new file mode 100644
index 000000000..8fba13ea9
--- /dev/null
+++ b/core/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectMethodImpl.java
@@ -0,0 +1,137 @@
+/*
+ * 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.impl.reflect;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.teavm.metaprogramming.ReflectClass;
+import org.teavm.metaprogramming.reflect.ReflectAnnotatedElement;
+import org.teavm.metaprogramming.reflect.ReflectMethod;
+import org.teavm.model.MethodReader;
+
+/**
+ *
+ * @author Alexey Andreev
+ */
+public class ReflectMethodImpl implements ReflectMethod {
+ private ReflectContext context;
+ private ReflectClassImpl> declaringClass;
+ public final MethodReader method;
+ private ReflectClassImpl> returnType;
+ private ReflectClass>[] parameterTypes;
+ private ReflectAnnotatedElementImpl annotations;
+ private ReflectAnnotatedElementImpl[] parameterAnnotations;
+
+ public ReflectMethodImpl(ReflectClassImpl> declaringClass, MethodReader method) {
+ this.declaringClass = declaringClass;
+ this.method = method;
+ context = declaringClass.getReflectContext();
+ }
+
+ @Override
+ public ReflectClass> getDeclaringClass() {
+ return declaringClass;
+ }
+
+ @Override
+ public String getName() {
+ return method.getName();
+ }
+
+ @Override
+ public int getModifiers() {
+ return ReflectContext.getModifiers(method);
+ }
+
+ @Override
+ public boolean isConstructor() {
+ return method.getName().equals("");
+ }
+
+ @Override
+ public ReflectClass> getReturnType() {
+ if (returnType == null) {
+ returnType = context.getClass(method.getResultType());
+ }
+ return returnType;
+ }
+
+ @Override
+ public ReflectClass>[] getParameterTypes() {
+ ensureParameterTypes();
+ return parameterTypes.clone();
+ }
+
+ @Override
+ public ReflectClass> getParameterType(int index) {
+ ensureParameterTypes();
+ return parameterTypes[index];
+ }
+
+ @Override
+ public int getParameterCount() {
+ ensureParameterTypes();
+ return parameterTypes.length;
+ }
+
+ private void ensureParameterTypes() {
+ if (parameterTypes == null) {
+ parameterTypes = Arrays.stream(method.getParameterTypes())
+ .map(type -> context.getClass(type))
+ .toArray(sz -> new ReflectClass>[sz]);
+ }
+ }
+
+ @Override
+ public S getAnnotation(Class type) {
+ if (annotations == null) {
+ annotations = new ReflectAnnotatedElementImpl(context, method.getAnnotations());
+ }
+ return annotations.getAnnotation(type);
+ }
+
+ @Override
+ public Object invoke(Object obj, Object... args) {
+ throw new IllegalStateException("Don't call this method from compile domain");
+ }
+
+ @Override
+ public Object construct(Object... args) {
+ throw new IllegalStateException("Don't call this method from compile domain");
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getReturnType()).append(' ').append(getName()).append('(');
+ ReflectClass>[] parameterTypes = getParameterTypes();
+ sb.append(Arrays.stream(parameterTypes).map(Objects::toString).collect(Collectors.joining(", ")));
+ sb.append(')');
+ return sb.toString();
+ }
+
+ @Override
+ public ReflectAnnotatedElement getParameterAnnotations(int index) {
+ if (parameterAnnotations == null) {
+ parameterAnnotations = Arrays.stream(method.getParameterAnnotations())
+ .map(annot -> new ReflectAnnotatedElementImpl(context, annot))
+ .toArray(sz -> new ReflectAnnotatedElementImpl[sz]);
+ }
+ return parameterAnnotations[index];
+ }
+}
diff --git a/core/teavm-core.iml b/core/teavm-core.iml
index 4f8549b6a..f97c35bec 100644
--- a/core/teavm-core.iml
+++ b/core/teavm-core.iml
@@ -27,6 +27,7 @@
+
diff --git a/extras-slf4j/teavm-extras-slf4j.iml b/extras-slf4j/teavm-extras-slf4j.iml
index ff38459f4..c0b542c8e 100644
--- a/extras-slf4j/teavm-extras-slf4j.iml
+++ b/extras-slf4j/teavm-extras-slf4j.iml
@@ -11,6 +11,7 @@
+
diff --git a/html4j/teavm-html4j.iml b/html4j/teavm-html4j.iml
index ed9502aa5..d94d778d1 100644
--- a/html4j/teavm-html4j.iml
+++ b/html4j/teavm-html4j.iml
@@ -20,6 +20,7 @@
+
diff --git a/jso/impl/teavm-jso-impl.iml b/jso/impl/teavm-jso-impl.iml
index a448936fb..28870086b 100644
--- a/jso/impl/teavm-jso-impl.iml
+++ b/jso/impl/teavm-jso-impl.iml
@@ -12,6 +12,7 @@
+
diff --git a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/Meta.java b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/Meta.java
new file mode 100644
index 000000000..4222ef9ad
--- /dev/null
+++ b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/Meta.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Meta {
+}
diff --git a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/ReflectClass.java b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/ReflectClass.java
index 37e596faa..8b624190c 100644
--- a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/ReflectClass.java
+++ b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/ReflectClass.java
@@ -16,10 +16,11 @@
package org.teavm.metaprogramming;
import java.util.Arrays;
+import org.teavm.metaprogramming.reflect.ReflectAnnotatedElement;
import org.teavm.metaprogramming.reflect.ReflectField;
import org.teavm.metaprogramming.reflect.ReflectMethod;
-public interface ReflectClass {
+public interface ReflectClass extends ReflectAnnotatedElement {
boolean isPrimitive();
boolean isInterface();
diff --git a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/reflect/ReflectMember.java b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/reflect/ReflectMember.java
index f02124c3c..fd9306007 100644
--- a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/reflect/ReflectMember.java
+++ b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/reflect/ReflectMember.java
@@ -17,7 +17,7 @@ package org.teavm.metaprogramming.reflect;
import org.teavm.metaprogramming.ReflectClass;
-public interface ReflectMember {
+public interface ReflectMember extends ReflectAnnotatedElement {
ReflectClass> getDeclaringClass();
String getName();
diff --git a/platform/teavm-platform.iml b/platform/teavm-platform.iml
index 869f418fe..edf328ac2 100644
--- a/platform/teavm-platform.iml
+++ b/platform/teavm-platform.iml
@@ -24,6 +24,7 @@
+
diff --git a/samples/async/teavm-samples-async.iml b/samples/async/teavm-samples-async.iml
index 828605ef2..afff7f298 100644
--- a/samples/async/teavm-samples-async.iml
+++ b/samples/async/teavm-samples-async.iml
@@ -25,6 +25,7 @@
+
diff --git a/samples/benchmark/teavm-samples-benchmark.iml b/samples/benchmark/teavm-samples-benchmark.iml
index d87c614cb..cdcbc7296 100644
--- a/samples/benchmark/teavm-samples-benchmark.iml
+++ b/samples/benchmark/teavm-samples-benchmark.iml
@@ -35,6 +35,7 @@
+
diff --git a/samples/hello/teavm-samples-hello.iml b/samples/hello/teavm-samples-hello.iml
index 385ff00b0..08f923ab9 100644
--- a/samples/hello/teavm-samples-hello.iml
+++ b/samples/hello/teavm-samples-hello.iml
@@ -25,6 +25,7 @@
+
diff --git a/samples/kotlin/teavm-samples-kotlin.iml b/samples/kotlin/teavm-samples-kotlin.iml
index e8088e5f8..170e5cb3c 100644
--- a/samples/kotlin/teavm-samples-kotlin.iml
+++ b/samples/kotlin/teavm-samples-kotlin.iml
@@ -29,6 +29,7 @@
+
diff --git a/samples/scala/teavm-samples-scala.iml b/samples/scala/teavm-samples-scala.iml
index 843b73782..9d3abcbc8 100644
--- a/samples/scala/teavm-samples-scala.iml
+++ b/samples/scala/teavm-samples-scala.iml
@@ -25,6 +25,7 @@
+
diff --git a/samples/storage/teavm-samples-storage.iml b/samples/storage/teavm-samples-storage.iml
index c3ee46956..ce450beb1 100644
--- a/samples/storage/teavm-samples-storage.iml
+++ b/samples/storage/teavm-samples-storage.iml
@@ -25,6 +25,7 @@
+
diff --git a/samples/video/teavm-samples-video.iml b/samples/video/teavm-samples-video.iml
index c3ee46956..ce450beb1 100644
--- a/samples/video/teavm-samples-video.iml
+++ b/samples/video/teavm-samples-video.iml
@@ -25,6 +25,7 @@
+
diff --git a/tests/teavm-tests.iml b/tests/teavm-tests.iml
index d0448738c..caeed3fc3 100644
--- a/tests/teavm-tests.iml
+++ b/tests/teavm-tests.iml
@@ -11,6 +11,7 @@
+
diff --git a/tools/chrome-rdp/teavm-chrome-rdp.iml b/tools/chrome-rdp/teavm-chrome-rdp.iml
index 698c7b466..995dfdf10 100644
--- a/tools/chrome-rdp/teavm-chrome-rdp.iml
+++ b/tools/chrome-rdp/teavm-chrome-rdp.iml
@@ -45,6 +45,7 @@
+
diff --git a/tools/cli/teavm-cli.iml b/tools/cli/teavm-cli.iml
index f1df25985..e67285d0e 100644
--- a/tools/cli/teavm-cli.iml
+++ b/tools/cli/teavm-cli.iml
@@ -11,6 +11,7 @@
+
diff --git a/tools/core/teavm-tooling.iml b/tools/core/teavm-tooling.iml
index fe89ce587..cfd432be7 100644
--- a/tools/core/teavm-tooling.iml
+++ b/tools/core/teavm-tooling.iml
@@ -24,6 +24,7 @@
+
diff --git a/tools/maven/plugin/teavm-maven-plugin.iml b/tools/maven/plugin/teavm-maven-plugin.iml
index 66a1d1672..bfb311088 100644
--- a/tools/maven/plugin/teavm-maven-plugin.iml
+++ b/tools/maven/plugin/teavm-maven-plugin.iml
@@ -42,6 +42,7 @@
+