Java tutorial
/* * 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.tooling; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import org.apache.commons.io.IOUtils; import org.teavm.backend.javascript.JavaScriptTarget; import org.teavm.backend.javascript.rendering.RenderingManager; import org.teavm.backend.wasm.WasmTarget; import org.teavm.backend.wasm.render.WasmBinaryVersion; import org.teavm.cache.DiskCachedClassHolderSource; import org.teavm.cache.DiskProgramCache; import org.teavm.cache.DiskRegularMethodNodeCache; import org.teavm.cache.FileSymbolTable; import org.teavm.debugging.information.DebugInformation; import org.teavm.debugging.information.DebugInformationBuilder; import org.teavm.dependency.DependencyInfo; import org.teavm.diagnostics.ProblemProvider; import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.PreOptimizingClassHolderSource; import org.teavm.model.ProgramReader; import org.teavm.parsing.ClasspathClassHolderSource; import org.teavm.tooling.sources.SourceFileProvider; import org.teavm.tooling.sources.SourceFilesCopier; import org.teavm.vm.BuildTarget; import org.teavm.vm.DirectoryBuildTarget; import org.teavm.vm.TeaVM; import org.teavm.vm.TeaVMBuilder; import org.teavm.vm.TeaVMEntryPoint; import org.teavm.vm.TeaVMOptimizationLevel; import org.teavm.vm.TeaVMProgressListener; import org.teavm.vm.TeaVMTarget; import org.teavm.vm.spi.AbstractRendererListener; public class TeaVMTool implements BaseTeaVMTool { private File targetDirectory = new File("."); private TeaVMTargetType targetType = TeaVMTargetType.JAVASCRIPT; private String targetFileName = ""; private boolean minifying = true; private String mainClass; private RuntimeCopyOperation runtime = RuntimeCopyOperation.SEPARATE; private Properties properties = new Properties(); private boolean mainPageIncluded; private boolean debugInformationGenerated; private boolean sourceMapsFileGenerated; private boolean sourceFilesCopied; private boolean incremental; private File cacheDirectory = new File("./teavm-cache"); private List<ClassHolderTransformer> transformers = new ArrayList<>(); private List<ClassAlias> classAliases = new ArrayList<>(); private List<MethodAlias> methodAliases = new ArrayList<>(); private TeaVMToolLog log = new EmptyTeaVMToolLog(); private ClassLoader classLoader = TeaVMTool.class.getClassLoader(); private DiskCachedClassHolderSource cachedClassSource; private DiskProgramCache programCache; private DiskRegularMethodNodeCache astCache; private FileSymbolTable symbolTable; private FileSymbolTable fileTable; private boolean cancelled; private TeaVMProgressListener progressListener; private TeaVM vm; private TeaVMOptimizationLevel optimizationLevel = TeaVMOptimizationLevel.SIMPLE; private List<SourceFileProvider> sourceFileProviders = new ArrayList<>(); private DebugInformationBuilder debugEmitter; private JavaScriptTarget javaScriptTarget; private WasmTarget webAssemblyTarget; private WasmBinaryVersion wasmVersion = WasmBinaryVersion.V_0x1; private Set<File> generatedFiles = new HashSet<>(); public File getTargetDirectory() { return targetDirectory; } @Override public void setTargetDirectory(File targetDirectory) { this.targetDirectory = targetDirectory; } public String getTargetFileName() { return targetFileName; } public void setTargetFileName(String targetFileName) { this.targetFileName = targetFileName; } public boolean isMinifying() { return minifying; } @Override public void setMinifying(boolean minifying) { this.minifying = minifying; } public boolean isIncremental() { return incremental; } @Override public void setIncremental(boolean incremental) { this.incremental = incremental; } public String getMainClass() { return mainClass; } public void setMainClass(String mainClass) { this.mainClass = mainClass; } public RuntimeCopyOperation getRuntime() { return runtime; } public void setRuntime(RuntimeCopyOperation runtime) { this.runtime = runtime; } public boolean isMainPageIncluded() { return mainPageIncluded; } public void setMainPageIncluded(boolean mainPageIncluded) { this.mainPageIncluded = mainPageIncluded; } public boolean isDebugInformationGenerated() { return debugInformationGenerated; } @Override public void setDebugInformationGenerated(boolean debugInformationGenerated) { this.debugInformationGenerated = debugInformationGenerated; } public File getCacheDirectory() { return cacheDirectory; } public void setCacheDirectory(File cacheDirectory) { this.cacheDirectory = cacheDirectory; } public boolean isSourceMapsFileGenerated() { return sourceMapsFileGenerated; } @Override public void setSourceMapsFileGenerated(boolean sourceMapsFileGenerated) { this.sourceMapsFileGenerated = sourceMapsFileGenerated; } public boolean isSourceFilesCopied() { return sourceFilesCopied; } @Override public void setSourceFilesCopied(boolean sourceFilesCopied) { this.sourceFilesCopied = sourceFilesCopied; } @Override public Properties getProperties() { return properties; } @Override public List<ClassHolderTransformer> getTransformers() { return transformers; } public List<ClassAlias> getClassAliases() { return classAliases; } public List<MethodAlias> getMethodAliases() { return methodAliases; } public TeaVMToolLog getLog() { return log; } @Override public void setLog(TeaVMToolLog log) { this.log = log; } public TeaVMTargetType getTargetType() { return targetType; } public void setTargetType(TeaVMTargetType targetType) { this.targetType = targetType; } public TeaVMOptimizationLevel getOptimizationLevel() { return optimizationLevel; } public void setOptimizationLevel(TeaVMOptimizationLevel optimizationLevel) { this.optimizationLevel = optimizationLevel; } public ClassLoader getClassLoader() { return classLoader; } @Override public void setClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } public WasmBinaryVersion getWasmVersion() { return wasmVersion; } public void setWasmVersion(WasmBinaryVersion wasmVersion) { this.wasmVersion = wasmVersion; } public void setProgressListener(TeaVMProgressListener progressListener) { this.progressListener = progressListener; } public boolean wasCancelled() { return cancelled; } public ProblemProvider getProblemProvider() { return vm != null ? vm.getProblemProvider() : null; } public DependencyInfo getDependencyInfo() { return vm.getDependencyInfo(); } public Collection<String> getClasses() { return vm != null ? vm.getClasses() : Collections.emptyList(); } public Set<File> getGeneratedFiles() { return generatedFiles; } public Collection<String> getUsedResources() { if (vm == null) { return Collections.emptyList(); } Set<String> resources = new HashSet<>(); ClassReaderSource classSource = vm.getDependencyClassSource(); InstructionLocationReader reader = new InstructionLocationReader(resources); for (MethodReference methodRef : vm.getMethods()) { ClassReader cls = classSource.get(methodRef.getClassName()); if (cls == null) { continue; } MethodReader method = cls.getMethod(methodRef.getDescriptor()); if (method == null) { continue; } ProgramReader program = method.getProgram(); if (program == null) { continue; } for (int i = 0; i < program.basicBlockCount(); ++i) { program.basicBlockAt(i).readAllInstructions(reader); } } return resources; } @Override public void addSourceFileProvider(SourceFileProvider sourceFileProvider) { sourceFileProviders.add(sourceFileProvider); } private TeaVMTarget prepareTarget() { switch (targetType) { case JAVASCRIPT: return prepareJavaScriptTarget(); case WEBASSEMBLY: return prepareWebAssemblyTarget(); } throw new IllegalStateException("Unknown target type: " + targetType); } private TeaVMTarget prepareJavaScriptTarget() { javaScriptTarget = new JavaScriptTarget(); javaScriptTarget.setMinifying(minifying); debugEmitter = debugInformationGenerated || sourceMapsFileGenerated ? new DebugInformationBuilder() : null; javaScriptTarget.setDebugEmitter(debugEmitter); if (incremental) { javaScriptTarget.setAstCache(astCache); } return javaScriptTarget; } private WasmTarget prepareWebAssemblyTarget() { webAssemblyTarget = new WasmTarget(); webAssemblyTarget.setDebugging(debugInformationGenerated); webAssemblyTarget.setCEmitted(debugInformationGenerated); webAssemblyTarget.setWastEmitted(debugInformationGenerated); webAssemblyTarget.setVersion(wasmVersion); return webAssemblyTarget; } public void generate() throws TeaVMToolException { try { cancelled = false; log.info("Building JavaScript file"); TeaVMBuilder vmBuilder = new TeaVMBuilder(prepareTarget()); if (incremental) { cacheDirectory.mkdirs(); symbolTable = new FileSymbolTable(new File(cacheDirectory, "symbols")); fileTable = new FileSymbolTable(new File(cacheDirectory, "files")); ClasspathClassHolderSource innerClassSource = new ClasspathClassHolderSource(classLoader); ClassHolderSource classSource = new PreOptimizingClassHolderSource(innerClassSource); cachedClassSource = new DiskCachedClassHolderSource(cacheDirectory, symbolTable, fileTable, classSource, innerClassSource); programCache = new DiskProgramCache(cacheDirectory, symbolTable, fileTable, innerClassSource); if (targetType == TeaVMTargetType.JAVASCRIPT) { astCache = new DiskRegularMethodNodeCache(cacheDirectory, symbolTable, fileTable, innerClassSource); } try { symbolTable.update(); fileTable.update(); } catch (IOException e) { log.info("Cache is missing"); } vmBuilder.setClassLoader(classLoader).setClassSource(cachedClassSource); } else { vmBuilder.setClassLoader(classLoader).setClassSource( new PreOptimizingClassHolderSource(new ClasspathClassHolderSource(classLoader))); } vm = vmBuilder.build(); if (progressListener != null) { vm.setProgressListener(progressListener); } vm.setProperties(properties); vm.setProgramCache(programCache); vm.setIncremental(incremental); vm.setOptimizationLevel(optimizationLevel); vm.installPlugins(); for (ClassHolderTransformer transformer : transformers) { vm.add(transformer); } if (mainClass != null) { MethodDescriptor mainMethodDesc = new MethodDescriptor("main", String[].class, void.class); vm.entryPoint("main", new MethodReference(mainClass, mainMethodDesc)) .withValue(1, "[java.lang.String").withArrayValue(1, "java.lang.String").async(); } for (ClassAlias alias : classAliases) { vm.exportType(alias.getAlias(), alias.getClassName()); } for (MethodAlias methodAlias : methodAliases) { MethodReference ref = new MethodReference(methodAlias.getClassName(), methodAlias.getMethodName(), MethodDescriptor.parseSignature(methodAlias.getDescriptor())); TeaVMEntryPoint entryPoint = vm.entryPoint(methodAlias.getAlias(), ref).async(); if (methodAlias.getTypes() != null) { for (int i = 0; i < methodAlias.getTypes().length; ++i) { String types = methodAlias.getTypes()[i]; if (types != null) { for (String type : types.split(" +")) { type = type.trim(); if (!type.isEmpty()) { entryPoint.withValue(i, type); } } } } } } targetDirectory.mkdirs(); if (runtime == RuntimeCopyOperation.MERGED) { javaScriptTarget.add(runtimeInjector); } BuildTarget buildTarget = new DirectoryBuildTarget(targetDirectory); String outputName = getResolvedTargetFileName(); vm.build(buildTarget, outputName); if (vm.wasCancelled()) { log.info("Build cancelled"); cancelled = true; return; } ProblemProvider problemProvider = vm.getProblemProvider(); if (problemProvider.getProblems().isEmpty()) { log.info("Output file successfully built"); } else if (problemProvider.getSevereProblems().isEmpty()) { log.info("Output file built with warnings"); TeaVMProblemRenderer.describeProblems(vm, log); } else { log.info("Output file built with errors"); TeaVMProblemRenderer.describeProblems(vm, log); } File outputFile = new File(targetDirectory, outputName); generatedFiles.add(outputFile); if (targetType == TeaVMTargetType.JAVASCRIPT) { try (OutputStream output = new FileOutputStream(new File(targetDirectory, outputName), true)) { try (Writer writer = new OutputStreamWriter(output, "UTF-8")) { additionalJavaScriptOutput(writer); } } } if (incremental) { programCache.flush(); if (astCache != null) { astCache.flush(); } cachedClassSource.flush(); symbolTable.flush(); fileTable.flush(); log.info("Cache updated"); } } catch (IOException e) { throw new TeaVMToolException("IO error occurred", e); } } private String getResolvedTargetFileName() { if (targetFileName.isEmpty()) { switch (targetType) { case JAVASCRIPT: return "classes.js"; case WEBASSEMBLY: return "classes.wasm"; default: return "classes"; } } return targetFileName; } private void additionalJavaScriptOutput(Writer writer) throws IOException { if (mainClass != null) { writer.append("main = $rt_mainStarter(main);\n"); } if (debugInformationGenerated) { assert debugEmitter != null; DebugInformation debugInfo = debugEmitter.getDebugInformation(); File debugSymbolFile = new File(targetDirectory, getResolvedTargetFileName() + ".teavmdbg"); try (OutputStream debugInfoOut = new BufferedOutputStream(new FileOutputStream(debugSymbolFile))) { debugInfo.write(debugInfoOut); } generatedFiles.add(debugSymbolFile); log.info("Debug information successfully written"); } if (sourceMapsFileGenerated) { assert debugEmitter != null; DebugInformation debugInfo = debugEmitter.getDebugInformation(); String sourceMapsFileName = getResolvedTargetFileName() + ".map"; writer.append("\n//# sourceMappingURL=").append(sourceMapsFileName); File sourceMapsFile = new File(targetDirectory, sourceMapsFileName); try (Writer sourceMapsOut = new OutputStreamWriter(new FileOutputStream(sourceMapsFile), "UTF-8")) { debugInfo.writeAsSourceMaps(sourceMapsOut, "src", getResolvedTargetFileName()); } log.info("Source maps successfully written"); } if (sourceFilesCopied) { copySourceFiles(); log.info("Source files successfully written"); } if (runtime == RuntimeCopyOperation.SEPARATE) { resourceToFile("org/teavm/backend/javascript/runtime.js", "runtime.js"); } if (mainPageIncluded) { String text; try (Reader reader = new InputStreamReader( classLoader.getResourceAsStream("org/teavm/tooling/main.html"), "UTF-8")) { text = IOUtils.toString(reader).replace("${classes.js}", getResolvedTargetFileName()); } File mainPageFile = new File(targetDirectory, "main.html"); try (Writer mainPageWriter = new OutputStreamWriter(new FileOutputStream(mainPageFile), "UTF-8")) { mainPageWriter.append(text); } } } private void copySourceFiles() { if (vm.getWrittenClasses() == null) { return; } SourceFilesCopier copier = new SourceFilesCopier(sourceFileProviders); copier.addClasses(vm.getWrittenClasses()); copier.setLog(log); copier.copy(new File(targetDirectory, "src")); } private AbstractRendererListener runtimeInjector = new AbstractRendererListener() { @Override public void begin(RenderingManager manager, BuildTarget buildTarget) throws IOException { StringWriter writer = new StringWriter(); resourceToWriter("org/teavm/backend/javascript/runtime.js", writer); writer.close(); manager.getWriter().append(writer.toString()).newLine(); } }; private void resourceToFile(String resource, String fileName) throws IOException { try (InputStream input = TeaVMTool.class.getClassLoader().getResourceAsStream(resource)) { File outputFile = new File(targetDirectory, fileName); try (OutputStream output = new FileOutputStream(outputFile)) { IOUtils.copy(input, output); } generatedFiles.add(outputFile); } } private void resourceToWriter(String resource, Writer writer) throws IOException { try (InputStream input = TeaVMTool.class.getClassLoader().getResourceAsStream(resource)) { IOUtils.copy(input, writer, "UTF-8"); } } }