com.ppedregal.typescript.maven.TscMojo.java Source code

Java tutorial

Introduction

Here is the source code for com.ppedregal.typescript.maven.TscMojo.java

Source

package com.ppedregal.typescript.maven;

/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * 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.
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeJavaObject;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.commonjs.module.RequireBuilder;
import org.mozilla.javascript.commonjs.module.provider.SoftCachingModuleScriptProvider;

/**
 * Goal which compiles a set of TypeScript files
 *
 * @goal tsc
 * 
 * @phase compile
 */
public class TscMojo extends AbstractMojo {
    /**
     * Output directory for .js compiled files
     * @parameter expression="${ts.targetDirectory}" default-value="target/ts"
     * @required
     */
    private File targetDirectory;

    /**
     * Source directory for .ts source files
     * @parameter expression="${ts.sourceDirectory}" default-value="src/main/ts"
     */
    private File sourceDirectory;

    /**
     * Source directory for .d.ts source files
     * @parameter expression="${ts.libraryDirectory}" default-value="src/main/d.ts"
     */
    private File libraryDirectory;

    /**
     * The lib.d.ts file used to pass into the compiler if required
     *
     * @parameter expression="${ts.libDTS}" default-value="src/main/tsc/lib.d.ts"
     */
    private File libDTS;

    /**
     * Encoding for files
     * @parameter expression="${project.build.sourceEncoding}
     */
    private String encoding = "utf-8";

    /**
     * Set to true to watch for changes to files and re-compile them on the fly.
     *
     * @parameter expression="${ts.watch}"
     */
    private boolean watch = false;

    /**
     * Set to true to use the command line 'tsc' executable if its on the PATH
     *
     * @parameter expression="${ts.useTsc}"
     */
    private boolean useTsc = false;

    /**
     * Set to true to ignore the lib.d.ts by default
     *
     * @parameter expression="${ts.nolib}"
     */
    private boolean noStandardLib = true;

    /**
     * The amount of millis to wait before polling the source files for changes
     *
     * @parameter expression="${ts.pollTime}"
     */
    private long pollTime = 100;

    private Script nodeScript;
    private Script tscScript;
    private ScriptableObject globalScope;
    private boolean watching;

    public void execute() throws MojoExecutionException {
        sourceDirectory.mkdirs();
        targetDirectory.mkdirs();

        try {
            compileScripts();
        } catch (IOException e) {
            throw createMojoExecutionException(e);
        }

        doCompileFiles(false);

        if (watch) {
            watching = true;
            getLog().info("Waiting for changes to " + sourceDirectory + " polling every " + pollTime + " millis");
            checkForChangesEvery(pollTime);
        }
    }

    private void checkForChangesEvery(long ms) throws MojoExecutionException {
        FileSetChangeMonitor monitor = new FileSetChangeMonitor(sourceDirectory, "**/*.ts");
        try {
            while (true) {
                Thread.sleep(ms);
                List<String> modified = monitor.getModifiedFilesSinceLastTimeIAsked();
                if (modified.size() > 0) {
                    // TODO ideally we'd just pass in the files to compile here...
                    doCompileFiles(true);
                }
            }
        } catch (InterruptedException e) {
            getLog().info("Caught interrupt, quitting.");
        }
    }

    private void doCompileFiles(boolean checkTimestamp) throws MojoExecutionException {
        Collection<File> files = FileUtils.listFiles(sourceDirectory, new String[] { "ts" }, true);
        doCompileFiles(checkTimestamp, files);
    }

    private void doCompileFiles(boolean checkTimestamp, Collection<File> files) throws MojoExecutionException {
        try {
            int compiledFiles = 0;
            if (!watching) {
                getLog().info("Searching directory " + sourceDirectory.getCanonicalPath());
            }
            for (File file : files) {
                try {
                    String path = file.getPath().substring(sourceDirectory.getPath().length());
                    String sourcePath = path;
                    String targetPath = FilenameUtils.removeExtension(path) + ".js";
                    File sourceFile = new File(sourceDirectory, sourcePath).getAbsoluteFile();
                    File targetFile = new File(targetDirectory, targetPath).getAbsoluteFile();
                    if (!targetFile.exists() || !checkTimestamp
                            || sourceFile.lastModified() > targetFile.lastModified()) {
                        String sourceFilePath = sourceFile.getPath();
                        getLog().info(String.format("Compiling: %s", sourceFilePath));
                        String generatePath = targetFile.getPath();
                        tsc("--out", generatePath, sourceFilePath);
                        getLog().info(String.format("Generated: %s", generatePath));
                        compiledFiles++;
                    }
                } catch (TscInvocationException e) {
                    getLog().error(e.getMessage());
                    if (getLog().isDebugEnabled()) {
                        getLog().debug(e);
                    }
                }
            }
            if (compiledFiles == 0) {
                getLog().info("Nothing to compile");
            } else {
                getLog().info(String.format("Compiled %s file(s)", compiledFiles));
            }
        } catch (IOException e) {
            throw createMojoExecutionException(e);
        }
    }

    private void compileScripts() throws IOException {
        try {
            Context.enter();
            Context ctx = Context.getCurrentContext();
            ctx.setOptimizationLevel(9);
            globalScope = ctx.initStandardObjects();
            RequireBuilder require = new RequireBuilder();
            require.setSandboxed(false);
            require.setModuleScriptProvider(
                    new SoftCachingModuleScriptProvider(new ClasspathModuleSourceProvider()));
            require.createRequire(ctx, globalScope).install(globalScope);
            nodeScript = compile(ctx, "node.js");
            tscScript = compile(ctx, "tsc.js");
        } finally {
            Context.exit();

        }
        Context.enter();
    }

