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;
  
  import java.io.BufferedReader;
  import java.io.File;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.io.StringReader;
  import java.io.StringWriter;
  import java.util.ArrayList;
  import java.util.Hashtable;
  import java.util.List;
  import java.util.Properties;
  import java.util.StringTokenizer;
  import java.util.Vector;
  import java.util.stream.Collectors;
  import java.util.stream.Stream;
  
  import netrexx.lang.Rexx;
  
  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.types.EnumeratedAttribute;
  import org.apache.tools.ant.util.FileUtils;
  
  // CheckStyle:InnerAssignmentCheck OFF - used too much in the file to be removed
  /**
   * Compiles NetRexx source files.
   * This task can take the following
   * arguments:
   * <ul>
   * <li>binary</li>
   * <li>classpath</li>
   * <li>comments</li>
   * <li>compile</li>
   * <li>console</li>
   * <li>crossref</li>
   * <li>decimal</li>
   * <li>destdir</li>
   * <li>diag</li>
   * <li>explicit</li>
   * <li>format</li>
   * <li>keep</li>
   * <li>logo</li>
   * <li>replace</li>
   * <li>savelog</li>
   * <li>srcdir</li>
   * <li>sourcedir</li>
   * <li>strictargs</li>
   * <li>strictassign</li>
   * <li>strictcase</li>
   * <li>strictimport</li>
   * <li>symbols</li>
   * <li>time</li>
   * <li>trace</li>
   * <li>utf8</li>
   * <li>verbose</li>
   * <li>suppressMethodArgumentNotUsed</li>
   * <li>suppressPrivatePropertyNotUsed</li>
   * <li>suppressVariableNotUsed</li>
   * <li>suppressExceptionNotSignalled</li>
   * <li>suppressDeprecation</li>
   * <li>removeKeepExtension</li>
   * </ul>
   * Of these arguments, the <b>srcdir</b> argument is required.
   *
   * <p>When this task executes, it will recursively scan the srcdir
   * looking for NetRexx source files to compile. This task makes its
   * compile decision based on timestamp.</p>
   * <p>Before files are compiled they and any other file in the
   * srcdir will be copied to the destdir allowing support files to be
   * located properly in the classpath. The reason for copying the source files
   * before the compile is that NetRexxC has only two destinations for classfiles:</p>
   * <ol>
   * <li>The current directory, and,</li>
   * <li>The directory the source is in (see sourcedir option)
   * </ol>
   */
  public class NetRexxC extends MatchingTask {
  
      // variables to hold arguments
      private boolean binary;
      private String classpath;
      private boolean comments;
      private boolean compact = true; // should be the default, as it integrates better in ant.
      private boolean compile = true;
      private boolean console;
      private boolean crossref;
      private boolean decimal = true;
      private File destDir;
      private boolean diag;
      private boolean explicit;
      private boolean format;
      private boolean keep;
      private boolean logo = true;
      private boolean replace;
      private boolean savelog;
      private File srcDir;
      private boolean sourcedir = true; // ?? Should this be the default for ant?
      private boolean strictargs;
      private boolean strictassign;
      private boolean strictcase;
      private boolean strictimport;
      private boolean strictprops;
      private boolean strictsignal;
      private boolean symbols;
      private boolean time;
      private String trace = "trace2";
      private boolean utf8;
      private String verbose = "verbose3";
      private boolean suppressMethodArgumentNotUsed = false;
      private boolean suppressPrivatePropertyNotUsed = false;
      private boolean suppressVariableNotUsed = false;
      private boolean suppressExceptionNotSignalled = false;
      private boolean suppressDeprecation = false;
      private boolean removeKeepExtension = false;
  
      // constants for the messages to suppress by flags and their corresponding properties
      static final String MSG_METHOD_ARGUMENT_NOT_USED
          = "Warning: Method argument is not used";
      static final String MSG_PRIVATE_PROPERTY_NOT_USED
          = "Warning: Private property is defined but not used";
      static final String MSG_VARIABLE_NOT_USED
          = "Warning: Variable is set but not used";
      static final String MSG_EXCEPTION_NOT_SIGNALLED
          = "is in SIGNALS list but is not signalled within the method";
      static final String MSG_DEPRECATION = "has been deprecated";
  
      // other implementation variables
      private Vector<String> compileList = new Vector<>();
      private Hashtable<String, String> filecopyList = new Hashtable<>();
  
      /**
       * Set whether literals are treated as binary, rather than NetRexx types.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default is false.
       * @param binary a <code>boolean</code> value.
       */
      public void setBinary(boolean binary) {
          this.binary = binary;
      }
  
      /**
       * Set the classpath used for NetRexx compilation.
       * @param classpath the classpath to use.
       */
      public void setClasspath(String classpath) {
          this.classpath = classpath;
      }
  
      /**
       * Set whether comments are passed through to the generated java source.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param comments a <code>boolean</code> value.
       */
      public void setComments(boolean comments) {
          this.comments = comments;
      }
  
      /**
       * Set whether error messages come out in compact or verbose format.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is true.
       * @param compact a <code>boolean</code> value.
       */
      public void setCompact(boolean compact) {
          this.compact = compact;
      }
  
      /**
       * Set whether the NetRexx compiler should compile the generated java code.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is true.
       * Setting this flag to false, will automatically set the keep flag to true.
       * @param compile a <code>boolean</code> value.
       */
      public void setCompile(boolean compile) {
          this.compile = compile;
          if (!this.compile && !this.keep) {
              this.keep = true;
          }
      }
  
      /**
       * Set whether or not compiler messages should be displayed on the 'console'.
       * Note that this task will rely on the default value for filtering compile messages.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param console a <code>boolean</code> value.
       */
      public void setConsole(boolean console) {
          this.console = console;
      }
  
      /**
       * Whether variable cross references are generated.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param crossref a <code>boolean</code> value.
       */
      public void setCrossref(boolean crossref) {
          this.crossref = crossref;
      }
  
      /**
       * Set whether decimal arithmetic should be used for the netrexx code.
       * Setting this to off will report decimal arithmetic as an error, for
       * performance critical applications.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is true.
       * @param decimal a <code>boolean</code> value.
       */
      public void setDecimal(boolean decimal) {
          this.decimal = decimal;
      }
  
      /**
       * Set the destination directory into which the NetRexx source files
       * should be copied and then compiled.
       * @param destDirName the destination directory.
       */
      public void setDestDir(File destDirName) {
          destDir = destDirName;
      }
  
      /**
       * Whether diagnostic information about the compile is generated
       * @param diag a <code>boolean</code> value.
       */
      public void setDiag(boolean diag) {
          this.diag = diag;
      }
  
      /**
       * Sets whether variables must be declared explicitly before use.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param explicit a <code>boolean</code> value.
       */
      public void setExplicit(boolean explicit) {
          this.explicit = explicit;
      }
  
      /**
       * Whether the generated java code is formatted nicely or left to match
       * NetRexx line numbers for call stack debugging.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value false.
       * @param format a <code>boolean</code> value.
       */
      public void setFormat(boolean format) {
          this.format = format;
      }
  
      /**
       * Whether the generated java code is produced.
       * This is not implemented yet.
       * @param java a <code>boolean</code> value.
       */
      public void setJava(boolean java) {
          log("The attribute java is currently unused.", Project.MSG_WARN);
      }
  
      /**
       * Sets whether the generated java source file should be kept after
       * compilation. The generated files will have an extension of .java.keep,
       * <b>not</b> .java. See setRemoveKeepExtension
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param keep a <code>boolean</code> value.
       * @see #setRemoveKeepExtension(boolean)
       */
      public void setKeep(boolean keep) {
          this.keep = keep;
      }
  
      /**
       * Whether the compiler text logo is displayed when compiling.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param logo a <code>boolean</code> value.
       */
      public void setLogo(boolean logo) {
          this.logo = logo;
      }
  
      /**
       * Whether the generated .java file should be replaced when compiling.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param replace a <code>boolean</code> value.
       */
      public void setReplace(boolean replace) {
          this.replace = replace;
      }
  
      /**
       * Sets whether the compiler messages will be written to NetRexxC.log as
       * well as to the console.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param savelog a <code>boolean</code> value.
       */
      public void setSavelog(boolean savelog) {
          this.savelog = savelog;
      }
  
      /**
       * Tells the NetRexx compiler to store the class files in the same
       * directory as the source files. The alternative is the working directory.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is true.
       * @param sourcedir a <code>boolean</code> value.
       */
      public void setSourcedir(boolean sourcedir) {
          this.sourcedir = sourcedir;
      }
  
      /**
       * Set the source dir to find the source Java files.
       * @param srcDirName the source directory.
       */
      public void setSrcDir(File srcDirName) {
          srcDir = srcDirName;
      }
  
      /**
       * Tells the NetRexx compiler that method calls always need parentheses,
       * even if no arguments are needed, e.g. <code>aStringVar.getBytes</code>
       * vs. <code>aStringVar.getBytes()</code>.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param strictargs a <code>boolean</code> value.
       */
      public void setStrictargs(boolean strictargs) {
          this.strictargs = strictargs;
      }
  
      /**
       * Tells the NetRexx compile that assignments must match exactly on type.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param strictassign a <code>boolean</code> value.
       */
      public void setStrictassign(boolean strictassign) {
          this.strictassign = strictassign;
      }
  
      /**
       * Specifies whether the NetRexx compiler should be case sensitive or not.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param strictcase a <code>boolean</code> value.
       */
      public void setStrictcase(boolean strictcase) {
          this.strictcase = strictcase;
      }
  
      /**
       * Sets whether classes need to be imported explicitly using an <code>import</code>
       * statement. By default the NetRexx compiler will import certain packages
       * automatically.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param strictimport a <code>boolean</code> value.
       */
      public void setStrictimport(boolean strictimport) {
          this.strictimport = strictimport;
      }
  
      /**
       * Sets whether local properties need to be qualified explicitly using
       * <code>this</code>.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param strictprops a <code>boolean</code> value.
       */
      public void setStrictprops(boolean strictprops) {
          this.strictprops = strictprops;
      }
  
      /**
       * Whether the compiler should force catching of exceptions by explicitly
       * named types.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false
       * @param strictsignal a <code>boolean</code> value.
       */
      public void setStrictsignal(boolean strictsignal) {
          this.strictsignal = strictsignal;
      }
  
      /**
       * Sets whether debug symbols should be generated into the class file.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param symbols a <code>boolean</code> value.
       */
      public void setSymbols(boolean symbols) {
          this.symbols = symbols;
      }
  
      /**
       * Asks the NetRexx compiler to print compilation times to the console
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param time a <code>boolean</code> value.
       */
      public void setTime(boolean time) {
          this.time = time;
      }
  
      /**
       * Turns on or off tracing and directs the resultant trace output Valid
       * values are: "trace", "trace1", "trace2" and "notrace". "trace" and
       * "trace2".
       * @param trace the value to set.
       */
      public void setTrace(TraceAttr trace) {
          this.trace = trace.getValue();
      }
  
      /**
       * Turns on or off tracing and directs the resultant trace output Valid
       * values are: "trace", "trace1", "trace2" and "notrace". "trace" and
       * "trace2".
       * @param trace the value to set.
       */
      public void setTrace(String trace) {
          TraceAttr t = new TraceAttr();
          t.setValue(trace);
          setTrace(t);
      }
  
      /**
       * Tells the NetRexx compiler that the source is in UTF8.
       * Valid true values are "yes", "on" or "true". Anything else sets the flag to false.
       * The default value is false.
       * @param utf8 a <code>boolean</code> value.
       */
      public void setUtf8(boolean utf8) {
          this.utf8 = utf8;
      }
  
      /**
       * Whether lots of warnings and error messages should be generated
       * @param verbose the value to set - verbose&lt;level&gt; or noverbose.
       */
      public void setVerbose(VerboseAttr verbose) {
          this.verbose = verbose.getValue();
      }
  
      /**
       * Whether lots of warnings and error messages should be generated
       * @param verbose the value to set - verbose&lt;level&gt; or noverbose.
       */
      public void setVerbose(String verbose) {
          VerboseAttr v = new VerboseAttr();
          v.setValue(verbose);
          setVerbose(v);
      }
  
      /**
       * Whether the task should suppress the "Method argument is not used" in
       * strictargs-Mode, which can not be suppressed by the compiler itself.
       * The warning is logged as verbose message, though.
       * @param suppressMethodArgumentNotUsed a <code>boolean</code> value.
       */
      public void setSuppressMethodArgumentNotUsed(boolean suppressMethodArgumentNotUsed) {
          this.suppressMethodArgumentNotUsed = suppressMethodArgumentNotUsed;
      }
  
      /**
       * Whether the task should suppress the "Private property is defined but
       * not used" in strictargs-Mode, which can be quite annoying while
       * developing. The warning is logged as verbose message, though.
       * @param suppressPrivatePropertyNotUsed a <code>boolean</code> value.
       */
      public void setSuppressPrivatePropertyNotUsed(boolean suppressPrivatePropertyNotUsed) {
          this.suppressPrivatePropertyNotUsed = suppressPrivatePropertyNotUsed;
      }
  
      /**
       * Whether the task should suppress the "Variable is set but not used" in
       * strictargs-Mode. Be careful with this one! The warning is logged as
       * verbose message, though.
       * @param suppressVariableNotUsed a <code>boolean</code> value.
       */
      public void setSuppressVariableNotUsed(boolean suppressVariableNotUsed) {
          this.suppressVariableNotUsed = suppressVariableNotUsed;
      }
  
      /**
       * Whether the task should suppress the "FooException is in SIGNALS list
       * but is not signalled within the method", which is sometimes rather
       * useless. The warning is logged as verbose message, though.
       * @param suppressExceptionNotSignalled a <code>boolean</code> value.
       */
      public void setSuppressExceptionNotSignalled(boolean suppressExceptionNotSignalled) {
          this.suppressExceptionNotSignalled = suppressExceptionNotSignalled;
      }
  
      /**
       * Tells whether we should filter out any deprecation-messages
       * of the compiler out.
       * @param suppressDeprecation a <code>boolean</code> value.
       */
      public void setSuppressDeprecation(boolean suppressDeprecation) {
          this.suppressDeprecation = suppressDeprecation;
      }
  
      /**
       * Tells whether the trailing .keep in nocompile-mode should be removed
       * so that the resulting java source really ends on .java.
       * This facilitates the use of the javadoc tool later on.
       * @param removeKeepExtension boolean
       */
      public void setRemoveKeepExtension(boolean removeKeepExtension) {
          this.removeKeepExtension = removeKeepExtension;
      }
  
      /**
       * init-Method sets defaults from Properties. That way, when ant is called
       * with arguments like -Dant.netrexxc.verbose=verbose5 one can easily take
       * control of all netrexxc-tasks.
       */
      @Override
      public void init() {
          String p;
  
          if ((p = getProject().getProperty("ant.netrexxc.binary")) != null) {
              this.binary = Project.toBoolean(p);
          }
          // classpath makes no sense
          if ((p = getProject().getProperty("ant.netrexxc.comments")) != null) {
              this.comments = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.compact")) != null) {
              this.compact = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.compile")) != null) {
              this.compile = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.console")) != null) {
              this.console = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.crossref")) != null) {
              this.crossref = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.decimal")) != null) {
              this.decimal = Project.toBoolean(p);
              // destDir
          }
          if ((p = getProject().getProperty("ant.netrexxc.diag")) != null) {
              this.diag = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.explicit")) != null) {
              this.explicit = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.format")) != null) {
              this.format = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.keep")) != null) {
              this.keep = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.logo")) != null) {
              this.logo = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.replace")) != null) {
              this.replace = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.savelog")) != null) {
              this.savelog = Project.toBoolean(p);
              // srcDir
          }
          if ((p = getProject().getProperty("ant.netrexxc.sourcedir")) != null) {
              this.sourcedir = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.strictargs")) != null) {
              this.strictargs = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.strictassign")) != null) {
              this.strictassign = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.strictcase")) != null) {
              this.strictcase = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.strictimport")) != null) {
              this.strictimport = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.strictprops")) != null) {
              this.strictprops = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.strictsignal")) != null) {
              this.strictsignal = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.symbols")) != null) {
              this.symbols = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.time")) != null) {
              this.time = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.trace")) != null) {
              setTrace(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.utf8")) != null) {
              this.utf8 = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.verbose")) != null) {
              setVerbose(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.suppressMethodArgumentNotUsed")) != null) {
              this.suppressMethodArgumentNotUsed = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.suppressPrivatePropertyNotUsed")) != null) {
              this.suppressPrivatePropertyNotUsed = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.suppressVariableNotUsed")) != null) {
              this.suppressVariableNotUsed = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.suppressExceptionNotSignalled")) != null) {
              this.suppressExceptionNotSignalled = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.suppressDeprecation")) != null) {
              this.suppressDeprecation = Project.toBoolean(p);
          }
          if ((p = getProject().getProperty("ant.netrexxc.removeKeepExtension")) != null) {
              this.removeKeepExtension = Project.toBoolean(p);
          }
      }
  
      /**
       * Executes the task - performs the actual compiler call.
       * @throws BuildException on error.
       */
      @Override
      public void execute() throws BuildException {
  
          // first off, make sure that we've got a srcdir and destdir
          if (srcDir == null || destDir == null) {
              throw new BuildException("srcDir and destDir attributes must be set!");
          }
  
          // scan source and dest dirs to build up both copy lists and
          // compile lists
          DirectoryScanner ds = getDirectoryScanner(srcDir);
  
          scanDir(srcDir, destDir, ds.getIncludedFiles());
  
          // copy the source and support files
          copyFilesToDestination();
  
          // compile the source files
          if (!compileList.isEmpty()) {
              log("Compiling " + compileList.size() + " source file"
                   + (compileList.size() == 1 ? "" : "s")
                   + " to " + destDir);
              doNetRexxCompile();
              if (removeKeepExtension && (!compile || keep)) {
                  removeKeepExtensions();
              }
          }
      }
  
      /**
       * Scans the directory looking for source files to be compiled and support
       * files to be copied.
       */
      private void scanDir(File srcDir, File destDir, String[] files) {
          for (String filename : files) {
              File srcFile = new File(srcDir, filename);
              File destFile = new File(destDir, filename);
              // if it's a non source file, copy it if a later date than the
              // dest
              // if it's a source file, see if the destination class file
              // needs to be recreated via compilation
              if (filename.toLowerCase().endsWith(".nrx")) {
                  File classFile =
                      new File(destDir,
                      filename.substring(0, filename.lastIndexOf('.')) + ".class");
                  File javaFile =
                      new File(destDir,
                      filename.substring(0, filename.lastIndexOf('.'))
                      + (removeKeepExtension ? ".java" : ".java.keep"));
  
                  // nocompile case tests against .java[.keep] file
                  if (!compile && srcFile.lastModified() > javaFile.lastModified()) {
                      filecopyList.put(srcFile.getAbsolutePath(), destFile.getAbsolutePath());
                      compileList.addElement(destFile.getAbsolutePath());
                  } else if (compile && srcFile.lastModified() > classFile.lastModified()) {
                      // compile case tests against .class file
                      filecopyList.put(srcFile.getAbsolutePath(), destFile.getAbsolutePath());
                      compileList.addElement(destFile.getAbsolutePath());
                  }
              } else if (srcFile.lastModified() > destFile.lastModified()) {
                  filecopyList.put(srcFile.getAbsolutePath(), destFile.getAbsolutePath());
              }
          }
      }
  
      /** Copy eligible files from the srcDir to destDir  */
      private void copyFilesToDestination() {
          if (!filecopyList.isEmpty()) {
              log("Copying " + filecopyList.size() + " file"
                   + (filecopyList.size() == 1 ? "" : "s")
                   + " to " + destDir.getAbsolutePath());
  
              filecopyList.forEach((fromFile, toFile) -> {
                  try {
                      FileUtils.getFileUtils().copyFile(fromFile, toFile);
                  } catch (IOException ioe) {
                      throw new BuildException("Failed to copy " + fromFile
                          + " to " + toFile + " due to " + ioe.getMessage(), ioe);
                  }
              });
          }
      }
  
      /**
       * Rename .java.keep files (back) to .java. The netrexxc renames all
       * .java files to .java.keep if either -keep or -nocompile option is set.
       */
      private void removeKeepExtensions() {
          if (!compileList.isEmpty()) {
              log("Removing .keep extension on " + compileList.size() + " file"
                   + (compileList.size() == 1 ? "" : "s"));
              compileList.forEach(nrxName -> {
                  String baseName =
                      nrxName.substring(0, nrxName.lastIndexOf('.'));
                  File fromFile = new File(baseName + ".java.keep");
                  File toFile = new File(baseName + ".java");
                  if (fromFile.renameTo(toFile)) {
                      log("Successfully renamed " + fromFile + " to " + toFile,
                          Project.MSG_VERBOSE);
                  } else {
                      log("Failed to rename " + fromFile + " to " + toFile);
                  }
              });
          }
      }
  
      /** Performs a compile using the NetRexx 1.1.x compiler  */
      private void doNetRexxCompile() throws BuildException {
          log("Using NetRexx compiler", Project.MSG_VERBOSE);
  
          String classpath = getCompileClasspath();
  
          // create an array of strings for input to the compiler: one array
          // comes from the compile options, the other from the compileList
          String[] compileOptionsArray = getCompileOptionsAsArray();
  
          // print nice output about what we are doing for the log
          log(Stream.of(compileOptionsArray)
              .collect(Collectors.joining(" ", "Compilation args: ", "")),
              Project.MSG_VERBOSE);
  
          log("Files to be compiled:", Project.MSG_VERBOSE);
  
          log(compileList.stream().map(s -> String.format("    %s%n", s))
                          .collect(Collectors.joining("")), Project.MSG_VERBOSE);
  
          // create a single array of arguments for the compiler
          String[] compileArgs =
                  Stream.concat(Stream.of(compileOptionsArray), compileList.stream())
                  .toArray(String[]::new);
  
          // need to set java.class.path property and restore it later
          // since the NetRexx compiler has no option for the classpath
          String currentClassPath = System.getProperty("java.class.path");
          Properties currentProperties = System.getProperties();
  
          currentProperties.put("java.class.path", classpath);
  
          try {
              StringWriter out = new StringWriter();
              PrintWriter w;
              int rc =
                  COM.ibm.netrexx.process.NetRexxC.main(new Rexx(compileArgs),
                                                        w = new PrintWriter(out)); //NOSONAR
              String sdir = srcDir.getAbsolutePath();
              String ddir = destDir.getAbsolutePath();
              boolean doReplace = !(sdir.equals(ddir));
              int dlen = ddir.length();
              BufferedReader in = new BufferedReader(new StringReader(out.toString()));
  
              log("replacing destdir '" + ddir + "' through sourcedir '"
                  + sdir + "'", Project.MSG_VERBOSE);
  
              String l;
              while ((l = in.readLine()) != null) {
                  int idx;
  
                  while (doReplace && ((idx = l.indexOf(ddir)) != -1)) {
                      // path is mentioned in the message
                      l = new StringBuilder(l).replace(idx, idx + dlen, sdir).toString();
                  }
                  // verbose level logging for suppressed messages
                  if (suppressMethodArgumentNotUsed
                      && l.contains(MSG_METHOD_ARGUMENT_NOT_USED)) {
                      log(l, Project.MSG_VERBOSE);
                  } else if (suppressPrivatePropertyNotUsed
                      && l.contains(MSG_PRIVATE_PROPERTY_NOT_USED)) {
                      log(l, Project.MSG_VERBOSE);
                  } else if (suppressVariableNotUsed
                      && l.contains(MSG_VARIABLE_NOT_USED)) {
                      log(l, Project.MSG_VERBOSE);
                  } else if (suppressExceptionNotSignalled
                      && l.contains(MSG_EXCEPTION_NOT_SIGNALLED)) {
                      log(l, Project.MSG_VERBOSE);
                  } else if (suppressDeprecation
                      && l.contains(MSG_DEPRECATION)) {
                      log(l, Project.MSG_VERBOSE);
                  } else if (l.contains("Error:")) {
                      // error level logging for compiler errors
                      log(l, Project.MSG_ERR);
                  } else if (l.contains("Warning:")) {
                      // warning for all warning messages
                      log(l, Project.MSG_WARN);
                  } else {
                      log(l, Project.MSG_INFO); // info level for the rest.
                  }
              }
              if (rc > 1) {
                  throw new BuildException(
                      "Compile failed, messages should have been provided.");
              }
              if (w.checkError()) {
                  throw new IOException("Encountered an error");
              }
          } catch (IOException ioe) {
              throw new BuildException(
                  "Unexpected IOException while playing with Strings", ioe);
          } finally {
              // need to reset java.class.path property
              // since the NetRexx compiler has no option for the classpath
              currentProperties = System.getProperties();
              currentProperties.put("java.class.path", currentClassPath);
          }
      }
  
      /** Builds the compilation classpath.  */
      private String getCompileClasspath() {
          StringBuilder classpath = new StringBuilder();
  
          // add dest dir to classpath so that previously compiled and
          // untouched classes are on classpath
          classpath.append(destDir.getAbsolutePath());
  
          // add our classpath to the mix
          if (this.classpath != null) {
              addExistingToClasspath(classpath, this.classpath);
          }
  
          // add the system classpath
          return classpath.toString();
      }
  
      /** This  */
      private String[] getCompileOptionsAsArray() {
          List<String> options = new ArrayList<>();
  
          options.add(binary ? "-binary" : "-nobinary");
          options.add(comments ? "-comments" : "-nocomments");
          options.add(compile ? "-compile" : "-nocompile");
          options.add(compact ? "-compact" : "-nocompact");
          options.add(console ? "-console" : "-noconsole");
          options.add(crossref ? "-crossref" : "-nocrossref");
          options.add(decimal ? "-decimal" : "-nodecimal");
          options.add(diag ? "-diag" : "-nodiag");
          options.add(explicit ? "-explicit" : "-noexplicit");
          options.add(format ? "-format" : "-noformat");
          options.add(keep ? "-keep" : "-nokeep");
          options.add(logo ? "-logo" : "-nologo");
          options.add(replace ? "-replace" : "-noreplace");
          options.add(savelog ? "-savelog" : "-nosavelog");
          options.add(sourcedir ? "-sourcedir" : "-nosourcedir");
          options.add(strictargs ? "-strictargs" : "-nostrictargs");
          options.add(strictassign ? "-strictassign" : "-nostrictassign");
          options.add(strictcase ? "-strictcase" : "-nostrictcase");
          options.add(strictimport ? "-strictimport" : "-nostrictimport");
          options.add(strictprops ? "-strictprops" : "-nostrictprops");
          options.add(strictsignal ? "-strictsignal" : "-nostrictsignal");
          options.add(symbols ? "-symbols" : "-nosymbols");
          options.add(time ? "-time" : "-notime");
          options.add("-" + trace);
          options.add(utf8 ? "-utf8" : "-noutf8");
          options.add("-" + verbose);
  
          return options.toArray(new String[0]);
      }
  
      /**
       * Takes a classpath-like string, and adds each element of this string to
       * a new classpath, if the components exist. Components that don't exist,
       * aren't added. We do this, because jikes issues warnings for
       * non-existent files/dirs in his classpath, and these warnings are pretty
       * annoying.
       *
       * @param target - target classpath
       * @param source - source classpath to get file objects.
       */
      private void addExistingToClasspath(StringBuilder target, String source) {
          StringTokenizer tok = new StringTokenizer(source,
              File.pathSeparator, false);
  
          while (tok.hasMoreTokens()) {
              File f = getProject().resolveFile(tok.nextToken());
  
              if (f.exists()) {
                  target.append(File.pathSeparator);
                  target.append(f.getAbsolutePath());
              } else {
                  log("Dropping from classpath: "
                      + f.getAbsolutePath(), Project.MSG_VERBOSE);
              }
          }
      }
  
      /**
       * Enumerated class corresponding to the trace attribute.
       */
      public static class TraceAttr extends EnumeratedAttribute {
          /** {@inheritDoc}. */
          @Override
          public String[] getValues() {
              return new String[] {"trace", "trace1", "trace2", "notrace"};
          }
      }
  
      /**
       * Enumerated class corresponding to the verbose attribute.
       */
      public static class VerboseAttr extends EnumeratedAttribute {
          /** {@inheritDoc}. */
          @Override
          public String[] getValues() {
              return new String[] {"verbose", "verbose0", "verbose1", "verbose2",
                  "verbose3", "verbose4", "verbose5", "noverbose"};
          }
      }
  }
  