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 ad4bf808a..ec59b1299 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 @@ -61,5 +61,44 @@ public class MetadataGeneratorTest { assertEquals("baz", res.getArrayB().get(0).getBar()); assertNull(res.getArrayC()); } + + @MetadataProvider(TestResourceGenerator.class) + private native TestResource getEmptyResource(); + + @Test + public void resourceDefaultsSet() { + TestResource res = getEmptyResource(); + assertEquals(0, res.getA()); + assertFalse(res.getB()); + assertEquals(0, res.getD()); + assertEquals(0, res.getE()); + assertEquals(0, res.getF(), 1E-10); + assertEquals(0, res.getG(), 1E-10); + assertNull(res.getFoo()); + assertNull(res.getArrayA()); + assertNull(res.getArrayB()); + assertNull(res.getArrayC()); + assertNull(res.getMapA()); + assertNull(res.getMapB()); + assertNull(res.getMapC()); + } + + @Test + public void resourceModifiedInRunTime() { + TestResource res = getEmptyResource(); + res.setA(23); + res.setB(true); + res.setD((byte)24); + res.setE((short)25); + res.setF(3.14f); + res.setG(2.72); + + assertEquals(23, res.getA()); + assertTrue(res.getB()); + assertEquals(24, res.getD()); + assertEquals(25, res.getE()); + assertEquals(3.14, res.getF(), 0.001); + assertEquals(2.72, res.getG(), 0.001); + } } 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 6edf353ce..43f672805 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 @@ -31,6 +31,8 @@ public class TestResourceGenerator implements MetadataGenerator { return createInt(context, 23); case "getResource": return getResource(context); + case "getEmptyResource": + return context.createResource(TestResource.class); default: throw new RuntimeException("Unsupported method: " + method); } diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java index 9e962512a..a936fee0f 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java @@ -62,6 +62,7 @@ class BuildTimeResourceProxyBuilder { Map methods = new HashMap<>(); private Map propertyIndexes = new HashMap<>(); private Object[] initialData; + private Map> propertyTypes = new HashMap<>(); public ProxyFactoryCreation(Class iface) { this.rootIface = iface; @@ -73,12 +74,24 @@ class BuildTimeResourceProxyBuilder { " that is not an interface"); } scanIface(rootIface); + + // Fill default values + initialData = new Object[propertyIndexes.size()]; + for (Map.Entry> property : propertyTypes.entrySet()) { + String propertyName = property.getKey(); + Class propertyType = property.getValue(); + initialData[propertyIndexes.get(propertyName)] = defaultValues.get(propertyType); + } + + // Generate write method Method writeMethod; try { writeMethod = ResourceWriter.class.getMethod("write", SourceWriter.class); } catch (NoSuchMethodException e) { throw new AssertionError("Method must exist", e); } + + // Create factory String[] properties = new String[propertyIndexes.size()]; for (Map.Entry entry : propertyIndexes.entrySet()) { properties[entry.getValue()] = entry.getKey(); @@ -129,18 +142,19 @@ class BuildTimeResourceProxyBuilder { } } - // Verify types of properties and fill default values - initialData = new Object[propertyIndexes.size()]; + // Verify types of properties for (Map.Entry> property : getters.entrySet()) { String propertyName = property.getKey(); Class propertyType = property.getValue(); if (!allowedPropertyTypes.contains(propertyType)) { if (!propertyType.isInterface() || !Resource.class.isAssignableFrom(propertyType)) { - throw new IllegalArgumentException("Property " + iface.getName() + "." + propertyName + + throw new IllegalArgumentException("Property " + rootIface.getName() + "." + propertyName + " has an illegal type " + propertyType.getName()); } } - initialData[propertyIndexes.get(propertyName)] = defaultValues.get(propertyType); + if (!propertyTypes.containsKey(propertyName)) { + propertyTypes.put(propertyName, propertyType); + } } // Scan superinterfaces diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorGenerator.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorGenerator.java index 73e142c5c..1eaa46a52 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorGenerator.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorGenerator.java @@ -52,7 +52,7 @@ class ResourceAccessorGenerator implements Injector { context.writeExpr(context.getArgument(0)); writePropertyAccessor(context, context.getArgument(1)); } - context.getWriter().append(']').ws().append('=').ws(); + context.getWriter().ws().append('=').ws(); context.writeExpr(context.getArgument(2)); context.getWriter().append(')'); break; @@ -87,14 +87,20 @@ class ResourceAccessorGenerator implements Injector { context.writeExpr(context.getArgument(0)); break; case "castToString": + context.getWriter().append('('); + context.writeExpr(context.getArgument(0)); + context.getWriter().ws().append("!==").ws().append("null").ws().append("?").ws(); context.getWriter().append("$rt_str("); context.writeExpr(context.getArgument(0)); - context.getWriter().append(")"); + context.getWriter().append(")").ws().append(':').ws().append("null)"); break; case "castFromString": + context.getWriter().append('('); + context.writeExpr(context.getArgument(0)); + context.getWriter().ws().append("!==").ws().append("null").ws().append("?").ws(); context.getWriter().append("$rt_ustr("); context.writeExpr(context.getArgument(0)); - context.getWriter().append(")"); + context.getWriter().append(")").ws().append(':').ws().append("null)"); break; } } 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 index 798031e2a..56e51343b 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java @@ -191,9 +191,88 @@ class ResourceProgramTransformer { } private List transformSetterInvocation(InvokeInstruction insn, String property) { + ValueType type = insn.getMethod().getDescriptor().parameterType(0); + List instructions = new ArrayList<>(); + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive)type).getKind()) { + case BOOLEAN: + castAndSetProperty(insn, property, instructions, boolean.class); + return instructions; + case BYTE: + castAndSetProperty(insn, property, instructions, byte.class); + return instructions; + case SHORT: + castAndSetProperty(insn, property, instructions, short.class); + return instructions; + case INTEGER: + castAndSetProperty(insn, property, instructions, int.class); + return instructions; + case FLOAT: + castAndSetProperty(insn, property, instructions, float.class); + return instructions; + case DOUBLE: + castAndSetProperty(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.String": { + Variable castVar = insn.getProgram().createVariable(); + InvokeInstruction castInvoke = new InvokeInstruction(); + castInvoke.setType(InvocationType.SPECIAL); + castInvoke.setMethod(new MethodReference(ResourceAccessor.class, "castFromString", + String.class, Object.class)); + castInvoke.getArguments().add(insn.getArguments().get(0)); + castInvoke.setReceiver(castVar); + instructions.add(castInvoke); + setProperty(insn, property, instructions, castVar); + return instructions; + } + default: { + setProperty(insn, property, instructions, insn.getArguments().get(0)); + return instructions; + } + } + } return null; } + private void setProperty(InvokeInstruction insn, String property, List instructions, + Variable valueVar) { + 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, "put", + Object.class, String.class, Object.class, void.class)); + accessorInvoke.getArguments().add(insn.getInstance()); + accessorInvoke.getArguments().add(nameVar); + accessorInvoke.getArguments().add(valueVar); + instructions.add(accessorInvoke); + } + + private void castAndSetProperty(InvokeInstruction insn, String property, List instructions, + Class primitive) { + Variable castVar = program.createVariable(); + 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, "castFrom" + primitiveCapitalized, + primitive, Object.class)); + castInvoke.getArguments().add(insn.getArguments().get(0)); + castInvoke.setReceiver(castVar); + instructions.add(castInvoke); + setProperty(insn, property, instructions, castVar); + } + private String getPropertyName(String name) { if (name.length() == 1) { return name.toLowerCase();