Merge
authorlana
Mon, 11 Aug 2014 10:05:15 -0700
changeset 26092 6a5d6de681af
parent 25873 024ed9c9ed13 (current diff)
parent 26090 9e2489a79aca (diff)
child 26093 8c9a1e6c4326
Merge
langtools/.hgtags
--- a/langtools/src/share/classes/com/sun/tools/sjavac/CompileJavaPackages.java	Sun Aug 10 19:39:06 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/sjavac/CompileJavaPackages.java	Mon Aug 11 10:05:15 2014 -0700
@@ -86,15 +86,9 @@
         boolean concurrentCompiles = true;
 
         // Fetch the id.
-        String idOpt = Util.extractStringOption("id", args.getServerConf());
-        if (idOpt == null || idOpt.equals("")) {
-            // No explicit id set. Create a random id so that the requests can be
-            // grouped properly in the server.
-            idOpt = "id"+(((new Random()).nextLong())&Long.MAX_VALUE);
-        }
-        final String id = idOpt;
+        final String id = Util.extractStringOption("id", javacService.serverSettings());
         // Only keep portfile and sjavac settings..
-        String psServerSettings = Util.cleanSubOptions(Util.set("portfile","sjavac","background","keepalive"), args.getServerConf());
+        String psServerSettings = Util.cleanSubOptions(Util.set("portfile","sjavac","background","keepalive"), javacService.serverSettings());
 
         // Get maximum heap size from the server!
         SysInfo sysinfo = javacService.getSysInfo();
--- a/langtools/src/share/classes/com/sun/tools/sjavac/JavacState.java	Sun Aug 10 19:39:06 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/sjavac/JavacState.java	Mon Aug 11 10:05:15 2014 -0700
@@ -60,7 +60,6 @@
     int numCores;
 
     // The bin_dir/javac_state
-    private String javacStateFilename;
     private File javacState;
 
     // The previous build state is loaded from javac_state
@@ -99,7 +98,7 @@
     private Set<String> recompiledPackages;
 
     // The output directories filled with tasty artifacts.
-    private File binDir, gensrcDir, headerDir;
+    private File binDir, gensrcDir, headerDir, stateDir;
 
     // The current status of the file system.
     private Set<File> binArtifacts;
@@ -128,7 +127,11 @@
     // Where to send stdout and stderr.
     private PrintStream out, err;
 
