Merge pull request #135 from apache/property-enumerator

Property enumerator
diff --git a/manual/Tasks/script.html b/manual/Tasks/script.html
index 7cb6a47..e029cd9 100644
--- a/manual/Tasks/script.html
+++ b/manual/Tasks/script.html
@@ -274,11 +274,25 @@
 full classified name with <strong>Packages</strong>. For example
 Ant's <code class="code">FileUtils</code> class can be imported
 with <code class="code">importClass(<strong>Packages</strong>.org.apache.tools.ant.util.FileUtils)</code></p>
-<p>In Java 8+, you may use the built-in Nashorn JavaScript engine rather than Rhino (which is
+<p>In Java 8 up until Java 14, you may use the built-in Nashorn JavaScript engine rather than Rhino (which is
 available in Java 7 runtime). Then, use <code>Java.type</code> as import statement for any Java
 class
 or <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/prog_guide/javascript.html#A1147207">the
 compatibility script</a>: <code>load("nashorn:mozilla_compat.js");</code>.</p>
+
+<p>Starting with Java 15 Nashorn has been removed again and you need
+  to provide an external JavaScript engine. Your best option probably
+  is <a href="https://github.com/graalvm/graaljs">GraalVM
+  JavaScript</a> which requires you to add a lot of extra jars. For
+  GraalVM JavaScript 20.1 you'll
+  need <code>org.graalvm.js:js</code>, <code>org.graalvm.js:js-engine</code>
+  which in turn
+  require <code>org.graalvm.regex:regex</code>, <code>org.graalvm.truffle:truffle-api</code>, <code>org.graalvm.sdk:graal-sdk</code>,
+  and <code>com.ibm.icu:icu4j</code>. GraalVM JavaScript is not a
+  drop-in replacement for Nashorn, see
+  Graal's <a href="https://github.com/graalvm/graaljs/blob/master/docs/user/NashornMigrationGuide.md">Nashorn
+  Migration Guide</a> for more details.</p>
+
 <p>The <code>&lt;script&gt;</code> task populates the Project instance under the
 name <code class="code">project</code>, so we can use that reference. Another way is to use its
 given name or getting its reference from the task itself. The Project provides methods for accessing
diff --git a/manual/install.html b/manual/install.html
index 4938e63..3984bee 100644
--- a/manual/install.html
+++ b/manual/install.html
@@ -828,12 +828,27 @@
            target="_top">https://www.ibm.com/software/awdtools/netrexx/library.html</a></td>
   </tr>
   <tr>
-    <td>rhino.jar<br/>(included in Java 7 runtime, replaced by Nashorn in Java&nbsp;8 and later)</td>
+    <td>rhino.jar<br/>(included in Java 7 runtime, replaced by Nashorn
+      in Java&nbsp;8 and later, dropped with Java 15)</td>
     <td>JavaScript with <a href="Tasks/script.html">script</a> task<br/><strong>Note</strong>: Apache BSF 2.4.0 works
       only with Rhino 1.5R4 and later versions.</td>
     <td><a href="https://www.mozilla.org/rhino/" target="_top">https://www.mozilla.org/rhino/</a></td>
   </tr>
   <tr>
+    <td>graalvm js.jar and js-scriptengine.jar<br/></td>
+    <td>JavaScript with <a href="Tasks/script.html">script</a> task for Java 15 and later<br/>
+    </td>
+    <td>Java 15 has dropped Nashorn
+      and <a href="https://github.com/graalvm/graaljs">GraalVM JavaScript</a>
+      is meant to replace it - outside of the Java class
+      library.<br/>
+      <strong>Note</strong> GraalVM JavaScript is not a drop-in
+      replacement for Nashorn, see the script task documentation for
+      details. Also GraakVM JavaScript requires a couple of more
+      dependencies, in particular GraalVM regex, truffle, the GraalVM
+      SDK and ICU.</td>
+  </tr>
+  <tr>
     <td>jython.jar</td>
     <td>Python with <a href="Tasks/script.html">script</a> task</td>
     <td><a href="https://www.jython.org/" target="_top">https://www.jython.org/</a></td>
diff --git a/src/main/org/apache/tools/ant/MagicNames.java b/src/main/org/apache/tools/ant/MagicNames.java
index 5cf2fa8..8ced505 100644
--- a/src/main/org/apache/tools/ant/MagicNames.java
+++ b/src/main/org/apache/tools/ant/MagicNames.java
@@ -337,5 +337,15 @@
      * @since Ant 1.10.8
      */
     public static final String TMPDIR = "ant.tmpdir";
+
+    /**
+     * Magic property that will be set to override java.io.tmpdir
+     * system property as the location for Ant's default temporary
+     * directory if a temp file is created and {@link #TMPDIR} is not
+     * set.
+     * Value: {@value}
+     * @since Ant 1.10.9
+     */
+    public static final String AUTO_TMPDIR = "ant.auto.tmpdir";
 }
 
