Package com.google.caja.tools

Source Code of com.google.caja.tools.AbstractCajaAntTask$Output

// Copyright (C) 2008 Google Inc.
//
// 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 com.google.caja.tools;

import com.google.caja.plugin.BuildServiceImplementation;
import com.google.caja.util.Lists;
import com.google.caja.util.Sets;
import com.google.caja.util.Strings;

import java.io.File;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;

/**
* An ANT task that operates on a set of files to produce a single output file
* via a {@link BuildService}.
*
* @see <a href="http://ant.apache.org/manual/tutorial-writing-tasks.html">ANT
*     Task Tutorial</a>
* @see <a href="http://ant.apache.org/manual/CoreTasks/typedef.html">Deploying
*     Tasks</a>
*
* @author mikesamuel@gmail.com
*/
public abstract class AbstractCajaAntTask extends Task {
  /** Input files to compile. */
  private final List<FileGroup> inputs = Lists.newArrayList();
  /** Files that the inputs might depend upon. */
  private final List<FileGroup> depends = Lists.newArrayList();
  /** Outputs to generate. */
  private final List<Output> outputs = Lists.newArrayList();

  /** Called to actually execute a job by invoking the BuildService. */
  protected abstract boolean run(BuildService buildService, PrintWriter logger,
                                 List<File> depends, List<File> inputs,
                                 File output, Map<String, Object> options)
      throws BuildException;

  @Override
  public void execute() throws BuildException {
    // TODO(jasvir): No-op that forces Strings to be loaded and initialized
    // before classloading classes that use it eg. in "ant jsdocs"
    Strings.lower("");
    try {
      for (FileGroup input : inputs) { input.requireExecutable(); }
      for (Output output : outputs) { output.requireExecutable(); }
      for (FileGroup depend : depends) { depend.requireExecutable(); }

      long youngest = Long.MIN_VALUE;
      List<File> inputFiles = Lists.newArrayList();
      for (FileGroup input : inputs) { inputFiles.addAll(input.files); }
      for (File file : inputFiles) {
        youngest = Math.max(youngest, file.lastModified());
      }
      if (Sets.newHashSet(inputFiles).size() != inputFiles.size()) {
        List<File> dupes = Lists.newArrayList(inputFiles);
        for (File f : Sets.newHashSet(inputFiles)) { dupes.remove(f); }
        throw new BuildException("Duplicate inputs: " + dupes);
      }
      List<File> dependees = Lists.newArrayList();
      for (FileGroup depend : depends) { dependees.addAll(depend.files); }
      for (File file : dependees) {
        youngest = Math.max(youngest, file.lastModified());
      }

      BuildService buildService = getBuildService();
      PrintWriter logger = getLogger();
      try {
        for (Output output : outputs) {
          output.build(inputFiles, dependees, youngest, buildService, logger);
        }
      } finally {
        logger.flush();
      }
    } catch (RuntimeException ex) {
      ex.printStackTrace();
      throw new BuildException(ex);
    }
  }

  protected List<File> jsLintInputs() {
    List<File> inputFiles = Lists.newArrayList();
    for (FileGroup input : inputs) {
      if (input.hasJsLint()) {
        inputFiles.addAll(input.files);
      }
    }
    return inputFiles;
  }

  /**
   * Wrap {@link Task#log(String)} in a PrintWriter so BuildService doesn't have
   * to know about ANT.
   */
  PrintWriter getLogger() {
    return new PrintWriter(
        new Writer() {
          StringBuilder sb = new StringBuilder();
          @Override
          public void write(char[] cbuf, int off, int len) {
            sb.append(cbuf, off, len);
          }
          @Override
          public void flush() {
            if (sb.length() != 0) {
              log(sb.toString().trim());
              sb = new StringBuilder();
            }
          }
          @Override
          public void close() { /* noop */ }
        }, true);
  }

  BuildService getBuildService() throws BuildException {
    return new BuildServiceImplementation();
  }

