// Copyright 2015 Michel Kraemer // // 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 de.undercouch.vertx.lang.typescript.compiler; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.function.Consumer; import java.util.function.Function; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import jdk.nashorn.api.scripting.ScriptObjectMirror; /** * Compiles TypeScript sources with the TypeScript compiler hosted by a * JavaScript engine * @author Michel Kraemer */ public class EngineCompiler implements TypeScriptCompiler { /** * Path to the TypeScript compiler */ static final String TYPESCRIPT_JS = "typescript/lib/typescriptServices.js"; /** * Path to a helper script calling the TypeScript compiler */ static final String COMPILE_JS = "vertx-typescript/util/compile.js"; /** * The JavaScript engine hosting the TypeScript compiler */ private ScriptEngine engine; /** * Creates the JavaScript engine that hosts the TypeScript compiler. Loads * the compiler and a helper script and evaluates them within the engine. * @return the engine */ private synchronized ScriptEngine getEngine() { if (engine != null) { return engine; } // create JavaScript engine ScriptEngineManager mgr = new ScriptEngineManager(); engine = mgr.getEngineByName("nashorn"); if (engine == null) { throw new IllegalStateException("Could not find Nashorn JavaScript engine."); } // load TypeScript compiler loadScript(TYPESCRIPT_JS, src -> { // WORKAROUND for a bug in Nashorn (https://bugs.openjdk.java.net/browse/JDK-8079426) // Inside the TypeScript compiler `ts.Diagnostics` is defined as a literal // with more than 256 items. This causes all elements to be undefined. // The bug has been fixed but it still occurs in Java 8u45. // find function where the diagnostics are defined String startStr = "ts.Diagnostics = {"; String endStr = "};"; int start = src.indexOf(startStr); int end = src.indexOf(endStr, start); String diagnostics = src.substring(start + startStr.length(), end); // change lines so properties are set one by one String[] diagLines = diagnostics.split("\n"); for (int i = 0; i < diagLines.length; ++i) { diagLines[i] = diagLines[i].replaceFirst("^\\s*(.+?):", "ts.Diagnostics.$1 ="); } // replace original lines with new ones String newDiagnostics = startStr + "};\n" + String.join("\n", diagLines) + ";"; src = src.substring(0, start) + newDiagnostics + src.substring(end + endStr.length()); return src; }); // load compile.js loadScript(COMPILE_JS, null); // define some globals engine.put("__lineSeparator", System.lineSeparator()); engine.put("__isFileNotFoundException", (Function<Object, Boolean>)(e -> e instanceof FileNotFoundException)); engine.put("__printlnErr", (Consumer<Object>)System.err::println); return engine; } /** * Loads a JavaScript file and evaluate it within {@link #engine} * @param name the name of the file to load */ private void loadScript(String name, Function<String, String> processSource) { URL url = getClass().getClassLoader().getResource(name); if (url == null) { throw new IllegalStateException("Cannot find " + name + " on classpath"); } try { String src = Source.fromURL(url, StandardCharsets.UTF_8).toString(); if (processSource != null) { src = processSource.apply(src); } engine.eval(src); } catch (ScriptException | IOException e) { throw new IllegalStateException("Could not evaluate " + name, e); } } @Override public String compile(String filename, SourceFactory sourceFactory) throws IOException { ScriptEngine e = getEngine(); ScriptObjectMirror o = (ScriptObjectMirror)e.get("compileTypescript"); return (String)o.call(null, filename, sourceFactory); } }