-    JavacState(Options options, boolean removeJavacState, PrintStream o, PrintStream e) {
+    // Command line options.
+    private Options options;
+
+    JavacState(Options op, boolean removeJavacState, PrintStream o, PrintStream e) {
+        options = op;
         out = o;
         err = e;
         numCores = options.getNumCores();
@@ -136,8 +139,8 @@
         binDir = Util.pathToFile(options.getDestDir());
         gensrcDir = Util.pathToFile(options.getGenSrcDir());
         headerDir = Util.pathToFile(options.getHeaderDir());
-        javacStateFilename = binDir.getPath()+File.separator+"javac_state";
-        javacState = new File(javacStateFilename);
+        stateDir = Util.pathToFile(options.getStateDir());
+        javacState = new File(stateDir, "javac_state");
         if (removeJavacState && javacState.exists()) {
             javacState.delete();
         }
@@ -148,7 +151,7 @@
             // We do not want to risk building a broken incremental build.
             // BUT since the makefiles still copy things straight into the bin_dir et al,
             // we avoid deleting files here, if the option --permit-unidentified-classes was supplied.
-            if (!options.isUnidentifiedArtifactPermitted()) {
+            if (!options.areUnidentifiedArtifactsPermitted()) {
                 deleteContents(binDir);
                 deleteContents(gensrcDir);
                 deleteContents(headerDir);
@@ -268,7 +271,7 @@
      */
     public void save() throws IOException {
         if (!needsSaving) return;
-        try (FileWriter out = new FileWriter(javacStateFilename)) {
+        try (FileWriter out = new FileWriter(javacState)) {
             StringBuilder b = new StringBuilder();
             long millisNow = System.currentTimeMillis();
             Date d = new Date(millisNow);
@@ -311,7 +314,7 @@
         boolean newCommandLine = false;
         boolean syntaxError = false;
 
-        try (BufferedReader in = new BufferedReader(new FileReader(db.javacStateFilename))) {
+        try (BufferedReader in = new BufferedReader(new FileReader(db.javacState))) {
             for (;;) {
                 String l = in.readLine();
                 if (l==null) break;
@@ -512,7 +515,8 @@
         allKnownArtifacts.add(javacState);
 
         for (File f : binArtifacts) {
-            if (!allKnownArtifacts.contains(f)) {
+            if (!allKnownArtifacts.contains(f) &&
+                !options.isUnidentifiedArtifactPermitted(f.getAbsolutePath())) {
                 Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state.");
                 f.delete();
             }
@@ -605,13 +609,16 @@
     /**
      * Recursively delete a directory and all its contents.
      */
-    private static void deleteContents(File dir) {
+    private void deleteContents(File dir) {
         if (dir != null && dir.exists()) {
             for (File f : dir.listFiles()) {
                 if (f.isDirectory()) {
                     deleteContents(f);
                 }
-                f.delete();
+                if (!options.isUnidentifiedArtifactPermitted(f.getAbsolutePath())) {
+                    Log.debug("Removing "+f.getAbsolutePath());
+                    f.delete();
+                }
             }
         }
     }
--- a/langtools/src/share/classes/com/sun/tools/sjavac/Main.java	Sun Aug 10 19:39:06 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/sjavac/Main.java	Mon Aug 11 10:05:15 2014 -0700
@@ -205,6 +205,9 @@
         if (!createIfMissing(options.getDestDir()))
             return -1;
 
+        if (!createIfMissing(options.getStateDir()))
+            return -1;
+
         Path gensrc = options.getGenSrcDir();
         if (gensrc != null && !createIfMissing(gensrc))
             return -1;
@@ -302,7 +305,7 @@
         // For examples, files that have been manually copied into these dirs.
         // Artifacts with bad timestamps (ie the on disk timestamp does not match the timestamp
         // in javac_state) have already been removed when the javac_state was loaded.
-        if (!options.isUnidentifiedArtifactPermitted()) {
+        if (!options.areUnidentifiedArtifactsPermitted()) {
             javac_state.removeUnidentifiedArtifacts();
         }
         // Go through all sources and taint all packages that miss artifacts.
@@ -345,7 +348,7 @@
                 // Currently sjavac always connects to a server through a socket
                 // regardless if sjavac runs as a background service or not.
                 // This will most likely change in the future.
-                JavacService javacService = new JavacServiceClient(options.getServerConf());
+                JavacService javacService = new JavacServiceClient(options);
                 again = javac_state.performJavaCompilations(javacService, options, recently_compiled, rc);
                 if (!rc[0]) break;
             } while (again);
@@ -375,8 +378,6 @@
             err = "Please specify output directory.";
         } else if (options.isJavaFilesAmongJavacArgs()) {
             err = "Sjavac does not handle explicit compilation of single .java files.";
-        } else if (options.isAtFilePresent()) {
-            err = "Sjavac does not handle @-files.";
         } else if (options.getServerConf() == null) {
             err = "No server configuration provided.";
         } else if (!options.getImplicitPolicy().equals("none")) {
--- a/langtools/src/share/classes/com/sun/tools/sjavac/comp/JavacServiceImpl.java	Sun Aug 10 19:39:06 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/sjavac/comp/JavacServiceImpl.java	Mon Aug 11 10:05:15 2014 -0700
@@ -143,4 +143,10 @@
 
         return compilationResult;
     }
+
+    @Override
+    public String serverSettings() {
+        return "";
+    }
+
 }
--- a/langtools/src/share/classes/com/sun/tools/sjavac/options/Option.java	Sun Aug 10 19:39:06 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/sjavac/options/Option.java	Mon Aug 11 10:05:15 2014 -0700
@@ -231,7 +231,14 @@
             helper.logLevel("info");
         }
     },
-    PERMIT_UNIDENTIFIED_ARTIFACTS("--permit-unidentified-artifacts", "Keep unidentified artifacts in destination directory") {
+    PERMIT_ARTIFACT("--permit-artifact=", "Allow this artifact in destination directory") {
+        @Override
+        protected void processMatching(ArgumentIterator iter, OptionHelper helper) {
+            String a = iter.current().substring(arg.length());
+            helper.permitArtifact(Paths.get(a).toFile().getAbsolutePath());
+        }
+    },
+    PERMIT_UNIDENTIFIED_ARTIFACTS("--permit-unidentified-artifacts", "Allow unidentified artifacts in destination directory") {
         @Override
         protected void processMatching(ArgumentIterator iter, OptionHelper helper) {
             helper.permitUnidentifiedArtifacts();
@@ -274,8 +281,16 @@
             if (dir != null)
                 helper.headerDir(dir);
         }
+    },
+    STATE_DIR("--state-dir=", "Directory used to store sjavac state and log files.") {
+        @Override
+        protected void processMatching(ArgumentIterator iter, OptionHelper helper) {
+            String p = iter.current().substring(arg.length());
+            helper.stateDir(Paths.get(p));
+        }
     };
 
+
     public final String arg;
 
     final String description;
--- a/langtools/src/share/classes/com/sun/tools/sjavac/options/OptionHelper.java	Sun Aug 10 19:39:06 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/sjavac/options/OptionHelper.java	Mon Aug 11 10:05:15 2014 -0700
@@ -25,12 +25,14 @@
 
 package com.sun.tools.sjavac.options;
 
+import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.List;
 
+import com.sun.tools.javac.main.CommandLine;
 import com.sun.tools.sjavac.Transformer;
 
 /**
@@ -78,6 +80,9 @@
     /** Record path for reference source list */
     public abstract void compareFoundSources(Path referenceList);
 
+    /** Record a single permitted artifact */
+    public abstract void permitArtifact(String f);
+
     /** Record the fact that unidentified artifacts are permitted */
     public abstract void permitUnidentifiedArtifacts();
 
@@ -102,6 +107,9 @@
     /** Sets the directory for generated headers */
     public abstract void headerDir(Path dir);
 
+    /** Sets the directory for state and log files generated by sjavac */
+    public abstract void stateDir(Path dir);
+
     /** Sets the implicit policy */
     public abstract void implicit(String policy);
 
@@ -112,7 +120,11 @@
      * @param args the arguments to traverse.
      */
     void traverse(String[] args) {
-
+        try {
+            args = CommandLine.parse(args); // Detect @file and load it as a command line.
+        } catch (java.io.IOException e) {
+            throw new IllegalArgumentException("Problem reading @"+e.getMessage());
+        }
         ArgumentIterator argIter = new ArgumentIterator(Arrays.asList(args));
 
         nextArg:
--- a/langtools/src/share/classes/com/sun/tools/sjavac/options/Options.java	Sun Aug 10 19:39:06 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/sjavac/options/Options.java	Mon Aug 11 10:05:15 2014 -0700
@@ -32,6 +32,8 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.HashSet;
 
 import com.sun.tools.sjavac.Transformer;
 
@@ -41,7 +43,7 @@
 public class Options {
 
     // Output directories
-    private Path destDir, genSrcDir, headerDir;
+    private Path destDir, genSrcDir, headerDir, stateDir;
 
     // Input directories
     private List<SourceLocation> sources = new ArrayList<>();
@@ -51,7 +53,8 @@
 
     private String logLevel = "info";
 
-    private boolean permitUnidentifiedArtifact = false;
+    private Set<String> permitted_artifacts = new HashSet<>();
+    private boolean permitUnidentifiedArtifacts = false;
     private boolean permitSourcesInDefaultPackage = false;
 
     private Path sourceReferenceList;
@@ -86,6 +89,11 @@
         return headerDir;
     }
 
+    /** Get the path for the state directory, defaults to destDir. */
+    public Path getStateDir() {
+        return stateDir != null ? stateDir : destDir;
+    }
+
     /** Get all source locations for files to be compiled */
     public List<SourceLocation> getSources() {
         return sources;
@@ -114,10 +122,15 @@
         return logLevel;
     }
 
+    /** Returns true iff the artifact is permitted in the output dir. */
+    public boolean isUnidentifiedArtifactPermitted(String f) {
+        return permitted_artifacts.contains(f);
+    }
+
     /** Returns true iff artifacts in the output directories should be kept,
      * even if they would not be generated in a clean build. */
-    public boolean isUnidentifiedArtifactPermitted() {
-        return permitUnidentifiedArtifact;
+    public boolean areUnidentifiedArtifactsPermitted() {
+        return permitUnidentifiedArtifacts;
     }
 
     /** Returns true iff sources in the default package should be permitted. */
@@ -176,14 +189,6 @@
         return false;
     }
 
-    /** Returns true iff an @-file is among the javac arguments */
-    public boolean isAtFilePresent() {
-        for (String javacArg : javacArgs)
-            if (javacArg.startsWith("@"))
-                return true;
-        return false;
-    }
-
     /**
      * Returns a string representation of the options that affect the result of
      * the compilation. (Used for saving the state of the options used in a
@@ -239,6 +244,9 @@
         if (destDir != null)
             args.addArg(Option.D, destDir.normalize());
 
+        if (stateDir != null)
+            args.addArg(Option.STATE_DIR, stateDir.normalize());
+
         // Source roots
         args.addSourceLocations(Option.SRC, sources);
         args.addSourceLocations(Option.SOURCEPATH, sourceSearchPaths);
@@ -249,7 +257,11 @@
         if (permitSourcesInDefaultPackage)
             args.addArg(Option.PERMIT_SOURCES_WITHOUT_PACKAGE);
 
-        if (permitUnidentifiedArtifact)
+        for (String f : permitted_artifacts) {
+            args.addArg(Option.PERMIT_ARTIFACT, f);
+        }
+
+        if (permitUnidentifiedArtifacts)
             args.addArg(Option.PERMIT_UNIDENTIFIED_ARTIFACTS);
 
         // Translation rules
@@ -327,6 +339,7 @@
 
         boolean headerProvided = false;
         boolean genSrcProvided = false;
+        boolean stateProvided = false;
 
         @Override
         public void reportError(String msg) {
@@ -399,8 +412,13 @@
         }
 
         @Override
+        public void permitArtifact(String f) {
+            permitted_artifacts.add(f);
+        }
+
+        @Override
         public void permitUnidentifiedArtifacts() {
-            permitUnidentifiedArtifact = true;
+            permitUnidentifiedArtifacts = true;
         }
 
         @Override
@@ -465,6 +483,16 @@
             headerDir = dir.toAbsolutePath();
         }
 
+        @Override
+        public void stateDir(Path dir) {
+            if (stateProvided) {
+                reportError("State directory already specified.");
+                return;
+            }
+            stateProvided = true;
+            stateDir = dir.toAbsolutePath();
+        }
+
         private List<SourceLocation> createSourceLocations(List<Path> paths) {
             List<SourceLocation> result = new ArrayList<>();
             for (Path path : paths) {
--- a/langtools/src/share/classes/com/sun/tools/sjavac/server/JavacService.java	Sun Aug 10 19:39:06 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/sjavac/server/JavacService.java	Mon Aug 11 10:05:15 2014 -0700
@@ -40,4 +40,5 @@
                               List<File> explicitSources,
                               Set<URI> sourcesToCompile,
                               Set<URI> visibleSources);
+    String serverSettings();
 }
--- a/langtools/src/share/classes/com/sun/tools/sjavac/server/JavacServiceClient.java	Sun Aug 10 19:39:06 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/sjavac/server/JavacServiceClient.java	Mon Aug 11 10:05:15 2014 -0700
@@ -46,6 +46,7 @@
 import java.util.Set;
 
 import com.sun.tools.sjavac.Util;
+import com.sun.tools.sjavac.options.Options;
 
 import static com.sun.tools.sjavac.server.CompilationResult.ERROR_BUT_TRY_AGAIN;
 import static com.sun.tools.sjavac.server.CompilationResult.ERROR_FATAL;
@@ -72,19 +73,34 @@
     // for example by setting: sjavac=java%20-jar%20...javac.jar%com.sun.tools.sjavac.Main
     private final String sjavac;
 
-    public JavacServiceClient(String settings) {
-        id = Util.extractStringOption("id", settings);
-        portfile = Util.extractStringOption("portfile", settings);
-        logfile = Util.extractStringOption("logfile", settings, portfile + ".javaclog");
-        stdouterrfile = Util.extractStringOption("stdouterrfile", settings, portfile + ".stdouterr");
-        background = Util.extractBooleanOption("background", settings, true);
-        sjavac = Util.extractStringOption("sjavac", settings, "sjavac");
-        int poolsize = Util.extractIntOption("poolsize", settings);
-        keepalive = Util.extractIntOption("keepalive", settings, 120);
+    // Store the server conf settings here.
+    private final String settings;
+
+    public JavacServiceClient(Options options) {
+        String tmpServerConf = options.getServerConf();
+        String serverConf = (tmpServerConf!=null)? tmpServerConf : "";
+        String tmpId = Util.extractStringOption("id", serverConf);
+        id = (tmpId!=null) ? tmpId : "id"+(((new java.util.Random()).nextLong())&Long.MAX_VALUE);
+        String p = Util.extractStringOption("portfile", serverConf);
+        portfile = (p!=null) ? p : options.getStateDir().toFile().getAbsolutePath()+File.separatorChar+"javac_server";
+        logfile = Util.extractStringOption("logfile", serverConf, portfile + ".javaclog");
+        stdouterrfile = Util.extractStringOption("stdouterrfile", serverConf, portfile + ".stdouterr");
+        background = Util.extractBooleanOption("background", serverConf, true);
+        sjavac = Util.extractStringOption("sjavac", serverConf, "sjavac");
+        int poolsize = Util.extractIntOption("poolsize", serverConf);
+        keepalive = Util.extractIntOption("keepalive", serverConf, 120);
 
         this.poolsize = poolsize > 0 ? poolsize : Runtime.getRuntime().availableProcessors();
+        settings = (serverConf.equals("")) ? "id="+id+",portfile="+portfile : serverConf;
     }
 
+    /**
+     * Hand out the server settings.
+     * @return The server settings, possibly a default value.
+     */
+    public String serverSettings() {
+        return settings;
+    }
 
     /**
      * Make a request to the server only to get the maximum possible heap size to use for compilations.
--- a/langtools/test/tools/sjavac/OptionDecoding.java	Sun Aug 10 19:39:06 2014 -0700
+++ b/langtools/test/tools/sjavac/OptionDecoding.java	Mon Aug 11 10:05:15 2014 -0700
@@ -25,7 +25,7 @@
 
 /*
  * @test
- * @bug 8035063
+ * @bug 8035063 8054465
  * @summary Tests decoding of String[] into Options.
  *
  * @build Wrapper
@@ -192,13 +192,16 @@
         assertEquals(17, options.getNumCores());
         assertEquals("debug", options.getLogLevel());
         assertEquals(false, options.isDefaultPackagePermitted());
-        assertEquals(false, options.isUnidentifiedArtifactPermitted());
+        assertEquals(false, options.areUnidentifiedArtifactsPermitted());
+        assertEquals(false, options.isUnidentifiedArtifactPermitted(Paths.get("bar.txt").toFile().getAbsolutePath()));
 
         options = Options.parseArgs("--permit-unidentified-artifacts",
+                                    "--permit-artifact=bar.txt",
                                     "--permit-sources-without-package");
         assertEquals("info", options.getLogLevel());
         assertEquals(true, options.isDefaultPackagePermitted());
-        assertEquals(true, options.isUnidentifiedArtifactPermitted());
+        assertEquals(true, options.areUnidentifiedArtifactsPermitted());
+        assertEquals(true, options.isUnidentifiedArtifactPermitted(Paths.get("bar.txt").toFile().getAbsolutePath()));
     }
 
     // Test server configuration options
--- a/langtools/test/tools/sjavac/SJavac.java	Sun Aug 10 19:39:06 2014 -0700
+++ b/langtools/test/tools/sjavac/SJavac.java	Mon Aug 11 10:05:15 2014 -0700
@@ -25,7 +25,7 @@
 /*
  * @test
  * @summary Test all aspects of sjavac.
- * @bug 8004658 8042441 8042699
+ * @bug 8004658 8042441 8042699 8054461 8054474 8054465
  *
  * @build Wrapper
  * @run main Wrapper SJavac
@@ -99,6 +99,9 @@
         compileCircularSources();
         compileExcludingDependency();
         incrementalCompileTestFullyQualifiedRef();
+        compileWithAtFile();
+        testStateDir();
+        testPermittedArtifact();
 
         delete(gensrc);
         delete(gensrc2);
@@ -463,6 +466,98 @@
                          "bin/javac_state");
     }
 
+   /**
+     * Tests @atfile
+     * @throws Exception If test fails
+     */
+    void compileWithAtFile() throws Exception {
+        System.out.println("\nTest @atfile with command line content.");
+        System.out.println("---------------------------------------");
+
+        delete(gensrc);
+        delete(gensrc2);
+        delete(bin);
+
+        populate(gensrc,
+                 "list.txt",
+                 "-if */alfa/omega/A.java\n-if */beta/B.java\ngensrc\n-d bin\n",
+                 "alfa/omega/A.java",
+                 "package alfa.omega; import beta.B; public class A { B b; }",
+                 "beta/B.java",
+                 "package beta; public class B { }",
+                 "beta/C.java",
+                 "broken");
+        previous_bin_state = collectState(bin);
+        compile("@gensrc/list.txt", "--server:portfile=testserver,background=false");
+
+        Map<String,Long> new_bin_state = collectState(bin);
+        verifyThatFilesHaveBeenAdded(previous_bin_state, new_bin_state,
+                         "bin/javac_state",
+                         "bin/alfa/omega/A.class",
+                         "bin/beta/B.class");
+    }
+
+    /**
+     * Tests storing javac_state into another directory.
+     * @throws Exception If test fails
+     */
+    void testStateDir() throws Exception {
+        System.out.println("\nVerify that --state-dir=bar works.");
+        System.out.println("----------------------------------");
+
+        Path bar = defaultfs.getPath("bar");
+        Files.createDirectory(bar);
+
+        delete(gensrc);
+        delete(bin);
+        delete(bar);
+        previous_bin_state = collectState(bin);
+        Map<String,Long> previous_bar_state = collectState(bar);
+
+        populate(gensrc,
+                 "alfa/omega/A.java",
+                 "package alfa.omega; public class A { }");
+
+        compile("--state-dir=bar", "-src", "gensrc", "-d", "bin", serverArg);
+
+        Map<String,Long> new_bin_state = collectState(bin);
+        verifyThatFilesHaveBeenAdded(previous_bin_state, new_bin_state,
+                                     "bin/alfa/omega/A.class");
+        Map<String,Long> new_bar_state = collectState(bar);
+        verifyThatFilesHaveBeenAdded(previous_bar_state, new_bar_state,
+                                     "bar/javac_state");
+    }
+
+    /**
+     * Test white listing of external artifacts inside the destination dir.
+     * @throws Exception If test fails
+     */
+    void testPermittedArtifact() throws Exception {
+        System.out.println("\nVerify that --permit-artifact=bar works.");
+        System.out.println("-------------------------------------------");
+
+        delete(gensrc);
+        delete(bin);
+
+        previous_bin_state = collectState(bin);
+
+        populate(gensrc,
+                 "alfa/omega/A.java",
+                 "package alfa.omega; public class A { }");
+
+        populate(bin,
+                 "alfa/omega/AA.class",
+                 "Ugh, a messy build system (tobefixed) wrote this class file, sjavac must not delete it.");
+
+        compile("--log=debug", "--permit-artifact=bin/alfa/omega/AA.class", "-src", "gensrc", "-d", "bin", serverArg);
+
+        Map<String,Long> new_bin_state = collectState(bin);
+        verifyThatFilesHaveBeenAdded(previous_bin_state, new_bin_state,
+                                     "bin/alfa/omega/A.class",
+                                     "bin/alfa/omega/AA.class",
+                                     "bin/javac_state");
+    }
+
     void removeFrom(Path dir, String... args) throws IOException {
         for (String filename : args) {
             Path p = dir.resolve(filename);