    private Script compile(Context context, String resource) throws IOException {
        InputStream stream = TscMojo.class.getClassLoader().getResourceAsStream(resource);
        if (stream == null) {
            throw new FileNotFoundException("Resource open error: " + resource);
        }
        try {
            return context.compileReader(new InputStreamReader(stream), resource, 1, null);
        } catch (IOException e) {
            throw new IOException("Resource read error: " + resource);
        } finally {
            try {
                stream.close();
            } catch (IOException e) {
                throw new IOException("Resource close error: " + resource);
            }
            stream = null;
        }
    }

    private void tsc(String... args) throws TscInvocationException, MojoExecutionException {
        if (useBinary(args)) {
            return;
        }

        try {
            Context.enter();
            Context ctx = Context.getCurrentContext();

            nodeScript.exec(ctx, globalScope);

            NativeObject proc = (NativeObject) globalScope.get("process");
            NativeArray argv = (NativeArray) proc.get("argv");
            argv.defineProperty("length", 0, ScriptableObject.EMPTY);
            int i = 0;
            argv.put(i++, argv, "node");
            argv.put(i++, argv, "tsc.js");
            if (noStandardLib) {
                argv.put(i++, argv, "--nolib");
            }
            if (libDTS != null && libDTS.exists()) {
                if (!watching) {
                    getLog().info("Adding standard library file " + libDTS);
                }
                argv.put(i++, argv, libDTS.getAbsolutePath());

            }
            if (libraryDirectory != null && libraryDirectory.exists()) {
                File[] libFiles = libraryDirectory.listFiles();
                if (libFiles != null) {
                    for (File libFile : libFiles) {
                        if (libFile.getName().endsWith(".d.ts") && libFile.exists()) {
                            if (!watching) {
                                getLog().info("Adding library file " + libFile);
                            }
                            argv.put(i++, argv, libFile.getAbsolutePath());
                        }
                    }
                }
            }
            for (String s : args) {
                argv.put(i++, argv, s);
            }
            proc.defineProperty("encoding", encoding, ScriptableObject.READONLY);

            NativeObject mainModule = (NativeObject) proc.get("mainModule");
            mainModule.defineProperty("filename", new File("tsc.js").getAbsolutePath(), ScriptableObject.READONLY);

            tscScript.exec(ctx, globalScope);

        } catch (JavaScriptException e) {
            if (e.getValue() instanceof NativeJavaObject) {
                NativeJavaObject njo = (NativeJavaObject) e.getValue();
                Object o = njo.unwrap();
                if (o instanceof ProcessExit) {
                    ProcessExit pe = (ProcessExit) o;
                    if (pe.getStatus() != 0) {
                        throw new TscInvocationException("Process Error: " + pe.getStatus());
                    }
                } else {
                    throw new TscInvocationException("Javascript Error", e);
                }
            } else {
                throw new TscInvocationException("Javascript Error", e);
            }
        } catch (RhinoException e) {
            getLog().error(e.getMessage());
            throw new TscInvocationException("Rhino Error", e);
        } finally {
            org.mozilla.javascript.Context.exit();
        }
    }

    private boolean useBinary(String[] args) throws MojoExecutionException {
        if (useTsc) {

            // lets try execute the 'tsc' executable directly
            List<String> arguments = new ArrayList<String>();
            arguments.add("tsc");
            if (libraryDirectory.exists()) {
                File[] libFiles = libraryDirectory.listFiles();
                if (libFiles != null) {
                    for (File libFile : libFiles) {
                        if (libFile.getName().endsWith(".d.ts") && libFile.exists()) {
                            String path = libFile.getAbsolutePath();
                            if (!watching) {
                                getLog().info("Adding library file " + libFile);
                            }
                            arguments.add(path);
                        }
                    }
                }
            }
            for (String arg : args) {
                arguments.add(arg);
            }

            getLog().debug("About to execute command: " + arguments);
            ProcessBuilder builder = new ProcessBuilder(arguments);
            try {
                Process process = builder.start();
                redirectOutput(process.getInputStream(), false);
                redirectOutput(process.getErrorStream(), true);

                int value = process.waitFor();
                if (value != 0) {
                    getLog().error("Failed to execute tsc. Return code: " + value);
                } else {
                    getLog().debug("Compiled file successfully");
                }
            } catch (IOException e) {
                getLog().error("Failed to execute tsc: " + e);
                throw createMojoExecutionException(e);
            } catch (InterruptedException e) {
                throw new MojoExecutionException(e.getMessage());
            }
            return true;
        }
        return false;
    }

    private void redirectOutput(InputStream is, boolean error) throws IOException {
        try {
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            while (true) {
                String line = br.readLine();
                if (line == null) {
                    break;
                }
                if (error) {
                    getLog().error(line);
                } else {
                    getLog().info(line);
                }
            }
        } finally {
            is.close();
        }
    }

    private MojoExecutionException createMojoExecutionException(IOException e) {
        return new MojoExecutionException(e.getMessage());
    }

    public File getTargetDirectory() {
        return targetDirectory;
    }

    public void setTargetDirectory(File targetDirectory) {
        this.targetDirectory = targetDirectory;
    }

    public File getSourceDirectory() {
        return sourceDirectory;
    }

    public void setSourceDirectory(File sourceDirectory) {
        this.sourceDirectory = sourceDirectory;
    }

    public String getEncoding() {
        return encoding;
    }

    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

}