diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java index fa1024b3c..55e383df0 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java @@ -15,8 +15,10 @@ */ package org.teavm.dependency; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.teavm.common.*; @@ -38,6 +40,7 @@ public class DependencyChecker implements DependencyInformation { private ConcurrentCachedMapper fieldCache; private ConcurrentMap achievableClasses = new ConcurrentHashMap<>(); private ConcurrentMap initializedClasses = new ConcurrentHashMap<>(); + private List listeners = new ArrayList<>(); public DependencyChecker(ClassHolderSource classSource, ClassLoader classLoader) { this(classSource, classLoader, new SimpleFiniteExecutor()); @@ -59,9 +62,19 @@ public class DependencyChecker implements DependencyInformation { }); methodCache.addKeyListener(new KeyListener() { @Override public void keyAdded(MethodReference key) { + for (DependencyListener listener : listeners) { + listener.methodAchieved(DependencyChecker.this, key); + } activateDependencyPlugin(key); } }); + fieldCache.addKeyListener(new KeyListener() { + @Override public void keyAdded(FieldReference key) { + for (DependencyListener listener : listeners) { + listener.fieldAchieved(DependencyChecker.this, key); + } + } + }); } public DependencyNode createNode() { @@ -72,6 +85,10 @@ public class DependencyChecker implements DependencyInformation { return classSource; } + public void addDependencyListener(DependencyListener listener) { + listeners.add(listener); + } + public void addEntryPoint(MethodReference methodRef, String... argumentTypes) { ValueType[] parameters = methodRef.getDescriptor().getParameterTypes(); if (parameters.length != argumentTypes.length) { @@ -98,7 +115,13 @@ public class DependencyChecker implements DependencyInformation { } boolean achieveClass(String className) { - return achievableClasses.putIfAbsent(className, dummyValue) == null; + boolean result = achievableClasses.putIfAbsent(className, dummyValue) == null; + if (result) { + for (DependencyListener listener : listeners) { + listener.classAchieved(this, className); + } + } + return result; } public MethodGraph attachMethodGraph(MethodReference methodRef) { diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyListener.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyListener.java new file mode 100644 index 000000000..16b470ef2 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyListener.java @@ -0,0 +1,31 @@ +/* + * 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.dependency; + +import org.teavm.model.FieldReference; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public interface DependencyListener { + void classAchieved(DependencyChecker dependencyChecker, String className); + + void methodAchieved(DependencyChecker dependencyChecker, MethodReference method); + + void fieldAchieved(DependencyChecker dependencyChecker, FieldReference field); +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/DirectoryBuildTarget.java b/teavm-core/src/main/java/org/teavm/javascript/DirectoryBuildTarget.java new file mode 100644 index 000000000..bfbd376b2 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/DirectoryBuildTarget.java @@ -0,0 +1,38 @@ +/* + * 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.javascript; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * + * @author Alexey Andreev + */ +public class DirectoryBuildTarget implements JavascriptBuildTarget { + private File directory; + + public DirectoryBuildTarget(File directory) { + this.directory = directory; + } + + @Override + public OutputStream createResource(String fileName) throws IOException { + return new FileOutputStream(new File(directory, fileName)); + } +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuildTarget.java b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuildTarget.java new file mode 100644 index 000000000..d6c787c71 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuildTarget.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.javascript; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * + * @author Alexey Andreev + */ +public interface JavascriptBuildTarget { + OutputStream createResource(String fileName) throws IOException; +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java index 478e81d26..566b6da7f 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java +++ b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java @@ -16,14 +16,12 @@ package org.teavm.javascript; import java.io.*; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import org.teavm.codegen.*; import org.teavm.common.FiniteExecutor; import org.teavm.dependency.DependencyChecker; import org.teavm.dependency.DependencyInformation; +import org.teavm.dependency.DependencyListener; import org.teavm.javascript.ast.ClassNode; import org.teavm.model.*; import org.teavm.model.util.*; @@ -34,8 +32,8 @@ import org.teavm.optimization.Devirtualization; * * @author Alexey Andreev */ -public class JavascriptBuilder { - private ClassHolderSource classSource; +public class JavascriptBuilder implements JavascriptBuilderHost { + private JavascriptProcessedClassSource classSource; private DependencyChecker dependencyChecker; private FiniteExecutor executor; private ClassLoader classLoader; @@ -44,6 +42,7 @@ public class JavascriptBuilder { private OutputStream logStream = System.out; private Map entryPoints = new HashMap<>(); private Map exportedClasses = new HashMap<>(); + private List ressourceRenderers = new ArrayList<>(); JavascriptBuilder(ClassHolderSource classSource, ClassLoader classLoader, FiniteExecutor executor) { this.classSource = new JavascriptProcessedClassSource(classSource); @@ -52,6 +51,21 @@ public class JavascriptBuilder { this.executor = executor; } + @Override + public void add(DependencyListener listener) { + dependencyChecker.addDependencyListener(listener); + } + + @Override + public void add(ClassHolderTransformer transformer) { + classSource.addTransformer(transformer); + } + + @Override + public void add(JavascriptResourceRenderer resourceRenderer) { + ressourceRenderers.add(resourceRenderer); + } + public boolean isMinifying() { return minifying; } @@ -91,7 +105,7 @@ public class JavascriptBuilder { return classSource; } - public void build(Appendable writer) throws RenderingException { + public void build(Appendable writer, JavascriptBuildTarget target) throws RenderingException { AliasProvider aliasProvider = minifying ? new MinifyingAliasProvider() : new DefaultAliasProvider(); DefaultNamingStrategy naming = new DefaultNamingStrategy(aliasProvider, classSource); naming.setMinifying(minifying); @@ -134,6 +148,9 @@ public class JavascriptBuilder { sourceWriter.append(entry.getKey()).ws().append("=").ws().appendClass(entry.getValue()).append(";") .softNewLine(); } + for (JavascriptResourceRenderer resourceRenderer : ressourceRenderers) { + resourceRenderer.render(target); + } } catch (IOException e) { throw new RenderingException("IO Error occured", e); } @@ -275,13 +292,19 @@ public class JavascriptBuilder { } } - public void build(File file) throws RenderingException { - try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) { - build(writer); + public void build(File dir, String fileName) throws RenderingException { + try (Writer writer = new OutputStreamWriter(new FileOutputStream(new File(dir, fileName)), "UTF-8")) { + build(writer, new DirectoryBuildTarget(dir)); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Platform does not support UTF-8", e); } catch (IOException e) { throw new RenderingException("IO error occured", e); } } + + public void installPlugins() { + for (JavascriptBuilderPlugin plugin : ServiceLoader.load(JavascriptBuilderPlugin.class)) { + plugin.install(this); + } + } } diff --git a/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilderHost.java b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilderHost.java new file mode 100644 index 000000000..bbc0f8714 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilderHost.java @@ -0,0 +1,31 @@ +/* + * 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.javascript; + +import org.teavm.dependency.DependencyListener; +import org.teavm.model.ClassHolderTransformer; + +/** + * + * @author Alexey Andreev + */ +public interface JavascriptBuilderHost { + void add(DependencyListener dependencyListener); + + void add(ClassHolderTransformer classTransformer); + + void add(JavascriptResourceRenderer resourceRenderer); +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilderPlugin.java b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilderPlugin.java new file mode 100644 index 000000000..8b1dd1672 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilderPlugin.java @@ -0,0 +1,24 @@ +/* + * 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.javascript; + +/** + * + * @author Alexey Andreev + */ +public interface JavascriptBuilderPlugin { + void install(JavascriptBuilderHost host); +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/JavascriptProcessedClassSource.java b/teavm-core/src/main/java/org/teavm/javascript/JavascriptProcessedClassSource.java index 9c6bb6e8d..1494164a9 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/JavascriptProcessedClassSource.java +++ b/teavm-core/src/main/java/org/teavm/javascript/JavascriptProcessedClassSource.java @@ -15,22 +15,30 @@ */ package org.teavm.javascript; +import java.util.ArrayList; +import java.util.List; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderSource; +import org.teavm.model.ClassHolderTransformer; import org.teavm.model.MethodHolder; /** * * @author Alexey Andreev */ -public class JavascriptProcessedClassSource implements ClassHolderSource { +class JavascriptProcessedClassSource implements ClassHolderSource { private ThreadLocal processor = new ThreadLocal<>(); private ClassHolderSource innerSource; + private List transformers = new ArrayList<>(); public JavascriptProcessedClassSource(ClassHolderSource innerSource) { this.innerSource = innerSource; } + public void addTransformer(ClassHolderTransformer transformer) { + transformers.add(transformer); + } + @Override public ClassHolder get(String name) { ClassHolder cls = innerSource.get(name); @@ -48,6 +56,9 @@ public class JavascriptProcessedClassSource implements ClassHolderSource { processor.processProgram(method.getProgram()); } } + for (ClassHolderTransformer transformer : transformers) { + transformer.transformClass(cls); + } } private JavascriptNativeProcessor getProcessor() { diff --git a/teavm-core/src/main/java/org/teavm/javascript/JavascriptResourceRenderer.java b/teavm-core/src/main/java/org/teavm/javascript/JavascriptResourceRenderer.java new file mode 100644 index 000000000..2bc6d5be2 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/JavascriptResourceRenderer.java @@ -0,0 +1,26 @@ +/* + * 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.javascript; + +import java.io.IOException; + +/** + * + * @author Alexey Andreev + */ +public interface JavascriptResourceRenderer { + void render(JavascriptBuildTarget buildTarget) throws IOException; +} diff --git a/teavm-core/src/main/java/org/teavm/model/ClassHolderTransformer.java b/teavm-core/src/main/java/org/teavm/model/ClassHolderTransformer.java new file mode 100644 index 000000000..66b6c5b04 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/model/ClassHolderTransformer.java @@ -0,0 +1,25 @@ +/* + * 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.model; + + +/** + * + * @author Alexey Andreev + */ +public interface ClassHolderTransformer { + void transformClass(ClassHolder cls); +} diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptJUnitMojo.java b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptJUnitMojo.java index 1539e1b38..a164a4853 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptJUnitMojo.java +++ b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptJUnitMojo.java @@ -37,6 +37,7 @@ import org.junit.Test; import org.teavm.common.FiniteExecutor; import org.teavm.common.SimpleFiniteExecutor; import org.teavm.common.ThreadPoolFiniteExecutor; +import org.teavm.javascript.DirectoryBuildTarget; import org.teavm.javascript.JavascriptBuilder; import org.teavm.javascript.JavascriptBuilderFactory; import org.teavm.model.*; @@ -234,6 +235,7 @@ public class BuildJavascriptJUnitMojo extends AbstractMojo { builderFactory.setExecutor(executor); JavascriptBuilder builder = builderFactory.create(); builder.setMinifying(minifying); + builder.installPlugins(); File file = new File(outputDir, targetName); try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) { MethodReference cons = new MethodReference(methodRef.getClassName(), @@ -241,7 +243,7 @@ public class BuildJavascriptJUnitMojo extends AbstractMojo { builder.entryPoint("initInstance", cons); builder.entryPoint("runTest", methodRef).withValue(0, cons.getClassName()); builder.exportType("TestClass", cons.getClassName()); - builder.build(innerWriter); + builder.build(innerWriter, new DirectoryBuildTarget(outputDir)); innerWriter.append("\n"); innerWriter.append("\nJUnitClient.run();"); innerWriter.close(); diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java index 5c03f0a0a..6776746a8 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java +++ b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java @@ -132,12 +132,13 @@ public class BuildJavascriptMojo extends AbstractMojo { JavascriptBuilder builder = builderFactory.create(); builder.setMinifying(minifying); builder.setBytecodeLogging(bytecodeLogging); + builder.installPlugins(); MethodDescriptor mainMethodDesc = new MethodDescriptor("main", ValueType.arrayOf( ValueType.object("java.lang.String")), ValueType.VOID); builder.entryPoint("main", new MethodReference(mainClass, mainMethodDesc)) .withValue(1, "java.lang.String"); targetDirectory.mkdirs(); - builder.build(new File(targetDirectory, targetFileName)); + builder.build(targetDirectory, targetFileName); log.info("JavaScript file successfully built"); if (!runtimeSuppressed) { resourceToFile("org/teavm/javascript/runtime.js", "runtime.js"); @@ -194,7 +195,7 @@ public class BuildJavascriptMojo extends AbstractMojo { } private void resourceToFile(String resource, String fileName) throws IOException { - try (InputStream input = BuildJavascriptJUnitMojo.class.getClassLoader().getResourceAsStream(resource)) { + try (InputStream input = BuildJavascriptMojo.class.getClassLoader().getResourceAsStream(resource)) { try (OutputStream output = new FileOutputStream(new File(targetDirectory, fileName))) { IOUtils.copy(input, output); }