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;
  
  //apache/ant imports
  import java.io.File;
  import java.time.Instant;
  import java.util.List;
  import java.util.StringTokenizer;
  import java.util.Vector;
  
  import org.apache.tools.ant.BuildException;
  import org.apache.tools.ant.DirectoryScanner;
  import org.apache.tools.ant.Project;
  import org.apache.tools.ant.taskdefs.Java;
  import org.apache.tools.ant.taskdefs.MatchingTask;
  import org.apache.tools.ant.types.Path;
  
  /**
   * Precompiles JSP's using WebLogic's JSP compiler (weblogic.jspc).
   *
   * Tested only on WebLogic 4.5.1 - NT4.0 and Solaris 5.7
   *
   * required attributes
   *      src : root of source tree for JSP, ie, the document root for your WebLogic server
   *      dest : root of destination directory, what you have set as
   *             WorkingDir in the WebLogic properties
   *      package : start package name under which your JSP's would be compiled
   *
   * other attributes
   *     classpath
   *
   * A classpath should be set which contains the WebLogic classes as well as all
   * application classes referenced by the JSP. The system classpath is also
   * appended when the jspc is called, so you may choose to put everything in
   * the classpath while calling Ant. However, since presumably the JSP's will
   * reference classes being build by Ant, it would be better to explicitly add
   * the classpath in the task
   *
   * The task checks timestamps on the JSP's and the generated classes, and compiles
   * only those files that have changed.
   *
   * It follows the WebLogic naming convention of putting classes in
   *  <b> _dirName/_fileName.class for dirname/fileName.jsp   </b>
   *
   * Limitation: It compiles the files through the Classic compiler only.
   * Limitation: Since it is my experience that WebLogic jspc throws out of
   *             memory error on being given too many files at one go, it is
   *             called multiple times with one jsp file each.
   *
   * <pre>
   * example
   * &lt;target name="jspcompile" depends="compile"&gt;
   *   &lt;wljspc src="c:\\weblogic\\myserver\\public_html"
   *           dest="c:\\weblogic\\myserver\\serverclasses" package="myapp.jsp"&gt;
   *   &lt;classpath&gt;
   *          &lt;pathelement location="${weblogic.classpath}" /&gt;
   *           &lt;pathelement path="${compile.dest}" /&gt;
   *      &lt;/classpath&gt;
   *
   *   &lt;/wljspc&gt;
   * &lt;/target&gt;
   * </pre>
   *
   */
  
  public class WLJspc extends MatchingTask {
      //TODO Test on other versions of WebLogic
      //TODO add more attributes to the task, to take care of all jspc options
      //TODO Test on Unix
  
      /** root of compiled files tree */
      private File destinationDirectory;
  
      /** root of source files tree */
      private File sourceDirectory;
  
      /** package under which resultant classes will reside */
      private String destinationPackage;
  
      /** classpath used to compile the jsp files. */
      private Path compileClasspath;
  
      //private String compilerPath; //fully qualified name for the compiler executable
  
      private String pathToPackage = "";
      private List<String> filesToDo = new Vector<>();
  
      /**
       * Run the task.
       * @throws BuildException if there is an error.
       */
      @Override
      public void execute() throws BuildException {
          if (!destinationDirectory.isDirectory()) {
              throw new BuildException("destination directory %s is not valid",
                  destinationDirectory.getPath());
          }
  
          if (!sourceDirectory.isDirectory()) {
              throw new BuildException("src directory %s is not valid",
                  sourceDirectory.getPath());
          }
  
          if (destinationPackage == null) {
              throw new BuildException("package attribute must be present.",
                                       getLocation());
          }
  
          pathToPackage
              = this.destinationPackage.replace('.', File.separatorChar);
          // get all the files in the sourceDirectory
          DirectoryScanner ds = super.getDirectoryScanner(sourceDirectory);
  
          //use the systemclasspath as well, to include the ant jar
          if (compileClasspath == null) {
              compileClasspath = new Path(getProject());
          }
  
          compileClasspath = compileClasspath.concatSystemClasspath();
  
          // WebLogic jspc calls System.exit() ... have to fork
          // Therefore, takes loads of time
          // Can pass directories at a time (*.jsp) but easily runs out of
          // memory on hefty dirs (even on  a Sun)
          Java helperTask = new Java(this);
          helperTask.setFork(true);
          helperTask.setClassname("weblogic.jspc");
          helperTask.setTaskName(getTaskName());
          // CheckStyle:MagicNumber OFF
          String[] args = new String[12];
          // CheckStyle:MagicNumber ON
  
          int j = 0;
          //TODO  this array stuff is a remnant of prev trials.. gotta remove.
          args[j++] = "-d";
          args[j++] = destinationDirectory.getAbsolutePath().trim();
          args[j++] = "-docroot";
          args[j++] = sourceDirectory.getAbsolutePath().trim();
          args[j++] = "-keepgenerated";
          //Call compiler as class... dont want to fork again
          //Use classic compiler -- can be parameterised?
          args[j++] =  "-compilerclass";
          args[j++] = "sun.tools.javac.Main";
          // WebLogic jspc does not seem to work unless u explicitly set this...
          // Does not take the classpath from the env....
          // Am i missing something about the Java task??
          args[j++] = "-classpath";
          args[j++] = compileClasspath.toString();
  
          this.scanDir(ds.getIncludedFiles());
          log("Compiling " + filesToDo.size() + " JSP files");
  
          for (String filename : filesToDo) {
              //TODO
              // All this to get package according to WebLogic standards
              // Can be written better... this is too hacky!
              // Careful.. similar code in scanDir, but slightly different!!
              File jspFile = new File(filename);
              args[j] = "-package";
              String parents = jspFile.getParent();
              if (parents == null || parents.isEmpty()) {
                  args[j + 1] = destinationPackage;
              } else {
                  parents = this.replaceString(parents, File.separator, "_.");
                  args[j + 1] = destinationPackage + "." + "_" + parents;
              }
  
              args[j + 2] =  sourceDirectory + File.separator + filename;
              helperTask.clearArgs();
  
              // CheckStyle:MagicNumber OFF
              for (int x = 0; x < j + 3; x++) {
                  helperTask.createArg().setValue(args[x]);
              }
              // CheckStyle:MagicNumber ON
  
              helperTask.setClasspath(compileClasspath);
              if (helperTask.executeJava() != 0) {
                  log(filename + " failed to compile", Project.MSG_WARN);
              }
          }
      }
  
      /**
       * Set the classpath to be used for this compilation.
       * @param classpath the classpath to use.
       */
      public void setClasspath(Path classpath) {
          if (compileClasspath == null) {
              compileClasspath = classpath;
          } else {
              compileClasspath.append(classpath);
          }
      }
  
      /**
       * Maybe creates a nested classpath element.
       * @return a path to be configured.
       */
      public Path createClasspath() {
          if (compileClasspath == null) {
              compileClasspath = new Path(getProject());
          }
          return compileClasspath;
      }
  
      /**
       * Set the directory containing the source jsp's
       *
       *
       * @param dirName the directory containing the source jsp's
       */
      public void setSrc(File dirName) {
          sourceDirectory = dirName;
      }
  
       /**
       * Set the directory containing the source jsp's
       *
       *
       * @param dirName the directory containing the source jsp's
       */
      public void setDest(File dirName) {
          destinationDirectory = dirName;
      }
  
      /**
       * Set the package under which the compiled classes go
       *
       * @param packageName the package name for the classes
       */
      public void setPackage(String packageName) {
          destinationPackage = packageName;
      }
  
      /**
       * Scan the array of files and add the jsp
       * files that need to be compiled to the filesToDo field.
       * @param files the files to scan.
       */
      protected void scanDir(String[] files) {
          long now = Instant.now().toEpochMilli();
          for (String file : files) {
              File srcFile = new File(this.sourceDirectory, file);
              //TODO
              // All this to convert source to destination directory according
              // to WebLogic standards - can be written better... this is too hacky!
              File jspFile = new File(file);
              String parents = jspFile.getParent();
  
              String pack;
              if (parents == null || parents.isEmpty()) {
                  pack = pathToPackage;
              } else {
                  parents =  this.replaceString(parents, File.separator, "_/");
                  pack = pathToPackage + File.separator + "_" + parents;
              }
  
              String filePath = pack + File.separator + "_";
              int startingIndex = file.lastIndexOf(File.separator) != -1
                      ? file.lastIndexOf(File.separator) + 1 : 0;
              int endingIndex = file.indexOf(".jsp");
              if (endingIndex == -1) {
                  log("Skipping " + file + ". Not a JSP",
                      Project.MSG_VERBOSE);
                  continue;
              }
  
              filePath += file.substring(startingIndex, endingIndex);
              filePath += ".class";
              File classFile = new File(this.destinationDirectory, filePath);
  
              if (srcFile.lastModified() > now) {
                  log("Warning: file modified in the future: "
                      + file, Project.MSG_WARN);
              }
              if (srcFile.lastModified() > classFile.lastModified()) {
                  filesToDo.add(file);
                  log("Recompiling File " + file, Project.MSG_VERBOSE);
              }
          }
      }
  
      /**
       * Replace occurrences of a string with a replacement string.
       * @param inpString the string to convert.
       * @param escapeChars the string to replace.
       * @param replaceChars the string to place.
       * @return the converted string.
       */
      protected String replaceString(String inpString, String escapeChars,
                                     String replaceChars) {
          StringBuilder localString = new StringBuilder();
          StringTokenizer st = new StringTokenizer(inpString, escapeChars, true);
          int numTokens = st.countTokens();
          for (int i = 0; i < numTokens; i++) {
              String test = st.nextToken();
              test = test.equals(escapeChars) ? replaceChars : test;
              localString.append(test);
          }
          return localString.toString();
      }
  }
  