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 getSuperclass() { + resolve(); + if (classReader == null || classReader.getParent() == null + || classReader.getName().equals(classReader.getParent())) { + return null; + } + return (ReflectClass) context.getClass(new ValueType.Object(classReader.getParent())); + } + + @SuppressWarnings("unchecked") + @Override + public ReflectClass[] getInterfaces() { + resolve(); + if (classReader == null) { + return (ReflectClass[]) Array.newInstance(ReflectClassImpl.class, 0); + } + return classReader.getInterfaces().stream() + .map(iface -> context.getClass(new ValueType.Object(iface))) + .toArray(sz -> (ReflectClass[]) 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 @@ +