content: /*
   *  Licensed to the Apache Software Foundation (ASF) under one or more
   *  contributor license agreements.  See the NOTICE file distributed with
   *  this work for additional information regarding copyright ownership.
   *  The ASF licenses this file to You 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
   *
   *      https://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.apache.tools.ant.taskdefs.optional.jsp;
  
  import java.io.File;
  import java.time.Instant;
  import java.util.Vector;
  
  import org.apache.tools.ant.AntClassLoader;
  import org.apache.tools.ant.BuildException;
  import org.apache.tools.ant.DirectoryScanner;
  import org.apache.tools.ant.Project;
  import org.apache.tools.ant.taskdefs.MatchingTask;
  import org.apache.tools.ant.taskdefs.optional.jsp.compilers.JspCompilerAdapter;
  import org.apache.tools.ant.taskdefs.optional.jsp.compilers.JspCompilerAdapterFactory;
  import org.apache.tools.ant.types.Path;
  import org.apache.tools.ant.types.Reference;
  
  /**
   * Runs a JSP compiler.
   *
   * <p>This task takes the given jsp files and compiles them into java
   * files. It is then up to the user to compile the java files into classes.</p>
   *
   * <p>The task requires the srcdir and destdir attributes to be
   * set. This Task is a MatchingTask, so the files to be compiled can be
   * specified using includes/excludes attributes or nested include/exclude
   * elements. Optional attributes are verbose (set the verbosity level passed
   * to jasper), package (name of the destination package for generated java
   * classes and classpath (the classpath to use when running the jsp
   * compiler).</p>
   * <p>This task supports the nested elements classpath (a Path) and
   * classpathref (a Reference) which can be used in preference to the
   * attribute classpath, if the jsp compiler is not already in the ant
   * classpath.</p>
   *
   * <h2>Usage</h2>
   * <pre>
   * &lt;jspc srcdir="${basedir}/src/war"
   *       destdir="${basedir}/gensrc"
   *       package="com.i3sp.jsp"
   *       verbose="9"&gt;
   *   &lt;include name="**\/*.jsp" /&gt;
   * &lt;/jspc&gt;
   * </pre>
   *
   * <p>Large amount of cutting and pasting from the Javac task...</p>
   * @since 1.5
   */
  public class JspC extends MatchingTask {
      private Path classpath;
      private Path compilerClasspath;
      private Path src;
      private File destDir;
      private String packageName;
      /** name of the compiler to use */
      private String compilerName = "jasper";
  
      /**
       *  -ieplugin &lt;clsid&gt; Java Plugin classid for Internet Explorer
       */
      private String iepluginid;
      private boolean mapped;
      private int verbose = 0;
      // CheckStyle:VisibilityModifier OFF - bc
      protected Vector<String> compileList = new Vector<>();
      Vector<File> javaFiles = new Vector<>();
  
      /**
       *  flag to control action on execution trouble
       */
      protected boolean failOnError = true;
  
      /**
       *  -uriroot &lt;dir&gt; The root directory that uri files should be resolved
       *  against,
       */
      private File uriroot;
  
      /**
       *  -webinc &lt;file&gt; Creates partial servlet mappings for the -webapp option
       */
      private File webinc;
  
      /**
       *  -webxml &lt;file&gt; Creates a complete web.xml when using the -webapp option.
       */
  
      private File webxml;
  
      /**
       *  web apps
       */
      protected WebAppParameter webApp;
  
  
  
      private static final String FAIL_MSG
          = "Compile failed, messages should have been provided.";
  
      // CheckStyle:VisibilityModifier ON
  
      /**
       * Set the path for source JSP files.
       * @param srcDir the source path.
       */
      public void setSrcDir(Path srcDir) {
          if (src == null) {
              src = srcDir;
          } else {
              src.append(srcDir);
          }
      }
  
      /**
       * Get the source dir.
       * @return the source path.
       */
      public Path getSrcDir() {
          return src;
      }
  
      /**
       * Set the destination directory into which the JSP source
       * files should be compiled.
       * @param destDir the destination directory.
       */
      public void setDestdir(File destDir) {
          this.destDir = destDir;
      }
  
      /**
       * Get the destination directory.
       * @return the directory.
       */
      public File getDestdir() {
          return destDir;
      }
  
      /**
       * Set the name of the package the compiled jsp files should be in.
       * @param pkg the name of the package.
       */
      public void setPackage(String pkg) {
          this.packageName = pkg;
      }
  
      /**
       * Get the name of the package.
       * @return the package.
       */
      public String getPackage() {
          return packageName;
      }
  
      /**
       * Set the verbose level of the compiler
       * @param i the verbose level to use.
       */
      public void setVerbose(int i) {
          verbose = i;
      }
  
      /**
       * Get the verbose level.
       * @return the level.
       */
      public int getVerbose() {
          return verbose;
      }
  
      /**
       * Whether or not the build should halt if compilation fails.
       * Defaults to <code>true</code>.
       * @param fail a <code>boolean</code> value.
       */
      public void setFailonerror(boolean fail) {
          failOnError = fail;
      }
      /**
       * Gets the failonerror flag.
       * @return the flag.
       */
      public boolean getFailonerror() {
          return failOnError;
      }
  
      /**
       * Get the IE CLASSID value.
       * @return the value.
       */
      public String getIeplugin() {
          return iepluginid;
      }
  
      /**
       * Java Plugin CLASSID for Internet Explorer
       * @param iepluginid the id to use.
       */
      public void setIeplugin(String iepluginid) {
          this.iepluginid = iepluginid;
      }
  
      /**
       * If true, generate separate write() calls for each HTML line
       * in the JSP.
       * @return mapping status
       */
      public boolean isMapped() {
          return mapped;
      }
  
      /**
       * If true, generate separate write() calls for each HTML line
       * in the JSP.
       * @param mapped a <code>boolean</code> value.
       */
      public void setMapped(boolean mapped) {
          this.mapped = mapped;
      }
  
      /**
       * The URI context of relative URI references in the JSP pages.
       * If it does not exist then it is derived from the location
       * of the file relative to the declared or derived value of uriroot.
       *
       * @param  uribase  The new Uribase value
       */
      public void setUribase(File uribase) {
          log("Uribase is currently an unused parameter", Project.MSG_WARN);
      }
  
      /**
       * Get the uri base value.
       * @return the value.
       */
      public File getUribase() {
          return uriroot;
      }
  
      /**
       *  The root directory that uri files should be resolved
       *  against. (Default is the directory jspc is invoked from)
       *
       * @param  uriroot  The new Uribase value
       */
      public void setUriroot(File uriroot) {
          this.uriroot = uriroot;
      }
  
      /**
       * Get the uri root value.
       * @return the value.
       */
      public File getUriroot() {
          return uriroot;
      }
  
      /**
       * Set the classpath to be used for this compilation.
       * @param cp the path to be used.
       */
      public void setClasspath(Path cp) {
          if (classpath == null) {
              classpath = cp;
          } else {
              classpath.append(cp);
          }
      }
  
      /**
       * Adds a path to the classpath.
       * @return a path to be configured.
       */
      public Path createClasspath() {
          if (classpath == null) {
              classpath = new Path(getProject());
          }
          return classpath.createPath();
      }
  
      /**
       * Adds a reference to a classpath defined elsewhere
       * @param r a reference to a classpath.
       */
      public void setClasspathRef(Reference r) {
          createClasspath().setRefid(r);
      }
  
      /**
       * Get the classpath.
       * @return the classpath.
       */
      public Path getClasspath() {
          return classpath;
      }
  
      /**
       * Set the classpath to be used to find this compiler adapter
       * @param cp the compiler classpath.
       */
      public void setCompilerclasspath(Path cp) {
          if (compilerClasspath == null) {
              compilerClasspath = cp;
          } else {
              compilerClasspath.append(cp);
          }
      }
  
      /**
       * get the classpath used to find the compiler adapter
       * @return the compiler classpath.
       */
      public Path getCompilerclasspath() {
          return compilerClasspath;
      }
  
      /**
       * Support nested compiler classpath, used to locate compiler adapter
       * @return a path to be configured.
       */
      public Path createCompilerclasspath() {
          if (compilerClasspath == null) {
              compilerClasspath = new Path(getProject());
          }
          return compilerClasspath.createPath();
      }
  
      /**
       *  Filename for web.xml.
       *
       * @param  webxml  The new Webxml value
       */
      public void setWebxml(File webxml) {
          this.webxml = webxml;
      }
  
      /**
       * Filename for web.xml.
       * @return The filename for web.xml.
       */
      public File getWebxml() {
          return this.webxml;
      }
  
      /**
       *  output filename for the fraction of web.xml that lists
       *  servlets.
       * @param  webinc  The new Webinc value
       */
      public void setWebinc(File webinc) {
          this.webinc = webinc;
      }
  
      /**
       * Get the webinc attribute.
       * @return the webinc attribute.
       */
      public File getWebinc() {
          return this.webinc;
      }
  
      /**
       * Adds a single webapp.
       *
       * @param  webappParam  add a web app parameter
       * @throws BuildException if more than one webapp is specified.
       */
      public void addWebApp(WebAppParameter webappParam)
          throws BuildException {
          //demand create vector of filesets
          if (webApp == null) {
              webApp = webappParam;
          } else {
              throw new BuildException("Only one webapp can be specified");
          }
      }
  
      /**
       * Get the web app.
       * @return the web app attribute.
       */
      public WebAppParameter getWebApp() {
          return webApp;
      }
  
      /**
       * Class name of a JSP compiler adapter.
       * @param compiler the compiler class name.
       */
      public void setCompiler(String compiler) {
          this.compilerName = compiler;
      }
  
      /**
       * get the list of files to compile
       * @return the list of files.
       */
      public Vector<String> getCompileList() {
          return compileList;
      }
  
      /**
       * execute by building up a list of files that
       * have changed and hand them off to a jsp compiler
       * @throws BuildException on error.
       */
      @Override
      public void execute()
          throws BuildException {
  
          // make sure that we've got a destdir
          if (destDir == null) {
              throw new BuildException("destdir attribute must be set!",
                                       getLocation());
          }
  
          if (!destDir.isDirectory()) {
              throw new BuildException("destination directory \"" + destDir
                                       + "\" does not exist or is not a directory",
                                       getLocation());
          }
  
          File dest = getActualDestDir();
  
          try (AntClassLoader al = getProject().createClassLoader(compilerClasspath)) {
              //bind to a compiler
              JspCompilerAdapter compiler =
                  JspCompilerAdapterFactory.getCompiler(compilerName, this, al);
  
              //if we are a webapp, hand off to the compiler, which had
              //better handle it
              if (webApp != null) {
                  doCompilation(compiler);
                  return;
              }
  
              // make sure that we've got a srcdir
              if (src == null) {
                  throw new BuildException("srcdir attribute must be set!",
                                           getLocation());
              }
              String[] list = src.list();
              if (list.length == 0) {
                  throw new BuildException("srcdir attribute must be set!",
                                           getLocation());
              }
  
  
              // if the compiler does its own dependency stuff, we just
              // call it right now
              if (compiler.implementsOwnDependencyChecking()) {
                  doCompilation(compiler);
                  return;
              }
  
              //the remainder of this method is only for compilers that
              //need their dependency work done
              JspMangler mangler = compiler.createMangler();
  
              // scan source directories and dest directory to build up both copy
              // lists and compile lists
              resetFileLists();
              int filecount = 0;
              for (String fileName : list) {
                  File srcDir = getProject().resolveFile(fileName);
                  if (!srcDir.exists()) {
                      throw new BuildException("srcdir \"" + srcDir.getPath()
                                               + "\" does not exist!",
                                               getLocation());
                  }
                  DirectoryScanner ds = this.getDirectoryScanner(srcDir);
                  String[] files = ds.getIncludedFiles();
                  filecount = files.length;
                  scanDir(srcDir, dest, mangler, files);
              }
  
              // compile the source files
  
              log("compiling " + compileList.size() + " files",
                  Project.MSG_VERBOSE);
  
              if (!compileList.isEmpty()) {
  
                  log("Compiling " + compileList.size() + " source file"
                      + (compileList.size() == 1 ? "" : "s")
                      + " to "
                      + dest);
                  doCompilation(compiler);
  
              } else if (filecount == 0) {
                  log("there were no files to compile", Project.MSG_INFO);
              } else {
                  log("all files are up to date", Project.MSG_VERBOSE);
              }
          }
      }
  
      /**
       * calculate where the files will end up:
       * this is destDir or it id destDir + the package name
       */
      private File getActualDestDir() {
          if (packageName == null) {
              return destDir;
          }
          return new File(destDir.getPath() + File.separatorChar
              + packageName.replace('.', File.separatorChar));
      }
  
      /**
       * do the compile
       */
      private void doCompilation(JspCompilerAdapter compiler)
          throws BuildException {
          // now we need to populate the compiler adapter
          compiler.setJspc(this);
  
          // finally, lets execute the compiler!!
          if (!compiler.execute()) {
              if (failOnError) {
                  throw new BuildException(FAIL_MSG, getLocation());
              }
              log(FAIL_MSG, Project.MSG_ERR);
          }
      }
  
      /**
       * Clear the list of files to be compiled and copied..
       */
      protected void resetFileLists() {
          compileList.removeAllElements();
      }
  
      /**
       * Scans the directory looking for source files to be compiled.
       * The results are returned in the class variable compileList
       * @param srcDir the source directory.
       * @param dest   the destination directory.
       * @param mangler the jsp filename mangler.
       * @param files   the file names to mangle.
       */
      protected void scanDir(File srcDir, File dest, JspMangler mangler,
                             String[] files) {
  
          long now = Instant.now().toEpochMilli();
  
          for (String filename : files) {
              File srcFile = new File(srcDir, filename);
              File javaFile = mapToJavaFile(mangler, srcFile, srcDir, dest);
              if (javaFile == null) {
                  continue;
              }
              if (srcFile.lastModified() > now) {
                  log("Warning: file modified in the future: " + filename,
                      Project.MSG_WARN);
              }
              if (isCompileNeeded(srcFile, javaFile)) {
                  compileList.addElement(srcFile.getAbsolutePath());
                  javaFiles.addElement(javaFile);
              }
          }
      }
  
      /**
       * Test whether or not compilation is needed. A return value of
       * <code>true<code> means yes, <code>false</code> means
       * our tests do not indicate this, but as the TLDs are
       * not used for dependency checking this is not guaranteed.
       * The current tests are
       * <ol>
       * <li>no dest file
       * <li>dest file out of date w.r.t source
       * <li>dest file zero bytes long
       * </ol>
       * @param srcFile JSP source file
       * @param javaFile JSP dest file
       * @return true if a compile is definitely needed.
       *
       */
      private boolean isCompileNeeded(File srcFile, File javaFile) {
          boolean shouldCompile = false;
          if (!javaFile.exists()) {
              shouldCompile = true;
              log("Compiling " + srcFile.getPath()
                  + " because java file " + javaFile.getPath()
                  + " does not exist", Project.MSG_VERBOSE);
          } else if (srcFile.lastModified() > javaFile.lastModified()) {
              shouldCompile = true;
              log("Compiling " + srcFile.getPath()
                  + " because it is out of date with respect to "
                  + javaFile.getPath(),
                  Project.MSG_VERBOSE);
          } else if (javaFile.length() == 0) {
              shouldCompile = true;
              log("Compiling " + srcFile.getPath()
                  + " because java file " + javaFile.getPath()
                  + " is empty", Project.MSG_VERBOSE);
          }
          return shouldCompile;
      }
  
      /**
       * get a filename from our jsp file.
       * @param mangler the jsp filename manager.
       * @param srcFile the source file.
       * @param srcDir  the source directory.
       * @param dest    the destination directory.
       * @return the filename.
       * @todo support packages and subdirs
       */
      protected File mapToJavaFile(JspMangler mangler, File srcFile, File srcDir,
                                   File dest) {
          if (!srcFile.getName().endsWith(".jsp")) {
              return null;
          }
          String javaFileName = mangler.mapJspToJavaName(srcFile);
          return new File(dest, javaFileName);
      }
  
      /**
       * delete any java output files that are empty
       * this is to get around a little defect in jasper: when it
       * fails, it leaves incomplete files around.
       */
      public void deleteEmptyJavaFiles() {
          if (javaFiles != null) {
              for (File file : javaFiles) {
                  if (file.exists() && file.length() == 0) {
                      log("deleting empty output file " + file);
                      file.delete();
                  }
              }
          }
      }
  
      /**
       * static inner class used as a parameter element
       */
      public static class WebAppParameter {
  
          /**
           * the sole option
           */
          private File directory;
  
          /**
           * query current directory
           * @return the directory.
           */
          public File getDirectory() {
              return directory;
          }
  
          /**
           * set directory; alternate syntax
           * @param directory the base dir.
           */
          public void setBaseDir(File directory) {
              this.directory = directory;
          }
      }
  
  }
  