From 31f9ca57ced13f3ad880d32e43c8155fccf2325a Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 17 Oct 2016 00:20:56 +0300 Subject: [PATCH] Add reflection support for fields --- .../org/teavm/classlib/ReflectionContext.java | 24 ++ .../teavm/classlib/ReflectionSupplier.java | 22 ++ .../org/teavm/classlib/impl/JCLPlugin.java | 11 + .../impl/ReflectionDependencyListener.java | 231 ++++++++++++++++++ .../classlib/impl/reflection/Converter.java | 30 +++ .../impl/reflection/ConverterInjector.java | 28 +++ .../teavm/classlib/impl/reflection/Flags.java | 87 +++++++ .../classlib/impl/reflection/JSClass.java | 28 +++ .../classlib/impl/reflection/JSField.java | 40 +++ .../impl/reflection/JSFieldGetter.java | 25 ++ .../impl/reflection/JSFieldSetter.java | 25 ++ .../classlib/java/lang/ClassGenerator.java | 225 +++++++++++++++++ .../org/teavm/classlib/java/lang/TClass.java | 148 +++++++++++ .../java/lang/reflect/TAccessibleObject.java | 52 ++++ .../java/lang/reflect/TAnnotatedElement.java | 43 +++- .../classlib/java/lang/reflect/TField.java | 115 +++++++++ .../classlib/java/lang/reflect/TMember.java | 31 +++ .../classlib/java/lang/reflect/TModifier.java | 109 +++++++++ .../teavm/platform/PlatformClassMetadata.java | 3 + .../classlib/java/lang/reflect/FieldTest.java | 146 +++++++++++ .../teavm/classlib/support/Reflectable.java | 26 ++ .../support/ReflectionSupplierImpl.java | 38 +++ .../org.teavm.classlib.ReflectionSupplier | 1 + 23 files changed, 1487 insertions(+), 1 deletion(-) create mode 100644 classlib/src/main/java/org/teavm/classlib/ReflectionContext.java create mode 100644 classlib/src/main/java/org/teavm/classlib/ReflectionSupplier.java create mode 100644 classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java create mode 100644 classlib/src/main/java/org/teavm/classlib/impl/reflection/Converter.java create mode 100644 classlib/src/main/java/org/teavm/classlib/impl/reflection/ConverterInjector.java create mode 100644 classlib/src/main/java/org/teavm/classlib/impl/reflection/Flags.java create mode 100644 classlib/src/main/java/org/teavm/classlib/impl/reflection/JSClass.java create mode 100644 classlib/src/main/java/org/teavm/classlib/impl/reflection/JSField.java create mode 100644 classlib/src/main/java/org/teavm/classlib/impl/reflection/JSFieldGetter.java create mode 100644 classlib/src/main/java/org/teavm/classlib/impl/reflection/JSFieldSetter.java create mode 100644 classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java create mode 100644 classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TAccessibleObject.java create mode 100644 classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TField.java create mode 100644 classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TMember.java create mode 100644 classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TModifier.java create mode 100644 tests/src/test/java/org/teavm/classlib/java/lang/reflect/FieldTest.java create mode 100644 tests/src/test/java/org/teavm/classlib/support/Reflectable.java create mode 100644 tests/src/test/java/org/teavm/classlib/support/ReflectionSupplierImpl.java create mode 100644 tests/src/test/resources/META-INF/services/org.teavm.classlib.ReflectionSupplier diff --git a/classlib/src/main/java/org/teavm/classlib/ReflectionContext.java b/classlib/src/main/java/org/teavm/classlib/ReflectionContext.java new file mode 100644 index 000000000..69d67d115 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/ReflectionContext.java @@ -0,0 +1,24 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib; + +import org.teavm.model.ClassReaderSource; + +public interface ReflectionContext { + ClassLoader getClassLoader(); + + ClassReaderSource getClassSource(); +} diff --git a/classlib/src/main/java/org/teavm/classlib/ReflectionSupplier.java b/classlib/src/main/java/org/teavm/classlib/ReflectionSupplier.java new file mode 100644 index 000000000..29fa290ba --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/ReflectionSupplier.java @@ -0,0 +1,22 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib; + +import java.util.Collection; + +public interface ReflectionSupplier { + Collection getAccessibleFields(ReflectionContext context, String className); +} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java index dcb56f7df..075655dcb 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java @@ -20,8 +20,11 @@ import java.lang.invoke.LambdaMetafactory; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.util.ArrayList; +import java.util.List; import java.util.ServiceLoader; import org.teavm.backend.javascript.TeaVMJavaScriptHost; +import org.teavm.classlib.ReflectionSupplier; import org.teavm.classlib.impl.lambda.LambdaMetafactorySubstitutor; import org.teavm.classlib.impl.unicode.CLDRReader; import org.teavm.classlib.java.lang.reflect.AnnotationDependencyListener; @@ -60,5 +63,13 @@ public class JCLPlugin implements TeaVMPlugin { host.add(new ScalaHacks()); host.add(new NumericClassTransformer()); + + List reflectionSuppliers = new ArrayList<>(); + for (ReflectionSupplier supplier : ServiceLoader.load(ReflectionSupplier.class, host.getClassLoader())) { + reflectionSuppliers.add(supplier); + } + ReflectionDependencyListener reflection = new ReflectionDependencyListener(reflectionSuppliers); + host.registerService(ReflectionDependencyListener.class, reflection); + host.add(reflection); } } diff --git a/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java b/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java new file mode 100644 index 000000000..68ae14aae --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java @@ -0,0 +1,231 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.teavm.classlib.ReflectionContext; +import org.teavm.classlib.ReflectionSupplier; +import org.teavm.dependency.AbstractDependencyListener; +import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.DependencyNode; +import org.teavm.dependency.FieldDependency; +import org.teavm.dependency.MethodDependency; +import org.teavm.model.CallLocation; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.ElementModifier; +import org.teavm.model.FieldReader; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; + +public class ReflectionDependencyListener extends AbstractDependencyListener { + private List reflectionSuppliers; + private MethodReference fieldGet = new MethodReference(Field.class, "get", Object.class, Object.class); + private MethodReference fieldSet = new MethodReference(Field.class, "set", Object.class, Object.class, void.class); + private MethodReference getFields = new MethodReference(Class.class, "getDeclaredFields", Field[].class); + private boolean fieldGetHandled; + private boolean fieldSetHandled; + private Map> accessibleFieldCache = new HashMap<>(); + private Set classesWithReflectableFields = new HashSet<>(); + + public ReflectionDependencyListener(List reflectionSuppliers) { + this.reflectionSuppliers = reflectionSuppliers; + } + + public Set getClassesWithReflectableFields() { + return classesWithReflectableFields; + } + + public Set getAccessibleFields(String className) { + return accessibleFieldCache.get(className); + } + + @Override + public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + if (method.getReference().equals(fieldGet)) { + handleFieldGet(agent, method, location); + } else if (method.getReference().equals(fieldSet)) { + handleFieldSet(agent, method, location); + } else if (method.getReference().equals(getFields)) { + method.getVariable(0).getClassValueNode().addConsumer(type -> { + if (!type.getName().startsWith("[")) { + classesWithReflectableFields.add(type.getName()); + } + }); + } + } + + private void handleFieldGet(DependencyAgent agent, MethodDependency method, CallLocation location) { + if (fieldGetHandled) { + return; + } + fieldGetHandled = true; + + DependencyNode classValueNode = agent.linkMethod(getFields, location).getVariable(0).getClassValueNode(); + classValueNode.addConsumer(reflectedType -> { + if (reflectedType.getName().startsWith("[")) { + return; + } + Set accessibleFields = getAccessibleFields(agent, reflectedType.getName()); + ClassReader cls = agent.getClassSource().get(reflectedType.getName()); + for (String fieldName : accessibleFields) { + FieldReader field = cls.getField(fieldName); + FieldDependency fieldDep = agent.linkField(field.getReference(), location); + propagateGet(agent, field.getType(), fieldDep.getValue(), method.getResult(), location); + linkClassIfNecessary(agent, field, location); + } + }); + } + + private void handleFieldSet(DependencyAgent agent, MethodDependency method, CallLocation location) { + if (fieldSetHandled) { + return; + } + fieldSetHandled = true; + + DependencyNode classValueNode = agent.linkMethod(getFields, location).getVariable(0).getClassValueNode(); + classValueNode.addConsumer(reflectedType -> { + if (reflectedType.getName().startsWith("[")) { + return; + } + + Set accessibleFields = getAccessibleFields(agent, reflectedType.getName()); + ClassReader cls = agent.getClassSource().get(reflectedType.getName()); + for (String fieldName : accessibleFields) { + FieldReader field = cls.getField(fieldName); + FieldDependency fieldDep = agent.linkField(field.getReference(), location); + propagateSet(agent, field.getType(), method.getVariable(2), fieldDep.getValue(), location); + linkClassIfNecessary(agent, field, location); + } + }); + } + + private void linkClassIfNecessary(DependencyAgent agent, FieldReader field, CallLocation location) { + if (field.hasModifier(ElementModifier.STATIC)) { + agent.linkClass(field.getOwnerName(), location).initClass(location); + } + } + + private Set getAccessibleFields(DependencyAgent agent, String className) { + return accessibleFieldCache.computeIfAbsent(className, key -> gatherAccessibleFields(agent, key)); + } + + private Set gatherAccessibleFields(DependencyAgent agent, String className) { + ReflectionContextImpl context = new ReflectionContextImpl(agent); + Set fields = new HashSet<>(); + for (ReflectionSupplier supplier : reflectionSuppliers) { + fields.addAll(supplier.getAccessibleFields(context, className)); + } + return fields; + } + + private void propagateGet(DependencyAgent agent, ValueType type, DependencyNode sourceNode, + DependencyNode targetNode, CallLocation location) { + if (type instanceof ValueType.Primitive) { + MethodReference boxMethod; + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + boxMethod = new MethodReference(Boolean.class, "valueOf", boolean.class, Boolean.class); + break; + case BYTE: + boxMethod = new MethodReference(Byte.class, "valueOf", byte.class, Byte.class); + break; + case SHORT: + boxMethod = new MethodReference(Short.class, "valueOf", short.class, Short.class); + break; + case CHARACTER: + boxMethod = new MethodReference(Character.class, "valueOf", char.class, Character.class); + break; + case INTEGER: + boxMethod = new MethodReference(Integer.class, "valueOf", int.class, Integer.class); + break; + case FLOAT: + boxMethod = new MethodReference(Float.class, "valueOf", float.class, Float.class); + break; + case DOUBLE: + boxMethod = new MethodReference(Double.class, "valueOf", double.class, Double.class); + break; + default: + throw new AssertionError(type.toString()); + } + MethodDependency boxMethodDep = agent.linkMethod(boxMethod, location); + boxMethodDep.use(); + boxMethodDep.getResult().connect(targetNode); + } else if (type instanceof ValueType.Array || type instanceof ValueType.Object) { + sourceNode.connect(targetNode); + } + } + + private void propagateSet(DependencyAgent agent, ValueType type, DependencyNode sourceNode, + DependencyNode targetNode, CallLocation location) { + if (type instanceof ValueType.Primitive) { + MethodReference unboxMethod; + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + unboxMethod = new MethodReference(Boolean.class, "booleanValue", boolean.class); + break; + case BYTE: + unboxMethod = new MethodReference(Byte.class, "byteValue", byte.class); + break; + case SHORT: + unboxMethod = new MethodReference(Short.class, "shortValue", short.class); + break; + case CHARACTER: + unboxMethod = new MethodReference(Character.class, "charValue", char.class); + break; + case INTEGER: + unboxMethod = new MethodReference(Integer.class, "intValue", int.class); + break; + case FLOAT: + unboxMethod = new MethodReference(Float.class, "floatValue", float.class); + break; + case DOUBLE: + unboxMethod = new MethodReference(Double.class, "doubleOf", double.class); + break; + default: + throw new AssertionError(type.toString()); + } + MethodDependency unboxMethodDep = agent.linkMethod(unboxMethod, location); + unboxMethodDep.use(); + sourceNode.connect(unboxMethodDep.getResult()); + } else if (type instanceof ValueType.Array || type instanceof ValueType.Object) { + sourceNode.connect(targetNode); + } + } + + private static class ReflectionContextImpl implements ReflectionContext { + private DependencyAgent agent; + + public ReflectionContextImpl(DependencyAgent agent) { + this.agent = agent; + } + + @Override + public ClassLoader getClassLoader() { + return agent.getClassLoader(); + } + + @Override + public ClassReaderSource getClassSource() { + return agent.getClassSource(); + } + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/reflection/Converter.java b/classlib/src/main/java/org/teavm/classlib/impl/reflection/Converter.java new file mode 100644 index 000000000..fce4a58b6 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/reflection/Converter.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.reflection; + +import org.teavm.backend.javascript.spi.InjectedBy; +import org.teavm.jso.JSObject; + +public final class Converter { + private Converter() { + } + + @InjectedBy(ConverterInjector.class) + public static native Object toJava(JSObject jsObject); + + @InjectedBy(ConverterInjector.class) + public static native JSObject fromJava(Object object); +} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/reflection/ConverterInjector.java b/classlib/src/main/java/org/teavm/classlib/impl/reflection/ConverterInjector.java new file mode 100644 index 000000000..6895e5aae --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/reflection/ConverterInjector.java @@ -0,0 +1,28 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.reflection; + +import java.io.IOException; +import org.teavm.backend.javascript.spi.Injector; +import org.teavm.backend.javascript.spi.InjectorContext; +import org.teavm.model.MethodReference; + +public class ConverterInjector implements Injector { + @Override + public void generate(InjectorContext context, MethodReference methodRef) throws IOException { + context.writeExpr(context.getArgument(0)); + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/reflection/Flags.java b/classlib/src/main/java/org/teavm/classlib/impl/reflection/Flags.java new file mode 100644 index 000000000..1d61d8c99 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/reflection/Flags.java @@ -0,0 +1,87 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.reflection; + +public final class Flags { + private Flags() { + } + + public static final int ABSTRACT = 1; + public static final int ANNOTATION = 2; + public static final int BRIDGE = 4; + public static final int DEPRECATED = 8; + public static final int ENUM = 16; + public static final int FINAL = 32; + public static final int INTERFACE = 64; + public static final int NATIVE = 128; + public static final int STATIC = 256; + public static final int STRICT = 512; + public static final int SUPER = 1024; + public static final int SYNCHRONIZED = 2048; + public static final int SYNTHETIC = 4096; + public static final int TRANSIENT = 8192; + public static final int VARARGS = 16384; + public static final int VOLATILE = 32768; + + public static final int PACKAGE_PRIVATE = 0; + public static final int PRIVATE = 1; + public static final int PROTECTED = 2; + public static final int PUBLIC = 3; + + public static int getModifiers(int flags, int access) { + int modifiers = 0; + switch (access) { + case PUBLIC: + modifiers |= 1; + break; + case PRIVATE: + modifiers |= 2; + break; + case PROTECTED: + modifiers |= 4; + break; + } + + // static + modifiers |= (flags >>> 5) & 8; + + // final + modifiers |= (flags >>> 1) & 16; + + // synchronized + modifiers |= (flags >>> 5) & 32; + + // volatile + modifiers |= (flags >>> 9) & 64; + + // transient + modifiers |= (flags >>> 6) & 128; + + // native + modifiers |= (flags << 1) & 256; + + // interface + modifiers |= (flags << 3) & 512; + + // abstract + modifiers |= (flags << 10) & 1024; + + // strict + modifiers |= (flags << 2) & 2048; + + return modifiers; + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSClass.java b/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSClass.java new file mode 100644 index 000000000..022095bd7 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSClass.java @@ -0,0 +1,28 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.reflection; + +import org.teavm.jso.JSProperty; +import org.teavm.jso.core.JSArray; +import org.teavm.platform.PlatformClassMetadata; + +public interface JSClass extends PlatformClassMetadata { + @JSProperty + JSArray getFields(); + + @JSProperty + void setFields(JSArray fields); +} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSField.java b/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSField.java new file mode 100644 index 000000000..004cd21ea --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSField.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.reflection; + +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.platform.PlatformClass; + +public interface JSField extends JSObject { + @JSProperty + String getName(); + + @JSProperty + int getModifiers(); + + @JSProperty + int getAccessLevel(); + + @JSProperty + PlatformClass getType(); + + @JSProperty + JSFieldGetter getGetter(); + + @JSProperty + JSFieldSetter getSetter(); +} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSFieldGetter.java b/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSFieldGetter.java new file mode 100644 index 000000000..f712a7160 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSFieldGetter.java @@ -0,0 +1,25 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.reflection; + +import org.teavm.jso.JSFunctor; +import org.teavm.jso.JSObject; +import org.teavm.platform.PlatformObject; + +@JSFunctor +public interface JSFieldGetter extends JSObject { + JSObject get(PlatformObject instance); +} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSFieldSetter.java b/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSFieldSetter.java new file mode 100644 index 000000000..5dc01e367 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/reflection/JSFieldSetter.java @@ -0,0 +1,25 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.reflection; + +import org.teavm.jso.JSFunctor; +import org.teavm.jso.JSObject; +import org.teavm.platform.PlatformObject; + +@JSFunctor +public interface JSFieldSetter extends JSObject { + void set(PlatformObject instance, JSObject value); +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java new file mode 100644 index 000000000..e348d37ac --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java @@ -0,0 +1,225 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang; + +import java.io.IOException; +import java.util.Set; +import org.teavm.backend.javascript.codegen.SourceWriter; +import org.teavm.backend.javascript.rendering.RenderingUtil; +import org.teavm.backend.javascript.spi.Generator; +import org.teavm.backend.javascript.spi.GeneratorContext; +import org.teavm.classlib.impl.ReflectionDependencyListener; +import org.teavm.model.ClassReader; +import org.teavm.model.ElementModifier; +import org.teavm.model.FieldReader; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; + +public class ClassGenerator implements Generator { + @Override + public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { + switch (methodRef.getName()) { + case "createJsFields": + generateCreateJsFields(context, writer); + break; + } + } + + private void generateCreateJsFields(GeneratorContext context, SourceWriter writer) throws IOException { + ReflectionDependencyListener reflection = context.getService(ReflectionDependencyListener.class); + for (String className : reflection.getClassesWithReflectableFields()) { + generateCreateJsFieldsForClass(context, writer, className); + } + } + + private void generateCreateJsFieldsForClass(GeneratorContext context, SourceWriter writer, String className) + throws IOException { + ReflectionDependencyListener reflection = context.getService(ReflectionDependencyListener.class); + Set accessibleFields = reflection.getAccessibleFields(className); + + ClassReader cls = context.getClassSource().get(className); + writer.appendClass(className).append(".$meta.fields").ws().append('=').ws().append('[').indent(); + + boolean first = true; + for (FieldReader field : cls.getFields()) { + if (!first) { + writer.append(",").ws(); + } else { + writer.softNewLine(); + } + first = false; + writer.append("{").indent().softNewLine(); + + writer.append("name").ws().append(':').ws().append('"') + .append(RenderingUtil.escapeString(field.getName())) + .append("\",").softNewLine(); + writer.append("modifiers").ws().append(':').ws().append(packModifiers(field.readModifiers())) + .append(",").softNewLine(); + writer.append("accessLevel").ws().append(':').ws().append(field.getLevel().ordinal()) + .append(",").softNewLine(); + writer.append("type").ws().append(':').ws().append(context.typeToClassString(field.getType())) + .append(",").softNewLine(); + + writer.append("getter").ws().append(':').ws(); + if (accessibleFields != null && accessibleFields.contains(field.getName())) { + renderGetter(writer, field); + } else { + writer.append("null"); + } + writer.append(",").softNewLine(); + + writer.append("setter").ws().append(':').ws(); + if (accessibleFields != null && accessibleFields.contains(field.getName())) { + renderSetter(writer, field); + } else { + writer.append("null"); + } + + writer.outdent().softNewLine().append("}"); + } + + writer.outdent().append("];").softNewLine(); + } + + private void renderGetter(SourceWriter writer, FieldReader field) throws IOException { + writer.append("function(obj)").ws().append("{").indent().softNewLine(); + initClass(writer, field); + writer.append("return "); + boxIfNecessary(writer, field.getType(), () -> fieldAccess(writer, field)); + writer.append(";").softNewLine(); + writer.outdent().append("}"); + } + + private void renderSetter(SourceWriter writer, FieldReader field) throws IOException { + writer.append("function(obj,").ws().append("val)").ws().append("{").indent().softNewLine(); + initClass(writer, field); + fieldAccess(writer, field); + writer.ws().append('=').ws(); + unboxIfNecessary(writer, field.getType(), () -> writer.append("val")); + writer.append(";").softNewLine(); + writer.outdent().append("}"); + } + + private void initClass(SourceWriter writer, FieldReader field) throws IOException { + if (field.hasModifier(ElementModifier.STATIC)) { + writer.appendClass(field.getOwnerName()).append("_$callClinit();").softNewLine(); + } + } + + private void fieldAccess(SourceWriter writer, FieldReader field) throws IOException { + if (field.hasModifier(ElementModifier.STATIC)) { + writer.appendStaticField(field.getReference()); + } else { + writer.append("obj.").appendField(field.getReference()); + } + } + + private void boxIfNecessary(SourceWriter writer, ValueType type, Fragment fragment) throws IOException { + boolean boxed = false; + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + writer.appendMethodBody(new MethodReference(Boolean.class, "valueOf", boolean.class, + Boolean.class)); + break; + case BYTE: + writer.appendMethodBody(new MethodReference(Byte.class, "valueOf", byte.class, Byte.class)); + break; + case SHORT: + writer.appendMethodBody(new MethodReference(Short.class, "valueOf", short.class, Short.class)); + break; + case CHARACTER: + writer.appendMethodBody(new MethodReference(Character.class, "valueOf", char.class, + Character.class)); + break; + case INTEGER: + writer.appendMethodBody(new MethodReference(Integer.class, "valueOf", int.class, Integer.class)); + break; + case LONG: + writer.appendMethodBody(new MethodReference(Long.class, "valueOf", long.class, Long.class)); + break; + case FLOAT: + writer.appendMethodBody(new MethodReference(Float.class, "valueOf", float.class, Float.class)); + break; + case DOUBLE: + writer.appendMethodBody(new MethodReference(Double.class, "valueOf", double.class, Double.class)); + break; + } + writer.append('('); + boxed = true; + } + fragment.render(); + if (boxed) { + writer.append(')'); + } + } + + private void unboxIfNecessary(SourceWriter writer, ValueType type, Fragment fragment) throws IOException { + boolean boxed = false; + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + writer.appendMethodBody(new MethodReference(Boolean.class, "booleanValue", boolean.class)); + break; + case BYTE: + writer.appendMethodBody(new MethodReference(Byte.class, "byteValue", byte.class)); + break; + case SHORT: + writer.appendMethodBody(new MethodReference(Short.class, "shortValue", short.class)); + break; + case CHARACTER: + writer.appendMethodBody(new MethodReference(Character.class, "charValue", char.class)); + break; + case INTEGER: + writer.appendMethodBody(new MethodReference(Integer.class, "intValue", int.class)); + break; + case LONG: + writer.appendMethodBody(new MethodReference(Long.class, "longValue", long.class)); + break; + case FLOAT: + writer.appendMethodBody(new MethodReference(Float.class, "floatValue", float.class)); + break; + case DOUBLE: + writer.appendMethodBody(new MethodReference(Double.class, "doubleValue", double.class)); + break; + } + writer.append('('); + boxed = true; + } + fragment.render(); + if (boxed) { + writer.append(')'); + } + } + + private interface Fragment { + void render() throws IOException; + } + + private int packModifiers(Set elementModifiers) { + ElementModifier[] knownModifiers = ElementModifier.values(); + int value = 0; + int bit = 1; + for (int i = 0; i < knownModifiers.length; ++i) { + ElementModifier modifier = knownModifiers[i]; + if (elementModifiers.contains(modifier)) { + value |= bit; + } + bit <<= 1; + } + return value; + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java index e998e9bc6..e52015efa 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java @@ -16,16 +16,30 @@ package org.teavm.classlib.java.lang; import java.io.InputStream; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; +import org.teavm.backend.javascript.spi.GeneratedBy; import org.teavm.classlib.impl.DeclaringClassMetadataGenerator; +import org.teavm.classlib.impl.reflection.Flags; +import org.teavm.classlib.impl.reflection.JSClass; +import org.teavm.classlib.impl.reflection.JSField; import org.teavm.classlib.java.lang.annotation.TAnnotation; import org.teavm.classlib.java.lang.reflect.TAnnotatedElement; import org.teavm.dependency.PluggableDependency; import org.teavm.interop.Address; import org.teavm.interop.DelegateTo; +import org.teavm.classlib.java.lang.reflect.TField; +import org.teavm.classlib.java.lang.reflect.TModifier; +import org.teavm.jso.core.JSArray; import org.teavm.platform.Platform; import org.teavm.platform.PlatformClass; +import org.teavm.platform.PlatformSequence; import org.teavm.platform.metadata.ClassResource; import org.teavm.platform.metadata.ClassScopedMetadataProvider; import org.teavm.runtime.RuntimeClass; @@ -37,6 +51,9 @@ public class TClass extends TObject implements TAnnotatedElement { private PlatformClass platformClass; private TAnnotation[] annotationsCache; private Map, TAnnotation> annotationsByType; + private TField[] declaredFields; + private TField[] fields; + private static boolean jsFieldsInitialized; private TClass(PlatformClass platformClass) { this.platformClass = platformClass; @@ -115,10 +132,123 @@ public class TClass extends TObject implements TAnnotatedElement { return Platform.isEnum(platformClass); } + public boolean isInterface() { + return (platformClass.getMetadata().getFlags() & Flags.INTERFACE) != 0; + } + public TClass getComponentType() { return getClass(Platform.getArrayItem(platformClass)); } + public TField[] getDeclaredFields() throws TSecurityException { + if (declaredFields == null) { + initJsFields(); + JSClass jsClass = (JSClass) getPlatformClass().getMetadata(); + JSArray jsFields = jsClass.getFields(); + declaredFields = new TField[jsFields.getLength()]; + for (int i = 0; i < jsFields.getLength(); ++i) { + JSField jsField = jsFields.get(i); + declaredFields[i] = new TField(this, jsField.getName(), jsField.getModifiers(), + jsField.getAccessLevel(), TClass.getClass(jsField.getType()), jsField.getGetter(), + jsField.getSetter()); + } + } + return declaredFields; + } + + private static void initJsFields() { + if (!jsFieldsInitialized) { + jsFieldsInitialized = true; + createJsFields(); + } + } + + @GeneratedBy(ClassGenerator.class) + private static native void createJsFields(); + + public TField[] getFields() throws TSecurityException { + if (fields == null) { + List fieldList = new ArrayList<>(); + TClass cls = this; + + if (cls.isInterface()) { + getFieldsOfInterfaces(cls, fieldList, new HashSet<>()); + } else { + while (cls != null) { + for (TField field : declaredFields) { + if (Modifier.isPublic(field.getModifiers())) { + fieldList.add(field); + } + } + cls = cls.getSuperclass(); + } + } + + fields = fieldList.toArray(new TField[fieldList.size()]); + } + return fields; + } + + public TField getDeclaredField(String name) throws TNoSuchFieldError { + for (TField field : getDeclaredFields()) { + if (field.getName().equals(name)) { + return field; + } + } + throw new TNoSuchFieldError(); + } + + public TField getField(String name) throws TNoSuchFieldError { + TField result = findField(name, new HashSet<>()); + if (result == null) { + throw new TNoSuchFieldError(); + } + return result; + } + + private TField findField(String name, Set visited) { + if (!visited.add(name)) { + return null; + } + + for (TField field : getDeclaredFields()) { + if (TModifier.isPublic(field.getModifiers()) && field.getName().equals(name)) { + return field; + } + } + + for (TClass iface : getInterfaces()) { + TField field = iface.findField(name, visited); + if (field != null) { + return field; + } + } + + TClass superclass = getSuperclass(); + if (superclass != null) { + TField field = superclass.findField(name, visited); + if (field != null) { + return field; + } + } + + return null; + } + + private static void getFieldsOfInterfaces(TClass iface, List fields, Set> visited) { + if (!visited.add(iface)) { + return; + } + for (TField field : iface.getDeclaredFields()) { + if (Modifier.isPublic(field.getModifiers())) { + fields.add(field); + } + } + for (TClass superInterface : iface.getInterfaces()) { + getFieldsOfInterfaces(superInterface, fields, visited); + } + } + public boolean desiredAssertionStatus() { return true; } @@ -128,6 +258,24 @@ public class TClass extends TObject implements TAnnotatedElement { return (TClass) getClass(platformClass.getMetadata().getSuperclass()); } + @SuppressWarnings("unchecked") + public TClass[] getInterfaces() { + PlatformSequence supertypes = platformClass.getMetadata().getSupertypes(); + + TClass[] filteredSupertypes = (TClass[]) new TClass[supertypes.getLength()]; + int j = 0; + for (int i = 0; i < supertypes.getLength(); ++i) { + if (supertypes.get(i) != platformClass.getMetadata().getSuperclass()) { + filteredSupertypes[j++] = (TClass) getClass(supertypes.get(j)); + } + } + + if (filteredSupertypes.length > j) { + filteredSupertypes = Arrays.copyOf(filteredSupertypes, j); + } + return filteredSupertypes; + } + @SuppressWarnings("unchecked") public T[] getEnumConstants() { return isEnum() ? (T[]) Platform.getEnumConstants(platformClass) : null; diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TAccessibleObject.java b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TAccessibleObject.java new file mode 100644 index 000000000..4bae126c0 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TAccessibleObject.java @@ -0,0 +1,52 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang.reflect; + +import org.teavm.classlib.java.lang.TClass; +import org.teavm.classlib.java.lang.TSecurityException; +import org.teavm.classlib.java.lang.annotation.TAnnotation; + +public class TAccessibleObject implements TAnnotatedElement { + protected TAccessibleObject() { + } + + public static void setAccessible(TAccessibleObject[] array, boolean flag) throws TSecurityException { + // Do nothing + } + + public void setAccessible(boolean flag) throws TSecurityException { + // Do nothing + } + + public boolean isAccessible() { + return true; + } + + @Override + public T getAnnotation(TClass annotationClass) { + return null; + } + + @Override + public TAnnotation[] getAnnotations() { + return null; + } + + @Override + public TAnnotation[] getDeclaredAnnotations() { + return null; + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TAnnotatedElement.java b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TAnnotatedElement.java index fe937e0e3..062d44295 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TAnnotatedElement.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TAnnotatedElement.java @@ -15,15 +15,56 @@ */ package org.teavm.classlib.java.lang.reflect; +import java.util.ArrayList; +import java.util.List; import org.teavm.classlib.java.lang.TClass; +import org.teavm.classlib.java.lang.TObject; import org.teavm.classlib.java.lang.annotation.TAnnotation; public interface TAnnotatedElement { - boolean isAnnotationPresent(TClass annotationClass); + default boolean isAnnotationPresent(TClass annotationClass) { + return getAnnotation(annotationClass) != null; + } T getAnnotation(TClass annotationClass); TAnnotation[] getAnnotations(); TAnnotation[] getDeclaredAnnotations(); + + default T[] getAnnotationsByType(TClass annotationClass) { + List result = new ArrayList<>(); + Object classAsObject = annotationClass; + for (TAnnotation annot : getAnnotations()) { + if (annot.annotationType() == classAsObject) { + result.add(annotationClass.cast((TObject) annot)); + } + } + @SuppressWarnings("unchecked") + T[] array = (T[]) (Object) TArray.newInstance(annotationClass, result.size()); + return result.toArray(array); + } + + default T getDeclaredAnnotation(TClass annotationClass) { + Object classAsObject = annotationClass; + for (TAnnotation annot : getDeclaredAnnotations()) { + if (annot.annotationType() == classAsObject) { + return annotationClass.cast((TObject) annot); + } + } + return null; + } + + default T[] getDeclaredAnnotationsByType(TClass annotationClass) { + List result = new ArrayList<>(); + Object classAsObject = annotationClass; + for (TAnnotation annot : getDeclaredAnnotations()) { + if (annot.annotationType() == classAsObject) { + result.add(annotationClass.cast((TObject) annot)); + } + } + @SuppressWarnings("unchecked") + T[] array = (T[]) (Object) TArray.newInstance(annotationClass, result.size()); + return result.toArray(array); + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TField.java b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TField.java new file mode 100644 index 000000000..e9104567b --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TField.java @@ -0,0 +1,115 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang.reflect; + +import org.teavm.classlib.impl.reflection.Converter; +import org.teavm.classlib.impl.reflection.Flags; +import org.teavm.classlib.impl.reflection.JSFieldGetter; +import org.teavm.classlib.impl.reflection.JSFieldSetter; +import org.teavm.classlib.java.lang.TClass; +import org.teavm.classlib.java.lang.TIllegalAccessException; +import org.teavm.classlib.java.lang.TIllegalArgumentException; +import org.teavm.classlib.java.lang.TObject; +import org.teavm.jso.JSObject; +import org.teavm.platform.Platform; + +public class TField extends TAccessibleObject implements TMember { + private TClass declaringClass; + private String name; + private int modifiers; + private int accessLevel; + private TClass type; + private JSFieldGetter getter; + private JSFieldSetter setter; + + public TField(TClass declaringClass, String name, int modifiers, int accessLevel, TClass type, + JSFieldGetter getter, JSFieldSetter setter) { + this.declaringClass = declaringClass; + this.name = name; + this.modifiers = modifiers; + this.accessLevel = accessLevel; + this.type = type; + this.getter = getter; + this.setter = setter; + } + + @Override + public TClass getDeclaringClass() { + return declaringClass; + } + + @Override + public String getName() { + return name; + } + + @Override + public int getModifiers() { + return Flags.getModifiers(modifiers, accessLevel); + } + + public boolean isEnumConstant() { + return (modifiers & Flags.ENUM) != 0; + } + + @Override + public boolean isSynthetic() { + return (modifiers & Flags.SYNTHETIC) != 0; + } + + public TClass getType() { + return type; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(TModifier.toString(getModifiers())); + if (sb.length() > 0) { + sb.append(' '); + } + sb.append(type.getName()).append(' ').append(declaringClass.getName()).append(".").append(name); + return sb.toString(); + } + + public Object get(Object obj) throws TIllegalArgumentException, TIllegalAccessException { + if (getter == null) { + throw new TIllegalAccessException(); + } + checkInstance(obj); + JSObject result = getter.get(Platform.getPlatformObject(obj)); + return Converter.toJava(result); + } + + public void set(Object obj, Object value) throws TIllegalArgumentException, TIllegalAccessException { + if (setter == null) { + throw new TIllegalAccessException(); + } + checkInstance(obj); + setter.set(Platform.getPlatformObject(obj), Converter.fromJava(value)); + } + + private void checkInstance(Object obj) { + if ((modifiers & Flags.STATIC) == 0) { + if (obj == null) { + throw new NullPointerException(); + } + if (!declaringClass.isInstance((TObject) obj)) { + throw new TIllegalArgumentException(); + } + } + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TMember.java b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TMember.java new file mode 100644 index 000000000..214ae5e8a --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TMember.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang.reflect; + +import org.teavm.classlib.java.lang.TClass; + +public interface TMember { + int PUBLIC = 0; + int DECLARED = 1; + + TClass getDeclaringClass(); + + String getName(); + + int getModifiers(); + + boolean isSynthetic(); +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TModifier.java b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TModifier.java new file mode 100644 index 000000000..386e5a213 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TModifier.java @@ -0,0 +1,109 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang.reflect; + +public class TModifier { + private static String[] modifierNames; + public static final int PUBLIC = 1; + public static final int PRIVATE = 2; + public static final int PROTECTED = 4; + public static final int STATIC = 8; + public static final int FINAL = 16; + public static final int SYNCHRONIZED = 32; + public static final int VOLATILE = 64; + public static final int TRANSIENT = 128; + public static final int NATIVE = 256; + public static final int INTERFACE = 512; + public static final int ABSTRACT = 1024; + public static final int STRICT = 2048; + private static final int[] canonicalOrder = { PUBLIC, PROTECTED, PRIVATE, ABSTRACT, STATIC, FINAL, TRANSIENT, + VOLATILE, SYNCHRONIZED, NATIVE, STRICT, INTERFACE }; + + private TModifier() { + } + + public static boolean isPublic(int mod) { + return (mod & PUBLIC) != 0; + } + + public static boolean isPrivate(int mod) { + return (mod & PRIVATE) != 0; + } + + public static boolean isProtected(int mod) { + return (mod & PROTECTED) != 0; + } + + public static boolean isStatic(int mod) { + return (mod & STATIC) != 0; + } + + public static boolean isFinal(int mod) { + return (mod & FINAL) != 0; + } + + public static boolean isSynchronized(int mod) { + return (mod & SYNCHRONIZED) != 0; + } + + public static boolean isVolatile(int mod) { + return (mod & VOLATILE) != 0; + } + + public static boolean isTransient(int mod) { + return (mod & TRANSIENT) != 0; + } + + public static boolean isNative(int mod) { + return (mod & NATIVE) != 0; + } + + public static boolean isInterface(int mod) { + return (mod & INTERFACE) != 0; + } + + public static boolean isAbstract(int mod) { + return (mod & ABSTRACT) != 0; + } + + public static boolean isStrict(int mod) { + return (mod & STRICT) != 0; + } + + public static String toString(int mod) { + StringBuilder sb = new StringBuilder(); + String[] modifierNames = getModifierNames(); + int index = 0; + for (int modifier : canonicalOrder) { + if ((mod & modifier) != 0) { + if (sb.length() > 0) { + sb.append(' '); + } + sb.append(modifierNames[index]); + } + ++index; + } + return sb.toString(); + } + + private static String[] getModifierNames() { + if (modifierNames == null) { + modifierNames = new String[] { "public", "protected", "private", "abstract", "static", "final", + "transient", "volatile", "synchronized", "native", "strictfp", "interface" }; + } + return modifierNames; + } +} diff --git a/platform/src/main/java/org/teavm/platform/PlatformClassMetadata.java b/platform/src/main/java/org/teavm/platform/PlatformClassMetadata.java index bd937db9c..247328d39 100644 --- a/platform/src/main/java/org/teavm/platform/PlatformClassMetadata.java +++ b/platform/src/main/java/org/teavm/platform/PlatformClassMetadata.java @@ -36,4 +36,7 @@ public interface PlatformClassMetadata extends JSObject { @JSProperty boolean isEnum(); + + @JSProperty + int getFlags(); } diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/reflect/FieldTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/reflect/FieldTest.java new file mode 100644 index 000000000..1fd1ab354 --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/lang/reflect/FieldTest.java @@ -0,0 +1,146 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang.reflect; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import java.lang.reflect.Field; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.classlib.support.Reflectable; +import org.teavm.junit.SkipJVM; +import org.teavm.junit.TeaVMTestRunner; + +@RunWith(TeaVMTestRunner.class) +public class FieldTest { + @Test + public void fieldsEnumerated() { + new ReflectableType(); + StringBuilder sb = new StringBuilder(); + for (Field field : ReflectableType.class.getDeclaredFields()) { + sb.append(field).append(";"); + } + assertEquals("" + + "public int org.teavm.classlib.java.lang.reflect.FieldTest$ReflectableType.a;" + + "private boolean org.teavm.classlib.java.lang.reflect.FieldTest$ReflectableType.b;" + + "java.lang.Object org.teavm.classlib.java.lang.reflect.FieldTest$ReflectableType.c;" + + "java.lang.String org.teavm.classlib.java.lang.reflect.FieldTest$ReflectableType.d;" + + "long org.teavm.classlib.java.lang.reflect.FieldTest$ReflectableType.e;" + + "private static short org.teavm.classlib.java.lang.reflect.FieldTest$ReflectableType.f;" + + "static boolean org.teavm.classlib.java.lang.reflect.FieldTest$ReflectableType.initialized;", + sb.toString()); + } + + @Test + public void fieldRead() throws NoSuchFieldException, IllegalAccessException { + ReflectableType instance = new ReflectableType(); + Field field = instance.getClass().getDeclaredField("a"); + Object result = field.get(instance); + assertEquals(23, result); + } + + @Test + public void fieldWritten() throws NoSuchFieldException, IllegalAccessException { + ReflectableType instance = new ReflectableType(); + Field field = instance.getClass().getDeclaredField("a"); + field.set(instance, 234); + assertEquals(234, instance.a); + } + + @Test(expected = IllegalAccessException.class) + @SkipJVM + public void fieldCannotBeRead() throws NoSuchFieldException, IllegalAccessException { + ReflectableType instance = new ReflectableType(); + Field field = instance.getClass().getDeclaredField("e"); + field.get(instance); + } + + @Test(expected = IllegalAccessException.class) + @SkipJVM + public void fieldCannotBeWritten() throws NoSuchFieldException, IllegalAccessException { + ReflectableType instance = new ReflectableType(); + Field field = instance.getClass().getDeclaredField("e"); + field.set(instance, 1L); + } + + @Test + public void staticFieldRead() throws NoSuchFieldException, IllegalAccessException { + Field field = ReflectableType.class.getDeclaredField("f"); + field.setAccessible(true); + Object result = field.get(null); + assertTrue(ReflectableType.initialized); + assertEquals(ReflectableType.f, result); + } + + @Test + public void staticFieldWritten() throws NoSuchFieldException, IllegalAccessException { + Field field = ReflectableType.class.getDeclaredField("f"); + field.setAccessible(true); + field.set(null, (short) 999); + assertTrue(ReflectableType.initialized); + assertEquals((short) 999, ReflectableType.f); + } + + @Test + public void dependencyMaintainedForGet() throws NoSuchFieldException, IllegalAccessException { + ReflectableType instance = new ReflectableType(); + instance.c = new Foo(123); + Field field = ReflectableType.class.getDeclaredField("c"); + Foo result = (Foo) field.get(instance); + assertEquals(123, result.getValue()); + } + + @Test + public void dependencyMaintainedForSet() throws NoSuchFieldException, IllegalAccessException { + ReflectableType instance = new ReflectableType(); + Field field = ReflectableType.class.getDeclaredField("c"); + field.set(instance, new Foo(123)); + assertEquals(123, ((Foo) instance.c).getValue()); + } + + static class ReflectableType { + @Reflectable public int a; + @Reflectable private boolean b; + @Reflectable Object c; + @Reflectable String d; + + long e; + + @Reflectable private static short f = 99; + + static boolean initialized = true; + + public ReflectableType() { + a = 23; + b = true; + c = "foo"; + d = "bar"; + e = 42; + } + } + + static class Foo { + int value; + + public Foo(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } +} diff --git a/tests/src/test/java/org/teavm/classlib/support/Reflectable.java b/tests/src/test/java/org/teavm/classlib/support/Reflectable.java new file mode 100644 index 000000000..f4cf5f1cb --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/support/Reflectable.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.classlib.support; + +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.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR }) +public @interface Reflectable { +} diff --git a/tests/src/test/java/org/teavm/classlib/support/ReflectionSupplierImpl.java b/tests/src/test/java/org/teavm/classlib/support/ReflectionSupplierImpl.java new file mode 100644 index 000000000..406588a4f --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/support/ReflectionSupplierImpl.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.support; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import org.teavm.classlib.ReflectionContext; +import org.teavm.classlib.ReflectionSupplier; +import org.teavm.model.ClassReader; +import org.teavm.model.FieldReader; + +public class ReflectionSupplierImpl implements ReflectionSupplier { + @Override + public Collection getAccessibleFields(ReflectionContext context, String className) { + ClassReader cls = context.getClassSource().get(className); + Set fields = new HashSet<>(); + for (FieldReader field : cls.getFields()) { + if (field.getAnnotations().get(Reflectable.class.getName()) != null) { + fields.add(field.getName()); + } + } + return fields; + } +} diff --git a/tests/src/test/resources/META-INF/services/org.teavm.classlib.ReflectionSupplier b/tests/src/test/resources/META-INF/services/org.teavm.classlib.ReflectionSupplier new file mode 100644 index 000000000..5af32c928 --- /dev/null +++ b/tests/src/test/resources/META-INF/services/org.teavm.classlib.ReflectionSupplier @@ -0,0 +1 @@ +org.teavm.classlib.support.ReflectionSupplierImpl \ No newline at end of file