  /** Invoked reflectively whenever {@code <input>} is seen. */
  public FileGroup createInput() {
    FileGroup input = new FileGroup("<input>");
    inputs.add(input);
    return input;
  }

  /** Invoked reflectively whenever {@code <depend>} is seen. */
  public FileGroup createDepend() {
    FileGroup depend = new FileGroup("<depend>");
    depends.add(depend);
    return depend;
  }

  /** Invoked reflectively whenever {@code <output>} is seen. */
  public final Output createOutput() {
    Output output = makeOutput();
    outputs.add(output);
    return output;
  }

  abstract Output makeOutput();

  /** Encapsulates input files and the output files that should be produced. */
  public abstract class Output {
    /** The file to build. */
    private File output;

    /**
     * The below are invoked reflectively by ant when {@code <job>} attributes
     * are seen.
     */
    public void setFile(File output) { this.output = output; }

    /** Checks that there is enough information to execute before execution. */
    void requireExecutable() throws BuildException {
      if (output == null) {
        throw new BuildException(
            "<output> at " + getLocation() + "missing 'file' attribute");
      }
    }

    /** Builds output, throwing a BuildException if build fails. */
    void build(List<File> inputs, List<File> dependees, long youngest,
               BuildService buildService, PrintWriter logger)
        throws BuildException {
      File outputTimeFile;
      if (output.isDirectory()) {
        outputTimeFile = new File(output, ".tstamp");
      } else {
        outputTimeFile = output;
      }
      long outputModified = outputTimeFile.lastModified();
      boolean modified  // -> the output file is older than any inputs.
          = (outputModified == 0L    // 0L -> !output.exists()
              || outputModified < youngest);

      if (modified) {
        // Make sure the output directory exists.
        File outputDir = output.getParentFile();
        if (!outputDir.exists()) {
          logger.println("mkdir " + outputDir);
          outputDir.mkdirs();
        }

        logger.println("building " + inputs.size() + " files to " + output);
        Map<String, Object> options = getOptions();
        boolean success = false;
        try {
          success = run(
              buildService, logger, dependees, inputs, output, options);
          if (!success) {
            throw new BuildException("Failed to build " + output);
          }
        } finally {
          if (!success) {
            if (outputTimeFile.exists()) { outputTimeFile.delete(); }
          }
        }
      }
    }

    /**
     * A map of options that are specified on the {@code output} element as
     * expected by {@link AbstractCajaAntTask#run}.
     */
    public Map<String, Object> getOptions() {
      return new HashMap<String, Object>();
    }
  }

  /**
   * A group of files.  This supports both the {file="path"} attribute and
   * nested filesets.
   */
  public class FileGroup {
    private final List<File> files = Lists.newArrayList();
    private final String originTag;
    private boolean jsLint = true;

    FileGroup(String originTag) { this.originTag = originTag; }

    // Invoked reflectively by ant
    public void setFile(File file) {
      this.files.add(file);
    }
    public void setJsLint(boolean jsLint) {
      this.jsLint = jsLint;
    }

    public boolean hasJsLint() {
      return jsLint;
    }

    // See http://ant.apache.org/manual/develop.html#nested-elements
    public void addConfiguredFileSet(FileSet fs) {
      DirectoryScanner scanner = fs.getDirectoryScanner(getProject());
      File baseDir = scanner.getBasedir();
      scanner.scan();
      String[] includedFiles = scanner.getIncludedFiles();
      Arrays.sort(includedFiles);
      for (String localPath : includedFiles) {
        files.add(new File(baseDir, localPath));
      }
    }

    void requireExecutable() throws BuildException {
      if (files.isEmpty()) {
        throw new BuildException(originTag + " at " + getLocation()
                                 + "missing 'file' attribute and '<fileset>'");
      }
      for (File file : files) {
        if (!file.canRead()) {
          throw new BuildException(originTag + " at " + getLocation()
                                   + "of '" + file + "' is not readable");
        }
      }
    }
  }
}
TOP

Related Classes of com.google.caja.tools.AbstractCajaAntTask$Output

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.