Java tutorial
/* * Copyright 2010 The Closure Compiler Authors. * * 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 be.crydust.jscompinplace; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.Lists; import com.google.common.io.Files; import com.google.javascript.jscomp.CheckLevel; import com.google.javascript.jscomp.CommandLineRunner; import com.google.javascript.jscomp.CompilationLevel; import com.google.javascript.jscomp.Compiler; import com.google.javascript.jscomp.CompilerOptions; import com.google.javascript.jscomp.DiagnosticGroup; import com.google.javascript.jscomp.DiagnosticGroups; import com.google.javascript.jscomp.MessageFormatter; import com.google.javascript.jscomp.Result; import com.google.javascript.jscomp.SourceFile; import com.google.javascript.jscomp.WarningLevel; import com.google.javascript.jscomp.ant.AntErrorManager; import com.google.javascript.jscomp.ant.Warning; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.FileList; import org.apache.tools.ant.types.Parameter; import org.apache.tools.ant.types.Path; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import java.nio.file.Paths; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.Arrays; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Resource; import org.apache.tools.ant.types.ResourceCollection; import org.apache.tools.ant.types.resources.FileResource; /** * This class implements a simple Ant task to do almost the same as * CommandLineRunner. * * Most of the public methods of this class are entry points for the Ant code to * hook into. * */ public final class CompileInplaceTask extends Task { private CompilerOptions.LanguageMode languageIn; private WarningLevel warningLevel; private boolean debugOptions; private String encoding = "UTF-8"; private String outputEncoding = "UTF-8"; private CompilationLevel compilationLevel; private boolean customExternsOnly; private boolean manageDependencies; private boolean prettyPrint; private boolean printInputDelimiter; private boolean generateExports; private boolean replaceProperties; private String replacePropertiesPrefix; private String outputWrapper; private File outputWrapperFile; private final List<Parameter> defineParams; private final List<Parameter> entryPointParams; private final List<FileList> externFileLists; private final List<FileList> sourceFileLists; private final List<Path> sourcePaths; private final List<FileSet> sourceFileSets; private final List<Warning> warnings; public CompileInplaceTask() { this.languageIn = CompilerOptions.LanguageMode.ECMASCRIPT3; this.warningLevel = WarningLevel.DEFAULT; this.debugOptions = false; this.compilationLevel = CompilationLevel.SIMPLE_OPTIMIZATIONS; this.customExternsOnly = false; this.manageDependencies = false; this.prettyPrint = false; this.printInputDelimiter = false; this.generateExports = false; this.replaceProperties = false; this.replacePropertiesPrefix = "closure.define."; this.defineParams = Lists.newLinkedList(); this.entryPointParams = Lists.newLinkedList(); this.externFileLists = Lists.newLinkedList(); this.sourceFileLists = Lists.newLinkedList(); this.sourcePaths = Lists.newLinkedList(); this.sourceFileSets = Lists.newLinkedList(); this.warnings = Lists.newLinkedList(); } /** * Set the language to which input sources conform. * * @param value The name of the language. (ECMASCRIPT3, ECMASCRIPT5, * ECMASCRIPT5_STRICT). */ public void setLanguageIn(String value) { switch (value) { case "ECMASCRIPT6_STRICT": case "ES6_STRICT": this.languageIn = CompilerOptions.LanguageMode.ECMASCRIPT6_STRICT; break; case "ECMASCRIPT6": case "ES6": this.languageIn = CompilerOptions.LanguageMode.ECMASCRIPT6; break; case "ECMASCRIPT5_STRICT": case "ES5_STRICT": this.languageIn = CompilerOptions.LanguageMode.ECMASCRIPT5_STRICT; break; case "ECMASCRIPT5": case "ES5": this.languageIn = CompilerOptions.LanguageMode.ECMASCRIPT5; break; case "ECMASCRIPT3": case "ES3": this.languageIn = CompilerOptions.LanguageMode.ECMASCRIPT3; break; default: throw new BuildException("Unrecognized 'languageIn' option value (" + value + ")"); } } /** * Set the warning level. * * @param value The warning level by string name. (default, quiet, verbose). */ public void setWarning(String value) { if ("default".equalsIgnoreCase(value)) { this.warningLevel = WarningLevel.DEFAULT; } else if ("quiet".equalsIgnoreCase(value)) { this.warningLevel = WarningLevel.QUIET; } else if ("verbose".equalsIgnoreCase(value)) { this.warningLevel = WarningLevel.VERBOSE; } else { throw new BuildException("Unrecognized 'warning' option value (" + value + ")"); } } /** * Enable debugging options. * * @param value True if debug mode is enabled. */ public void setDebug(boolean value) { this.debugOptions = value; } /** * Set the compilation level. * * @param value The optimization level by string name. (whitespace, simple, * advanced). */ public void setCompilationLevel(String value) { if ("simple".equalsIgnoreCase(value)) { this.compilationLevel = CompilationLevel.SIMPLE_OPTIMIZATIONS; } else if ("advanced".equalsIgnoreCase(value)) { this.compilationLevel = CompilationLevel.ADVANCED_OPTIMIZATIONS; } else if ("whitespace".equalsIgnoreCase(value)) { this.compilationLevel = CompilationLevel.WHITESPACE_ONLY; } else { throw new BuildException("Unrecognized 'compilation' option value (" + value + ")"); } } public void setManageDependencies(boolean value) { this.manageDependencies = value; } /** * Use only custom externs. */ public void setCustomExternsOnly(boolean value) { this.customExternsOnly = value; } /** * Set output wrapper. */ public void setOutputWrapper(String value) { this.outputWrapper = value; } /** * Set output wrapper file. */ public void setOutputWrapperFile(File value) { this.outputWrapperFile = value; } /** * Set the replacement property prefix. */ public void setReplacePropertiesPrefix(String value) { this.replacePropertiesPrefix = value; } /** * Whether to replace {@code @define} lines with properties */ public void setReplaceProperties(boolean value) { this.replaceProperties = value; } /** * Set input file encoding */ public void setEncoding(String encoding) { this.encoding = encoding; } /** * Set output file encoding */ public void setOutputEncoding(String outputEncoding) { this.outputEncoding = outputEncoding; } /** * Set pretty print formatting option */ public void setPrettyPrint(boolean pretty) { this.prettyPrint = pretty; } /** * Set print input delimiter formatting option */ public void setPrintInputDelimiter(boolean print) { this.printInputDelimiter = print; } /** * Set generateExports option */ public void setGenerateExports(boolean generateExports) { this.generateExports = generateExports; } /** * Sets the externs file. */ public void addExterns(FileList list) { this.externFileLists.add(list); } /** * Adds a <warning/> entry * * Each warning entry must have two attributes, group and level. Group must * contain one of the constants from DiagnosticGroups (e.g., * "ACCESS_CONTROLS"), while level must contain one of the CheckLevel * constants ("ERROR", "WARNING" or "OFF"). */ public void addWarning(Warning warning) { this.warnings.add(warning); } /** * Adds a <entrypoint/> entry * * Each entrypoint entry must have one attribute, name. */ public void addEntryPoint(Parameter entrypoint) { this.entryPointParams.add(entrypoint); } /** * Sets the source files. */ public void addSources(FileList list) { this.sourceFileLists.add(list); } /** * Adds a <path/> entry. */ public void addPath(Path list) { this.sourcePaths.add(list); } /** * Adds a <fileset/> entry. */ public void addFileSet(FileSet fileSet) { this.sourceFileSets.add(fileSet); } @Override public void execute() { Compiler.setLoggingLevel(Level.OFF); CompilerOptions options = createCompilerOptions(); List<SourceFile> externs = findExternFiles(); List<SourceFile> sourceFiles = findSourceFiles(); log("Compiling " + sourceFiles.size() + " file(s) with " + externs.size() + " extern(s)"); for (SourceFile sourceFile : sourceFiles) { Compiler compiler = createCompiler(options); log("Compiling " + sourceFile.getOriginalPath()); Result result = compiler.compile(externs, Arrays.asList(sourceFile), options); if (result.success) { StringBuilder source = new StringBuilder(compiler.toSource()); if (this.outputWrapperFile != null) { try { this.outputWrapper = Files.toString(this.outputWrapperFile, UTF_8); } catch (Exception e) { throw new BuildException("Invalid output_wrapper_file specified."); } } if (this.outputWrapper != null) { int pos = -1; pos = this.outputWrapper.indexOf(CommandLineRunner.OUTPUT_MARKER); if (pos > -1) { String prefix = this.outputWrapper.substring(0, pos); source.insert(0, prefix); // end of outputWrapper int suffixStart = pos + CommandLineRunner.OUTPUT_MARKER.length(); String suffix = this.outputWrapper.substring(suffixStart); source.append(suffix); } else { throw new BuildException("Invalid output_wrapper specified. " + "Missing '" + CommandLineRunner.OUTPUT_MARKER + "'."); } } writeResult(new File(sourceFile.getOriginalPath()), source.toString()); } else { throw new BuildException("Compilation failed."); } } } private CompilerOptions createCompilerOptions() { CompilerOptions options = new CompilerOptions(); this.compilationLevel.setOptionsForCompilationLevel(options); if (this.debugOptions) { this.compilationLevel.setDebugOptionsForCompilationLevel(options); } options.prettyPrint = this.prettyPrint; options.printInputDelimiter = this.printInputDelimiter; options.generateExports = this.generateExports; options.setLanguageIn(this.languageIn); options.setOutputCharset(this.outputEncoding); this.warningLevel.setOptionsForWarningLevel(options); options.setManageClosureDependencies(manageDependencies); convertEntryPointParameters(options); options.setTrustedStrings(true); if (replaceProperties) { convertPropertiesMap(options); } convertDefineParameters(options); for (Warning warning : warnings) { CheckLevel level = warning.getLevel(); String groupName = warning.getGroup(); DiagnosticGroup group = new DiagnosticGroups().forName(groupName); if (group == null) { throw new BuildException("Unrecognized 'warning' option value (" + groupName + ")"); } options.setWarningLevel(group, level); } return options; } /** * Creates a new {@code <define/>} nested element. Supports name and value * attributes. */ public Parameter createDefine() { Parameter param = new Parameter(); defineParams.add(param); return param; } /** * Creates a new {@code <entrypoint/>} nested element. Supports name * attribute. */ public Parameter createEntryPoint() { Parameter param = new Parameter(); entryPointParams.add(param); return param; } /** * Converts {@code <define/>} nested elements into Compiler {@code @define} * replacements. Note: unlike project properties, {@code <define/>} elements * do not need to be named starting with the replacement prefix. */ private void convertDefineParameters(CompilerOptions options) { for (Parameter p : defineParams) { String key = p.getName(); Object value = p.getValue(); if (!setDefine(options, key, value)) { log("Unexpected @define value for name=" + key + "; value=" + value); } } } /** * Converts {@code <entrypoint/>} nested elements into Compiler entrypoint * replacements. */ private void convertEntryPointParameters(CompilerOptions options) { List<String> entryPoints = Lists.newLinkedList(); for (Parameter p : entryPointParams) { String key = p.getName(); entryPoints.add(key); } if (this.manageDependencies) { options.setManageClosureDependencies(entryPoints); } } /** * Converts project properties beginning with the replacement prefix into * Compiler {@code @define} replacements. * * @param options */ private void convertPropertiesMap(CompilerOptions options) { @SuppressWarnings("unchecked") Map<String, Object> props = getProject().getProperties(); for (Map.Entry<String, Object> entry : props.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if (key.startsWith(replacePropertiesPrefix)) { key = key.substring(replacePropertiesPrefix.length()); if (!setDefine(options, key, value)) { log("Unexpected property value for key=" + key + "; value=" + value); } } } } /** * Maps Ant-style values (e.g., from Properties) into expected Closure * {@code @define} literals * * @return True if the {@code @define} replacement succeeded, false if the * variable's value could not be mapped properly. */ private boolean setDefine(CompilerOptions options, String key, Object value) { boolean success = false; if (value instanceof String) { final boolean isTrue = "true".equals(value); final boolean isFalse = "false".equals(value); if (isTrue || isFalse) { options.setDefineToBooleanLiteral(key, isTrue); } else { try { double dblTemp = Double.parseDouble((String) value); options.setDefineToDoubleLiteral(key, dblTemp); } catch (NumberFormatException nfe) { // Not a number, assume string options.setDefineToStringLiteral(key, (String) value); } } success = true; } else if (value instanceof Boolean) { options.setDefineToBooleanLiteral(key, (Boolean) value); success = true; } else if (value instanceof Integer) { options.setDefineToNumberLiteral(key, (Integer) value); success = true; } else if (value instanceof Double) { options.setDefineToDoubleLiteral(key, (Double) value); success = true; } return success; } private Compiler createCompiler(CompilerOptions options) { Compiler compiler = new Compiler(); MessageFormatter formatter = options.errorFormat.toFormatter(compiler, false); AntErrorManager errorManager = new AntErrorManager(formatter, this); compiler.setErrorManager(errorManager); return compiler; } private List<SourceFile> findExternFiles() { List<SourceFile> files = Lists.newLinkedList(); if (!this.customExternsOnly) { files.addAll(getDefaultExterns()); } for (FileList list : this.externFileLists) { files.addAll(findJavaScriptFiles(list)); } return files; } private List<SourceFile> findSourceFiles() { List<SourceFile> files = Lists.newLinkedList(); for (FileList list : this.sourceFileLists) { files.addAll(findJavaScriptFiles(list)); } for (Path list : this.sourcePaths) { files.addAll(findJavaScriptFiles(list)); } for (FileSet fileSet : this.sourceFileSets) { files.addAll(findJavaScriptFiles(fileSet)); } return files; } /** * Translates an Ant resource collection into the file list format that the * compiler expects. */ private List<SourceFile> findJavaScriptFiles(ResourceCollection rc) { List<SourceFile> files = Lists.newLinkedList(); Iterator<Resource> iter = rc.iterator(); while (iter.hasNext()) { FileResource fr = iter.next().as(FileResource.class); File file = Paths.get("").toAbsolutePath().relativize(fr.getFile().toPath()).toFile(); files.add(SourceFile.fromFile(file, Charset.forName(encoding))); } return files; } /** * Gets the default externs set. * * Adapted from {@link CommandLineRunner}. */ private List<SourceFile> getDefaultExterns() { try { return CommandLineRunner.getDefaultExterns(); } catch (IOException e) { throw new BuildException(e); } } private void writeResult(File outputFile, String source) { if (outputFile.getParentFile().mkdirs()) { log("Created missing parent directory " + outputFile.getParentFile(), Project.MSG_DEBUG); } try { OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(outputFile), outputEncoding); out.append(source); out.flush(); out.close(); } catch (IOException e) { throw new BuildException(e); } log("Compiled JavaScript written to " + outputFile.getAbsolutePath(), Project.MSG_DEBUG); } }