diff --git a/teavm-core/src/main/java/org/teavm/codegen/SourceWriter.java b/teavm-core/src/main/java/org/teavm/codegen/SourceWriter.java index 8551956cd..1ca23eed4 100644 --- a/teavm-core/src/main/java/org/teavm/codegen/SourceWriter.java +++ b/teavm-core/src/main/java/org/teavm/codegen/SourceWriter.java @@ -31,10 +31,13 @@ public class SourceWriter implements Appendable { private NamingStrategy naming; private boolean lineStart; private boolean minified; + private int lineWidth; + private int pos; - SourceWriter(NamingStrategy naming, Appendable innerWriter) { + SourceWriter(NamingStrategy naming, Appendable innerWriter, int lineWidth) { this.naming = naming; this.innerWriter = innerWriter; + this.lineWidth = lineWidth; } void setMinified(boolean minified) { @@ -42,8 +45,7 @@ public class SourceWriter implements Appendable { } public SourceWriter append(String value) throws IOException { - appendIndent(); - innerWriter.append(value); + append((CharSequence)value); return this; } @@ -59,23 +61,43 @@ public class SourceWriter implements Appendable { public SourceWriter append(char value) throws IOException { appendIndent(); innerWriter.append(value); + if (value == '\n') { + newLine(); + } else { + pos++; + } return this; } @Override public SourceWriter append(CharSequence csq) throws IOException { - appendIndent(); - innerWriter.append(csq); + append(csq, 0, csq.length()); return this; } @Override public SourceWriter append(CharSequence csq, int start, int end) throws IOException { - appendIndent(); - innerWriter.append(csq, start, end); + int last = start; + for (int i = start; i < end; ++i) { + if (csq.charAt(i) == '\n') { + appendSingleLine(csq, last, i); + newLine(); + last = i; + } + } + appendSingleLine(csq, last, end); return this; } + private void appendSingleLine(CharSequence csq, int start, int end) throws IOException { + if (start == end) { + return; + } + appendIndent(); + pos += end - start; + innerWriter.append(csq, start, end); + } + public SourceWriter appendClass(String cls) throws NamingException, IOException { return append(naming.getNameFor(cls)); } @@ -109,6 +131,7 @@ public class SourceWriter implements Appendable { if (lineStart) { for (int i = 0; i < indentSize; ++i) { innerWriter.append(" "); + pos += 4; } lineStart = false; } @@ -116,13 +139,26 @@ public class SourceWriter implements Appendable { public SourceWriter newLine() throws IOException{ innerWriter.append('\n'); + pos = 0; lineStart = true; return this; } - public SourceWriter ws() throws IOException{ - if (!minified) { - innerWriter.append(' '); + public SourceWriter ws() throws IOException { + if (pos >= lineWidth) { + newLine(); + } else { + if (!minified) { + innerWriter.append(' '); + pos++; + } + } + return this; + } + + public SourceWriter tokenBoundary() throws IOException { + if (pos >= lineWidth) { + newLine(); } return this; } @@ -130,6 +166,7 @@ public class SourceWriter implements Appendable { public SourceWriter softNewLine() throws IOException{ if (!minified) { innerWriter.append('\n'); + pos = 0; lineStart = true; } return this; diff --git a/teavm-core/src/main/java/org/teavm/codegen/SourceWriterBuilder.java b/teavm-core/src/main/java/org/teavm/codegen/SourceWriterBuilder.java index f3748421c..35796b388 100644 --- a/teavm-core/src/main/java/org/teavm/codegen/SourceWriterBuilder.java +++ b/teavm-core/src/main/java/org/teavm/codegen/SourceWriterBuilder.java @@ -22,6 +22,7 @@ package org.teavm.codegen; public class SourceWriterBuilder { private NamingStrategy naming; private boolean minified; + private int lineWidth = 512; public SourceWriterBuilder(NamingStrategy naming) { this.naming = naming; @@ -35,8 +36,12 @@ public class SourceWriterBuilder { this.minified = minified; } + public void setLineWidth(int lineWidth) { + this.lineWidth = lineWidth; + } + public SourceWriter build(Appendable innerWriter) { - SourceWriter writer = new SourceWriter(naming, innerWriter); + SourceWriter writer = new SourceWriter(naming, innerWriter, lineWidth); writer.setMinified(minified); return writer; } diff --git a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java index 8fef2ed31..dbb2986b8 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java @@ -43,6 +43,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext private ClassLoader classLoader; private boolean minifying; private Map injectorMap = new HashMap<>(); + private Properties properties = new Properties(); private static class InjectorHolder { public final Injector injector; @@ -88,6 +89,16 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext return classLoader; } + @Override + public Properties getProperties() { + return new Properties(properties); + } + + public void setProperties(Properties properties) { + this.properties.clear(); + this.properties.putAll(properties); + } + public void renderRuntime() throws RenderingException { try { renderRuntimeCls(); @@ -483,6 +494,16 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext public ListableClassReaderSource getClassSource() { return classSource; } + + @Override + public ClassLoader getClassLoader() { + return classLoader; + } + + @Override + public Properties getProperties() { + return null; + } } @Override diff --git a/teavm-core/src/main/java/org/teavm/javascript/RenderingContext.java b/teavm-core/src/main/java/org/teavm/javascript/RenderingContext.java index 2b15f5684..48e2b6cae 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/RenderingContext.java +++ b/teavm-core/src/main/java/org/teavm/javascript/RenderingContext.java @@ -15,6 +15,7 @@ */ package org.teavm.javascript; +import java.util.Properties; import org.teavm.codegen.NamingStrategy; import org.teavm.codegen.SourceWriter; import org.teavm.model.ListableClassReaderSource; @@ -33,4 +34,6 @@ public interface RenderingContext { ListableClassReaderSource getClassSource(); ClassLoader getClassLoader(); + + Properties getProperties(); } diff --git a/teavm-core/src/main/java/org/teavm/javascript/ni/GeneratorContext.java b/teavm-core/src/main/java/org/teavm/javascript/ni/GeneratorContext.java index 9082b8819..d54363239 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ni/GeneratorContext.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ni/GeneratorContext.java @@ -15,6 +15,7 @@ */ package org.teavm.javascript.ni; +import java.util.Properties; import org.teavm.model.ListableClassReaderSource; /** @@ -25,4 +26,8 @@ public interface GeneratorContext { String getParameterName(int index); ListableClassReaderSource getClassSource(); + + ClassLoader getClassLoader(); + + Properties getProperties(); } diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceArray.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceArray.java index c8aca068d..43d1ae312 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceArray.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceArray.java @@ -15,15 +15,17 @@ */ package org.teavm.platform.plugin; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import org.teavm.codegen.SourceWriter; import org.teavm.platform.metadata.ResourceArray; /** * * @author Alexey Andreev */ -class BuildTimeResourceArray implements ResourceArray { +class BuildTimeResourceArray implements ResourceArray, ResourceWriter { private List data = new ArrayList<>(); @Override @@ -40,4 +42,16 @@ class BuildTimeResourceArray implements ResourceArray { public void add(T elem) { data.add(elem); } + + @Override + public void write(SourceWriter writer) throws IOException { + writer.append('[').tokenBoundary(); + for (int i = 0; i < data.size(); ++i) { + if (i > 0) { + writer.append(',').ws(); + } + ResourceWriterHelper.write(writer, data.get(i)); + } + writer.append(']').tokenBoundary(); + } } diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceMap.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceMap.java index 36174df46..213b2c479 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceMap.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceMap.java @@ -15,15 +15,17 @@ */ package org.teavm.platform.plugin; +import java.io.IOException; import java.util.HashMap; import java.util.Map; +import org.teavm.codegen.SourceWriter; import org.teavm.platform.metadata.ResourceMap; /** * * @author Alexey Andreev */ -class BuildTimeResourceMap implements ResourceMap { +class BuildTimeResourceMap implements ResourceMap, ResourceWriter { private Map data = new HashMap<>(); @Override @@ -40,4 +42,20 @@ class BuildTimeResourceMap implements ResourceMap { public void put(String key, T value) { data.put(key, value); } + + @Override + public void write(SourceWriter writer) throws IOException { + writer.append('{'); + boolean first = true; + for (Map.Entry entry : data.entrySet()) { + if (!first) { + writer.append(",").ws(); + } + first = false; + ResourceWriterHelper.writeString(writer, entry.getKey()); + writer.ws().append(':').ws(); + ResourceWriterHelper.write(writer, entry.getValue()); + } + writer.append('}').tokenBoundary(); + } } 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 b5a09e757..0baf44b47 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 @@ -31,6 +31,16 @@ class BuildTimeResourceProxyBuilder { boolean.class, Boolean.class, byte.class, Byte.class, short.class, Short.class, int.class, Integer.class, float.class, Float.class, double.class, Double.class, String.class, ResourceArray.class, ResourceMap.class)); + private static Map, Object> defaultValues = new HashMap<>(); + + static { + defaultValues.put(boolean.class, false); + defaultValues.put(byte.class, (byte)0); + defaultValues.put(short.class, (short)0); + defaultValues.put(int.class, 0); + defaultValues.put(float.class, 0F); + defaultValues.put(double.class, 0.0); + } public BuildTimeResourceProxy buildProxy(Class iface) { BuildTimeResourceProxyFactory factory = factories.get(iface); @@ -50,8 +60,8 @@ class BuildTimeResourceProxyBuilder { Map> getters = new HashMap<>(); Map> setters = new HashMap<>(); Map methods = new HashMap<>(); - private List initialData = new ArrayList<>(); private Map propertyIndexes = new HashMap<>(); + private Object[] initialData; public ProxyFactoryCreation(Class iface) { this.rootIface = iface; @@ -63,7 +73,7 @@ class BuildTimeResourceProxyBuilder { " that is not an interface"); } scanIface(rootIface); - return new BuildTimeResourceProxyFactory(methods, initialData.toArray(new Object[initialData.size()])); + return new BuildTimeResourceProxyFactory(methods, initialData); } private void scanIface(Class iface) { @@ -109,18 +119,18 @@ class BuildTimeResourceProxyBuilder { } // Verify types of properties and fill default values + initialData = new Object[propertyIndexes.size()]; for (Map.Entry> property : getters.entrySet()) { String propertyName = property.getKey(); Class propertyType = property.getValue(); - if (allowedPropertyTypes.contains(propertyType)) { - continue; - } - if (!propertyType.isInterface() || !propertyType.isAnnotationPresent(Resource.class)) { - throw new IllegalArgumentException("Property " + iface.getName() + "." + propertyName + - " has an illegal type " + propertyType.getName()); + if (!allowedPropertyTypes.contains(propertyType)) { + if (!propertyType.isInterface() || !propertyType.isAnnotationPresent(Resource.class)) { + throw new IllegalArgumentException("Property " + iface.getName() + "." + propertyName + + " has an illegal type " + propertyType.getName()); + } } + initialData[propertyIndexes.get(propertyName)] = defaultValues.get(propertyType); } - // TODO: fill default values // Scan superinterfaces for (Class superIface : iface.getInterfaces()) { diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceWriterMethod.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceWriterMethod.java new file mode 100644 index 000000000..dead2901c --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceWriterMethod.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014 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.platform.plugin; + +import org.teavm.codegen.SourceWriter; + +/** + * + * @author Alexey Andreev + */ +class BuildTimeResourceWriterMethod implements BuildTimeResourceMethod { + private String[] propertyNames; + + public BuildTimeResourceWriterMethod(String[] propertyNames) { + this.propertyNames = propertyNames; + } + + @Override + public Object invoke(BuildTimeResourceProxy proxy, Object[] args) throws Throwable { + SourceWriter writer = (SourceWriter)args[0]; + writer.append('{'); + for (int i = 0; i < propertyNames.length; ++i) { + if (i > 0) { + writer.append(',').ws(); + } + ResourceWriterHelper.writeString(writer, propertyNames[i]); + writer.ws().append(':').ws(); + ResourceWriterHelper.write(writer, proxy.data[i]); + } + writer.append('}').tokenBoundary(); + return null; + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/DefaultMetadataGeneratorContext.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/DefaultMetadataGeneratorContext.java index 608f7fbfe..cd38cc840 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/DefaultMetadataGeneratorContext.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/DefaultMetadataGeneratorContext.java @@ -32,7 +32,7 @@ class DefaultMetadataGeneratorContext implements MetadataGeneratorContext { private Properties properties; private BuildTimeResourceProxyBuilder proxyBuilder = new BuildTimeResourceProxyBuilder(); - private DefaultMetadataGeneratorContext(ListableClassReaderSource classSource, ClassLoader classLoader, + public DefaultMetadataGeneratorContext(ListableClassReaderSource classSource, ClassLoader classLoader, Properties properties) { this.classSource = classSource; this.classLoader = classLoader; @@ -56,7 +56,8 @@ class DefaultMetadataGeneratorContext implements MetadataGeneratorContext { @Override public T createResource(Class resourceType) { - return resourceType.cast(Proxy.newProxyInstance(classLoader, new Class[] { resourceType }, + return resourceType.cast(Proxy.newProxyInstance(classLoader, + new Class[] { resourceType, ResourceWriter.class }, proxyBuilder.buildProxy(resourceType))); } diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/MetadataProviderNativeGenerator.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/MetadataProviderNativeGenerator.java index 9d9c2a8dd..2d43695e4 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/MetadataProviderNativeGenerator.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/MetadataProviderNativeGenerator.java @@ -16,19 +16,23 @@ package org.teavm.platform.plugin; import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import org.teavm.codegen.SourceWriter; import org.teavm.javascript.ni.Generator; import org.teavm.javascript.ni.GeneratorContext; import org.teavm.model.*; +import org.teavm.platform.metadata.MetadataGenerator; import org.teavm.platform.metadata.MetadataProvider; /** * * @author Alexey Andreev */ -class MetadataProviderNativeGenerator implements Generator { +public class MetadataProviderNativeGenerator implements Generator { @Override public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { + // Validate method ClassReader cls = context.getClassSource().get(methodRef.getClassName()); MethodReader method = cls.getMethod(methodRef.getDescriptor()); AnnotationReader providerAnnot = method.getAnnotations().get(MetadataProvider.class.getName()); @@ -39,5 +43,43 @@ class MetadataProviderNativeGenerator implements Generator { throw new IllegalStateException("Method " + method.getReference() + " was marked with " + MetadataProvider.class.getName() + " but it is not native"); } + + // Find and instantiate metadata generator + ValueType generatorType = providerAnnot.getValue("value").getJavaClass(); + String generatorClassName = ((ValueType.Object)generatorType).getClassName(); + Class generatorClass; + try { + generatorClass = Class.forName(generatorClassName, true, context.getClassLoader()); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Can't find metadata generator class: " + generatorClassName, e); + } + Constructor cons; + try { + cons = generatorClass.getConstructor(); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Metadata generator " + generatorClassName + " does not have a public " + + "no-arg constructor", e); + } + MetadataGenerator generator; + try { + generator = (MetadataGenerator)cons.newInstance(); + } catch (IllegalAccessException | InstantiationException e) { + throw new RuntimeException("Error instantiating metadata generator " + generatorClassName, e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Error instantiating metadata generator " + generatorClassName, + e.getTargetException()); + } + DefaultMetadataGeneratorContext metadataContext = new DefaultMetadataGeneratorContext(context.getClassSource(), + context.getClassLoader(), context.getProperties()); + + // Generate resource loader + Object resource = generator.generateMetadata(metadataContext, methodRef); + writer.append("if (!").appendMethodBody(methodRef).append("$$resource === undefined").append(") {") + .indent().softNewLine(); + writer.appendMethodBody(methodRef).append("$$resource = "); + ResourceWriterHelper.write(writer, resource); + writer.append(';').softNewLine(); + writer.outdent().append('}').softNewLine(); + writer.append("return ").appendMethodBody(methodRef).append("$$resource;").softNewLine(); } } diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceWriter.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceWriter.java new file mode 100644 index 000000000..5f1be5689 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceWriter.java @@ -0,0 +1,27 @@ +/* + * Copyright 2014 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.platform.plugin; + +import java.io.IOException; +import org.teavm.codegen.SourceWriter; + +/** + * + * @author Alexey Andreev + */ +interface ResourceWriter { + void write(SourceWriter writer) throws IOException; +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceWriterHelper.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceWriterHelper.java new file mode 100644 index 000000000..b263a3f24 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceWriterHelper.java @@ -0,0 +1,74 @@ +/* + * Copyright 2014 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.platform.plugin; + +import java.io.IOException; +import org.teavm.codegen.SourceWriter; + +/** + * + * @author Alexey Andreev + */ +final class ResourceWriterHelper { + public static void write(SourceWriter writer, Object resource) throws IOException { + if (resource == null) { + writer.append("null"); + } else { + if (resource instanceof ResourceWriter) { + ((ResourceWriter)resource).write(writer); + } else if (resource instanceof Number) { + writer.append(resource); + } else if (resource instanceof Boolean) { + writer.append(resource == Boolean.TRUE ? "true" : "false"); + } else if (resource instanceof String) { + writeString(writer, (String)resource); + } else { + throw new RuntimeException("Error compiling resources. Value of illegal type found: " + + resource.getClass()); + } + } + } + + public static void writeString(SourceWriter writer, String s) throws IOException { + writer.append('"'); + for (int i = 0; i < s.length(); ++i) { + char c = s.charAt(i); + switch (c) { + case '\0': + writer.append("\\0"); + break; + case '\n': + writer.append("\\n"); + break; + case '\r': + writer.append("\\r"); + break; + case '\t': + writer.append("\\t"); + break; + default: + if (c < 32) { + writer.append("\\u00").append(Character.forDigit(c / 16, 16)) + .append(Character.forDigit(c % 16, 16)); + } else { + writer.append(c); + } + break; + } + } + writer.append('"'); + } +}