diff --git a/src/main/org/apache/tools/ant/util/FileUtils.java b/src/main/org/apache/tools/ant/util/FileUtils.java
index 4667184..d835438 100644
--- a/src/main/org/apache/tools/ant/util/FileUtils.java
+++ b/src/main/org/apache/tools/ant/util/FileUtils.java
@@ -110,6 +110,11 @@
             PosixFilePermissions.asFileAttribute(EnumSet.of(PosixFilePermission.OWNER_READ,
                 PosixFilePermission.OWNER_WRITE))
         };
+    private static final FileAttribute[] TMPDIR_ATTRIBUTES =
+        new FileAttribute[] {
+            PosixFilePermissions.asFileAttribute(EnumSet.of(PosixFilePermission.OWNER_READ,
+                PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE))
+        };
     private static final FileAttribute[] NO_TMPFILE_ATTRIBUTES = new FileAttribute[0];
 
     /**
@@ -991,14 +996,35 @@
     public File createTempFile(final Project project, String prefix, String suffix,
             final File parentDir, final boolean deleteOnExit, final boolean createFile) {
         File result;
-        final String parent;
+        String p = null;
         if (parentDir != null) {
-            parent = parentDir.getPath();
+            p = parentDir.getPath();
         } else if (project != null && project.getProperty(MagicNames.TMPDIR) != null) {
-            parent = project.getProperty(MagicNames.TMPDIR);
-        } else {
-            parent = System.getProperty("java.io.tmpdir");
+            p = project.getProperty(MagicNames.TMPDIR);
+        } else if (project != null && deleteOnExit) {
+            if (project.getProperty(MagicNames.AUTO_TMPDIR) != null) {
+                p = project.getProperty(MagicNames.AUTO_TMPDIR);
+            } else {
+                final Path systemTempDirPath =
+                    new File(System.getProperty("java.io.tmpdir")).toPath();
+                final PosixFileAttributeView systemTempDirPosixAttributes =
+                    Files.getFileAttributeView(systemTempDirPath, PosixFileAttributeView.class);
+                if (systemTempDirPosixAttributes != null) {
+                    // no reason to create an extra temp dir if we cannot set permissions
+                    try {
+                        final File projectTempDir = Files.createTempDirectory(systemTempDirPath,
+                            "ant", TMPDIR_ATTRIBUTES)
+                            .toFile();
+                        projectTempDir.deleteOnExit();
+                        p = projectTempDir.getAbsolutePath();
+                        project.setProperty(MagicNames.AUTO_TMPDIR, p);
+                    } catch (IOException ex) {
+                        // silently fall back to system temp directory
+                    }
+                }
+            }
         }
+        final String parent = p != null ? p : System.getProperty("java.io.tmpdir");
         if (prefix == null) {
             prefix = NULL_PLACEHOLDER;
         }
diff --git a/src/main/org/apache/tools/ant/util/optional/JavaxScriptRunner.java b/src/main/org/apache/tools/ant/util/optional/JavaxScriptRunner.java
index 761e5c8..a9b565c 100644
--- a/src/main/org/apache/tools/ant/util/optional/JavaxScriptRunner.java
+++ b/src/main/org/apache/tools/ant/util/optional/JavaxScriptRunner.java
@@ -18,6 +18,8 @@
 
 package org.apache.tools.ant.util.optional;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.function.BiConsumer;
@@ -34,6 +36,7 @@
 import org.apache.tools.ant.BuildException;
 import org.apache.tools.ant.MagicNames;
 import org.apache.tools.ant.Project;
+import org.apache.tools.ant.util.JavaEnvUtils;
 import org.apache.tools.ant.util.ScriptRunnerBase;
 
 /**
@@ -192,6 +195,14 @@
         }
         ScriptEngine result =
             new ScriptEngineManager().getEngineByName(getLanguage());
+        if (result == null && JavaEnvUtils.isAtLeastJavaVersion("15")
+            && languageIsJavaScript()) {
+            getProject()
+                .log("Java 15 has removed Nashorn, you must provide an engine "
+                     + "for running JavaScript yourself. "
+                     + "GraalVM JavaScript currently is the preferred option.",
+                     Project.MSG_WARN);
+        }
         maybeApplyGraalJsProperties(result);
         if (result != null && getKeepEngine()) {
             this.keptEngine = result;
@@ -208,6 +219,12 @@
         }
     }
 
+    private final static List<String> JS_LANGUAGES = Arrays.asList("js", "javascript");
+
+    private boolean languageIsJavaScript() {
+        return JS_LANGUAGES.contains(getLanguage());
+    }
+
     /**
      * Traverse a Throwable's cause(s) and return the BuildException
      * most deeply nested into it - if any.
diff --git a/src/tests/junit/org/apache/tools/ant/util/FileUtilsTest.java b/src/tests/junit/org/apache/tools/ant/util/FileUtilsTest.java
index da46520..d5448a6 100644
--- a/src/tests/junit/org/apache/tools/ant/util/FileUtilsTest.java
+++ b/src/tests/junit/org/apache/tools/ant/util/FileUtilsTest.java
@@ -50,6 +50,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
@@ -450,6 +452,68 @@
                 tmp2.getAbsolutePath()));
     }
 
+    @Test
+    public void createTempFileUsesAntTmpDirIfSetAndDeleteOnExitIsTrue() throws IOException {
+        final Project project = new Project();
+        final File projectTmpDir = folder.newFolder("subdir");
+        project.setProperty("ant.tmpdir", projectTmpDir.getAbsolutePath());
+        final File tmpFile = getFileUtils().createTempFile(project, null, null, null, true, true);
+        assertTrue(tmpFile + " must be child of " + projectTmpDir,
+                   tmpFile.getAbsolutePath().startsWith(projectTmpDir.getAbsolutePath()));
+    }
+
+    @Test
+    public void createTempFileUsesAntTmpDirIfSetAndDeleteOnExitIsFalse() throws IOException {
+        final Project project = new Project();
+        final File projectTmpDir = folder.newFolder("subdir");
+        project.setProperty("ant.tmpdir", projectTmpDir.getAbsolutePath());
+        final File tmpFile = getFileUtils().createTempFile(project, null, null, null, false, true);
+        assertTrue(tmpFile + " must be child of " + projectTmpDir,
+                   tmpFile.getAbsolutePath().startsWith(projectTmpDir.getAbsolutePath()));
+    }
+
+    @Test
+    public void createTempFileCreatesAutoTmpDirIfDeleteOnExitIsTrueOnUnix() throws IOException {
+        assumeFalse("Test doesn't run on DOS", Os.isFamily("dos"));
+        final Project project = new Project();
+        final File tmpFile = getFileUtils().createTempFile(project, null, null, null, true, true);
+        final String autoTempDir = project.getProperty("ant.auto.tmpdir");
+        assertNotNull(autoTempDir);
+        assertTrue(tmpFile + " must be child of " + autoTempDir,
+                   tmpFile.getAbsolutePath().startsWith(autoTempDir));
+    }
+
+    @Test
+    public void createTempFileDoesntCreateAutoTmpDirIfDeleteOnExitIsFalse() throws IOException {
+        final Project project = new Project();
+        final File tmpFile = getFileUtils().createTempFile(project, null, null, null, false, true);
+        assertNull(project.getProperty("ant.auto.tmpdir"));
+    }
+
+    @Test
+    public void createTempFileReusesAutoTmpDirIfDeleteOnExitIsTrueOnUnix() throws IOException {
+        assumeFalse("Test doesn't run on DOS", Os.isFamily("dos"));
+        final Project project = new Project();
+        final File tmpFile = getFileUtils().createTempFile(project, null, null, null, true, true);
+        final String autoTempDir = project.getProperty("ant.auto.tmpdir");
+        assertNotNull(autoTempDir);
+        final File tmpFile2 = getFileUtils().createTempFile(project, null, null, null, true, true);
+        assertTrue(tmpFile2 + " must be child of " + autoTempDir,
+                   tmpFile2.getAbsolutePath().startsWith(autoTempDir));
+    }
+
+    @Test
+    public void createTempFileDoesntReusesAutoTmpDirIfDeleteOnExitIsFalse() throws IOException {
+        assumeFalse("Test doesn't run on DOS", Os.isFamily("dos"));
+        final Project project = new Project();
+        final File tmpFile = getFileUtils().createTempFile(project, null, null, null, true, true);
+        final String autoTempDir = project.getProperty("ant.auto.tmpdir");
+        assertNotNull(autoTempDir);
+        final File tmpFile2 = getFileUtils().createTempFile(project, null, null, null, false, true);
+        assertFalse(tmpFile2 + " must not be child of " + autoTempDir,
+                    tmpFile2.getAbsolutePath().startsWith(autoTempDir));
+    }
+
     /**
      * Test contentEquals
      */