diff --git a/teavm-classlib/src/test/java/org/teavm/platform/metadata/MetadataGeneratorTest.java b/teavm-classlib/src/test/java/org/teavm/platform/metadata/MetadataGeneratorTest.java index 49de83443..1c56755aa 100644 --- a/teavm-classlib/src/test/java/org/teavm/platform/metadata/MetadataGeneratorTest.java +++ b/teavm-classlib/src/test/java/org/teavm/platform/metadata/MetadataGeneratorTest.java @@ -51,9 +51,22 @@ public class MetadataGeneratorTest { assertEquals(25, res.getE()); assertEquals(3.14, res.getF(), 0.001); assertEquals(2.72, res.getG(), 0.001); + assertEquals(Integer.valueOf(26), res.getH()); assertNull(res.getI()); assertEquals(Byte.valueOf((byte)27), res.getJ()); assertEquals(Short.valueOf((short)28), res.getK()); + assertEquals(100, res.getL().floatValue(), 0.1); + assertEquals(200, res.getM().doubleValue(), 0.1); + + assertEquals("qwe", res.getFoo()); + + assertEquals(2, res.getArrayA().size()); + assertEquals(Integer.valueOf(2), res.getArrayA().get(0)); + assertEquals(Integer.valueOf(3), res.getArrayA().get(1)); + assertEquals(1, res.getArrayB().size()); + assertEquals("baz", res.getArrayB().get(0)); + assertNull(res.getArrayC()); } } + diff --git a/teavm-classlib/src/test/java/org/teavm/platform/metadata/TestResourceGenerator.java b/teavm-classlib/src/test/java/org/teavm/platform/metadata/TestResourceGenerator.java index 6b787b37c..2dbf1e392 100644 --- a/teavm-classlib/src/test/java/org/teavm/platform/metadata/TestResourceGenerator.java +++ b/teavm-classlib/src/test/java/org/teavm/platform/metadata/TestResourceGenerator.java @@ -50,6 +50,7 @@ public class TestResourceGenerator implements MetadataGenerator { resource.setK((short)28); resource.setL(100f); resource.setM(200.0); + resource.setFoo("qwe"); ResourceArray array = context.createResourceArray(); array.add(2); diff --git a/teavm-core/src/main/java/org/teavm/model/ValueType.java b/teavm-core/src/main/java/org/teavm/model/ValueType.java index b4f7795ab..53547517b 100644 --- a/teavm-core/src/main/java/org/teavm/model/ValueType.java +++ b/teavm-core/src/main/java/org/teavm/model/ValueType.java @@ -191,7 +191,26 @@ public abstract class ValueType { } public static ValueType primitive(PrimitiveType type) { - return new Primitive(type); + switch (type) { + case BOOLEAN: + return BOOLEAN; + case BYTE: + return BYTE; + case CHARACTER: + return CHARACTER; + case SHORT: + return SHORT; + case INTEGER: + return INTEGER; + case LONG: + return LONG; + case FLOAT: + return FLOAT; + case DOUBLE: + return DOUBLE; + default: + throw new AssertionError("Unknown primitive type " + type); + } } public static ValueType[] parseMany(String text) { diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java new file mode 100644 index 000000000..4b542a767 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java @@ -0,0 +1,295 @@ +package org.teavm.platform.plugin; + +import java.util.*; +import org.teavm.model.*; +import org.teavm.model.instructions.*; +import org.teavm.platform.metadata.Resource; +import org.teavm.platform.metadata.ResourceArray; +import org.teavm.platform.metadata.ResourceMap; + +/** + * + * @author Alexey Andreev + */ +class ResourceProgramTransformer { + private ClassReaderSource innerSource; + private Program program; + private Set arrayItemsVars = new HashSet<>(); + + public ResourceProgramTransformer(ClassReaderSource innerSource, Program program) { + this.innerSource = innerSource; + this.program = program; + } + + public void transformProgram() { + for (int i = 0; i < program.basicBlockCount(); ++i) { + transformBasicBlock(program.basicBlockAt(i)); + } + if (!arrayItemsVars.isEmpty()) { + for (int i = 0; i < program.basicBlockCount(); ++i) { + postProcessBasicBlock(program.basicBlockAt(i)); + } + } + } + + private void transformBasicBlock(BasicBlock block) { + List instructions = block.getInstructions(); + for (int i = 0; i < instructions.size(); ++i) { + Instruction insn = instructions.get(i); + if (insn instanceof InvokeInstruction) { + InvokeInstruction invoke = (InvokeInstruction)insn; + List replacement = transformInvoke(invoke); + if (replacement != null) { + instructions.set(i, new EmptyInstruction()); + instructions.addAll(i, replacement); + i += replacement.size(); + } + } + } + } + + private void postProcessBasicBlock(BasicBlock block) { + List instructions = block.getInstructions(); + for (int i = 0; i < instructions.size(); ++i) { + Instruction insn = instructions.get(i); + if (!(insn instanceof CastInstruction)) { + continue; + } + CastInstruction cast = (CastInstruction)insn; + if (!arrayItemsVars.contains(cast.getReceiver())) { + continue; + } + if (!(cast.getTargetType() instanceof ValueType.Object)) { + continue; + } + String targetTypeName = ((ValueType.Object)cast.getTargetType()).getClassName(); + Variable var = cast.getValue(); + Variable recv = cast.getReceiver(); + switch (targetTypeName) { + case "java.lang.Integer": + instructions.set(i, castToWrapper(var, recv, int.class, Integer.class)); + break; + case "java.lang.Boolean": + instructions.set(i, castToWrapper(var, recv, boolean.class, Boolean.class)); + break; + case "java.lang.Byte": + instructions.set(i, castToWrapper(var, recv, byte.class, Byte.class)); + break; + case "java.lang.Short": + instructions.set(i, castToWrapper(var, recv, short.class, Short.class)); + break; + case "java.lang.Float": + instructions.set(i, castToWrapper(var, recv, float.class, Float.class)); + break; + case "java.lang.Double": + instructions.set(i, castToWrapper(var, recv, double.class, Double.class)); + break; + case "java.lang.String": { + InvokeInstruction castInvoke = new InvokeInstruction(); + castInvoke.setType(InvocationType.SPECIAL); + castInvoke.setMethod(new MethodReference(ResourceAccessor.class, "castToString", + Object.class, String.class)); + castInvoke.getArguments().add(var); + castInvoke.setReceiver(recv); + instructions.set(i, castInvoke); + break; + } + } + } + } + + private List transformInvoke(InvokeInstruction insn) { + if (insn.getType() != InvocationType.VIRTUAL) { + return null; + } + MethodReference method = insn.getMethod(); + if (method.getClassName().equals(ResourceArray.class.getName()) || + method.getClassName().equals(ResourceMap.class.getName())) { + if (method.getName().equals("get")) { + arrayItemsVars.add(insn.getReceiver()); + } + InvokeInstruction accessInsn = new InvokeInstruction(); + accessInsn.setType(InvocationType.SPECIAL); + ValueType[] types = new ValueType[method.getDescriptor().parameterCount() + 2]; + types[0] = ValueType.object("java.lang.Object"); + System.arraycopy(method.getDescriptor().getSignature(), 0, types, 1, + method.getDescriptor().parameterCount() + 1); + accessInsn.setMethod(new MethodReference(ResourceAccessor.class.getName(), method.getName(), types)); + accessInsn.getArguments().add(insn.getInstance()); + accessInsn.getArguments().addAll(insn.getArguments()); + accessInsn.setReceiver(insn.getReceiver()); + return Arrays.asList(accessInsn); + } + ClassReader iface = innerSource.get(method.getClassName()); + if (iface.getAnnotations().get(Resource.class.getName()) == null) { + return null; + } + if (method.getName().startsWith("get")) { + if (method.getName().length() > 3) { + return transformGetterInvocation(insn, getPropertyName(method.getName().substring(3))); + } + } else if (method.getName().startsWith("is")) { + if (method.getName().length() > 2) { + return transformGetterInvocation(insn, getPropertyName(method.getName().substring(2))); + } + } else if (method.getName().startsWith("set")) { + if (method.getName().length() > 3) { + return transformSetterInvocation(insn, getPropertyName(method.getName().substring(3))); + } + } + return null; + } + + private List transformGetterInvocation(InvokeInstruction insn, String property) { + if (insn.getReceiver() == null) { + return Collections.emptyList(); + } + ValueType type = insn.getMethod().getDescriptor().getResultType(); + List instructions = new ArrayList<>(); + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive)type).getKind()) { + case BOOLEAN: + getAndCastProperty(insn, property, instructions, boolean.class); + return instructions; + case BYTE: + getAndCastProperty(insn, property, instructions, byte.class); + return instructions; + case SHORT: + getAndCastProperty(insn, property, instructions, short.class); + return instructions; + case INTEGER: + getAndCastProperty(insn, property, instructions, int.class); + return instructions; + case FLOAT: + getAndCastProperty(insn, property, instructions, float.class); + return instructions; + case DOUBLE: + getAndCastProperty(insn, property, instructions, double.class); + return instructions; + case CHARACTER: + case LONG: + break; + } + } else if (type instanceof ValueType.Object) { + switch (((ValueType.Object)type).getClassName()) { + case "java.lang.Boolean": + getAndCastPropertyToWrapper(insn, property, instructions, boolean.class, Boolean.class); + return instructions; + case "java.lang.Byte": + getAndCastPropertyToWrapper(insn, property, instructions, byte.class, Byte.class); + return instructions; + case "java.lang.Short": + getAndCastPropertyToWrapper(insn, property, instructions, short.class, Short.class); + return instructions; + case "java.lang.Integer": + getAndCastPropertyToWrapper(insn, property, instructions, int.class, Integer.class); + return instructions; + case "java.lang.Float": + getAndCastPropertyToWrapper(insn, property, instructions, float.class, Float.class); + return instructions; + case "java.lang.Double": + getAndCastPropertyToWrapper(insn, property, instructions, double.class, Double.class); + return instructions; + case "java.lang.String": { + Variable resultVar = insn.getProgram().createVariable(); + getProperty(insn, property, instructions, resultVar); + InvokeInstruction castInvoke = new InvokeInstruction(); + castInvoke.setType(InvocationType.SPECIAL); + castInvoke.setMethod(new MethodReference(ResourceAccessor.class, "castToString", + Object.class, String.class)); + castInvoke.getArguments().add(resultVar); + castInvoke.setReceiver(insn.getReceiver()); + instructions.add(castInvoke); + return instructions; + } + default: { + Variable resultVar = insn.getProgram().createVariable(); + getProperty(insn, property, instructions, resultVar); + CastInstruction castInsn = new CastInstruction(); + castInsn.setReceiver(insn.getReceiver()); + castInsn.setTargetType(type); + castInsn.setValue(resultVar); + instructions.add(castInsn); + return instructions; + } + } + } + return null; + } + + private void getProperty(InvokeInstruction insn, String property, List instructions, + Variable resultVar) { + Variable nameVar = program.createVariable(); + StringConstantInstruction nameInsn = new StringConstantInstruction(); + nameInsn.setConstant(property); + nameInsn.setReceiver(nameVar); + instructions.add(nameInsn); + InvokeInstruction accessorInvoke = new InvokeInstruction(); + accessorInvoke.setType(InvocationType.SPECIAL); + accessorInvoke.setMethod(new MethodReference(ResourceAccessor.class, "get", + Object.class, String.class, Object.class)); + accessorInvoke.getArguments().add(insn.getInstance()); + accessorInvoke.getArguments().add(nameVar); + accessorInvoke.setReceiver(resultVar); + instructions.add(accessorInvoke); + } + + private void getAndCastProperty(InvokeInstruction insn, String property, List instructions, + Class primitive) { + Variable resultVar = program.createVariable(); + getProperty(insn, property, instructions, resultVar); + InvokeInstruction castInvoke = new InvokeInstruction(); + castInvoke.setType(InvocationType.SPECIAL); + String primitiveCapitalized = primitive.getName(); + primitiveCapitalized = Character.toUpperCase(primitiveCapitalized.charAt(0)) + + primitiveCapitalized.substring(1); + castInvoke.setMethod(new MethodReference(ResourceAccessor.class, "castTo" + primitiveCapitalized, + Object.class, primitive)); + castInvoke.getArguments().add(resultVar); + castInvoke.setReceiver(insn.getReceiver()); + instructions.add(castInvoke); + } + + private Instruction castToWrapper(Variable var, Variable receiver, Class primitive, Class wrapper) { + InvokeInstruction castInvoke = new InvokeInstruction(); + castInvoke.setType(InvocationType.SPECIAL); + String primitiveCapitalized = primitive.getName(); + primitiveCapitalized = Character.toUpperCase(primitiveCapitalized.charAt(0)) + + primitiveCapitalized.substring(1); + castInvoke.setMethod(new MethodReference(ResourceAccessor.class, "castTo" + primitiveCapitalized + "Wrapper", + Object.class, wrapper)); + castInvoke.getArguments().add(var); + castInvoke.setReceiver(receiver); + return castInvoke; + } + + private void getAndCastPropertyToWrapper(InvokeInstruction insn, String property, List instructions, + Class primitive, Class wrapper) { + Variable resultVar = program.createVariable(); + getProperty(insn, property, instructions, resultVar); + InvokeInstruction castInvoke = new InvokeInstruction(); + castInvoke.setType(InvocationType.SPECIAL); + String primitiveCapitalized = primitive.getName(); + primitiveCapitalized = Character.toUpperCase(primitiveCapitalized.charAt(0)) + + primitiveCapitalized.substring(1); + castInvoke.setMethod(new MethodReference(ResourceAccessor.class, "castTo" + primitiveCapitalized + "Wrapper", + Object.class, wrapper)); + castInvoke.getArguments().add(resultVar); + castInvoke.setReceiver(insn.getReceiver()); + instructions.add(castInvoke); + } + + private List transformSetterInvocation(InvokeInstruction insn, String property) { + return null; + } + + private String getPropertyName(String name) { + if (name.length() == 1) { + return name.toLowerCase(); + } + if (Character.isUpperCase(name.charAt(1))) { + return name; + } + return Character.toLowerCase(name.charAt(0)) + name.substring(1); + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceTransformer.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceTransformer.java index 9e33c292f..f0b8854d2 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceTransformer.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceTransformer.java @@ -15,15 +15,7 @@ */ package org.teavm.platform.plugin; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; import org.teavm.model.*; -import org.teavm.model.instructions.*; -import org.teavm.platform.metadata.Resource; -import org.teavm.platform.metadata.ResourceArray; -import org.teavm.platform.metadata.ResourceMap; /** * @@ -35,200 +27,8 @@ class ResourceTransformer implements ClassHolderTransformer { for (MethodHolder method : cls.getMethods()) { Program program = method.getProgram(); if (program != null) { - transformProgram(innerSource, program); + new ResourceProgramTransformer(innerSource, program).transformProgram(); } } } - - private void transformProgram(ClassReaderSource innerSource, Program program) { - for (int i = 0; i < program.basicBlockCount(); ++i) { - transformBasicBlock(innerSource, program.basicBlockAt(i)); - } - } - - private void transformBasicBlock(ClassReaderSource innerSource, BasicBlock block) { - List instructions = block.getInstructions(); - for (int i = 0; i < instructions.size(); ++i) { - Instruction insn = instructions.get(i); - if (insn instanceof InvokeInstruction) { - InvokeInstruction invoke = (InvokeInstruction)insn; - List replacement = transformInvoke(innerSource, invoke); - if (replacement != null) { - instructions.set(i, new EmptyInstruction()); - instructions.addAll(i, replacement); - i += replacement.size(); - } - } - } - } - - private List transformInvoke(ClassReaderSource innerSource, InvokeInstruction insn) { - if (insn.getType() != InvocationType.VIRTUAL) { - return null; - } - MethodReference method = insn.getMethod(); - if (method.getClassName().equals(ResourceArray.class.getName()) || - method.getClassName().equals(ResourceMap.class.getName())) { - InvokeInstruction accessInsn = new InvokeInstruction(); - accessInsn.setType(InvocationType.SPECIAL); - ValueType[] types = new ValueType[method.getDescriptor().parameterCount() + 2]; - types[0] = ValueType.object("java.lang.Object"); - System.arraycopy(method.getDescriptor().getSignature(), 0, types, 1, - method.getDescriptor().parameterCount() + 1); - accessInsn.setMethod(new MethodReference(ResourceAccessor.class.getName(), method.getName(), types)); - accessInsn.getArguments().add(insn.getInstance()); - accessInsn.getArguments().addAll(insn.getArguments()); - accessInsn.setReceiver(insn.getReceiver()); - return Arrays.asList(accessInsn); - } - ClassReader iface = innerSource.get(method.getClassName()); - if (iface.getAnnotations().get(Resource.class.getName()) == null) { - return null; - } - if (method.getName().startsWith("get")) { - if (method.getName().length() > 3) { - return transformGetterInvocation(insn, getPropertyName(method.getName().substring(3))); - } - } else if (method.getName().startsWith("is")) { - if (method.getName().length() > 2) { - return transformGetterInvocation(insn, getPropertyName(method.getName().substring(2))); - } - } else if (method.getName().startsWith("set")) { - if (method.getName().length() > 3) { - return transformSetterInvocation(insn, getPropertyName(method.getName().substring(3))); - } - } - return null; - } - - private List transformGetterInvocation(InvokeInstruction insn, String property) { - if (insn.getReceiver() == null) { - return Collections.emptyList(); - } - ValueType type = insn.getMethod().getDescriptor().getResultType(); - List instructions = new ArrayList<>(); - if (type instanceof ValueType.Primitive) { - switch (((ValueType.Primitive)type).getKind()) { - case BOOLEAN: - getAndCastProperty(insn, property, instructions, boolean.class); - return instructions; - case BYTE: - getAndCastProperty(insn, property, instructions, byte.class); - return instructions; - case SHORT: - getAndCastProperty(insn, property, instructions, short.class); - return instructions; - case INTEGER: - getAndCastProperty(insn, property, instructions, int.class); - return instructions; - case FLOAT: - getAndCastProperty(insn, property, instructions, float.class); - return instructions; - case DOUBLE: - getAndCastProperty(insn, property, instructions, double.class); - return instructions; - case CHARACTER: - case LONG: - break; - } - } else if (type instanceof ValueType.Object) { - switch (((ValueType.Object)type).getClassName()) { - case "java.lang.Boolean": - getAndCastPropertyToWrapper(insn, property, instructions, boolean.class, Boolean.class); - return instructions; - case "java.lang.Byte": - getAndCastPropertyToWrapper(insn, property, instructions, byte.class, Byte.class); - return instructions; - case "java.lang.Short": - getAndCastPropertyToWrapper(insn, property, instructions, short.class, Short.class); - return instructions; - case "java.lang.Integer": - getAndCastPropertyToWrapper(insn, property, instructions, int.class, Integer.class); - return instructions; - case "java.lang.Float": - getAndCastPropertyToWrapper(insn, property, instructions, float.class, Float.class); - return instructions; - case "java.lang.Double": - getAndCastPropertyToWrapper(insn, property, instructions, double.class, Double.class); - return instructions; - default: { - Variable resultVar = insn.getProgram().createVariable(); - getProperty(insn, property, instructions, resultVar); - CastInstruction castInsn = new CastInstruction(); - castInsn.setReceiver(insn.getReceiver()); - castInsn.setTargetType(type); - castInsn.setValue(resultVar); - instructions.add(castInsn); - return instructions; - } - } - } - return null; - } - - private void getProperty(InvokeInstruction insn, String property, List instructions, - Variable resultVar) { - Program program = insn.getProgram(); - Variable nameVar = program.createVariable(); - StringConstantInstruction nameInsn = new StringConstantInstruction(); - nameInsn.setConstant(property); - nameInsn.setReceiver(nameVar); - instructions.add(nameInsn); - InvokeInstruction accessorInvoke = new InvokeInstruction(); - accessorInvoke.setType(InvocationType.SPECIAL); - accessorInvoke.setMethod(new MethodReference(ResourceAccessor.class, "get", - Object.class, String.class, Object.class)); - accessorInvoke.getArguments().add(insn.getInstance()); - accessorInvoke.getArguments().add(nameVar); - accessorInvoke.setReceiver(resultVar); - instructions.add(accessorInvoke); - } - - private void getAndCastProperty(InvokeInstruction insn, String property, List instructions, - Class primitive) { - Program program = insn.getProgram(); - Variable resultVar = program.createVariable(); - getProperty(insn, property, instructions, resultVar); - InvokeInstruction castInvoke = new InvokeInstruction(); - castInvoke.setType(InvocationType.SPECIAL); - String primitiveCapitalized = primitive.getName(); - primitiveCapitalized = Character.toUpperCase(primitiveCapitalized.charAt(0)) + - primitiveCapitalized.substring(1); - castInvoke.setMethod(new MethodReference(ResourceAccessor.class, "castTo" + primitiveCapitalized, - Object.class, primitive)); - castInvoke.getArguments().add(resultVar); - castInvoke.setReceiver(insn.getReceiver()); - instructions.add(castInvoke); - } - - private void getAndCastPropertyToWrapper(InvokeInstruction insn, String property, List instructions, - Class primitive, Class wrapper) { - Program program = insn.getProgram(); - Variable resultVar = program.createVariable(); - getProperty(insn, property, instructions, resultVar); - InvokeInstruction castInvoke = new InvokeInstruction(); - castInvoke.setType(InvocationType.SPECIAL); - String primitiveCapitalized = primitive.getName(); - primitiveCapitalized = Character.toUpperCase(primitiveCapitalized.charAt(0)) + - primitiveCapitalized.substring(1); - castInvoke.setMethod(new MethodReference(ResourceAccessor.class, "castTo" + primitiveCapitalized + "Wrapper", - Object.class, wrapper)); - castInvoke.getArguments().add(resultVar); - castInvoke.setReceiver(insn.getReceiver()); - instructions.add(castInvoke); - } - - private List transformSetterInvocation(InvokeInstruction insn, String property) { - return null; - } - - private String getPropertyName(String name) { - if (name.length() == 1) { - return name.toLowerCase(); - } - if (Character.isUpperCase(name.charAt(1))) { - return name; - } - return Character.toLowerCase(name.charAt(0)) + name.substring(1); - } }