Repository: dita-ot/dita-ot
Issue: if parallel=true, some files are not rendered

## Expected Behavior
all files are rendered

## Actual Behavior
some files are skipped with error "folder could not be created". seemingly randomly in documents, but consistently between builds

## Steps to Reproduce
render a document with a lot of topics in the same subfolder (?)

## Copy of the error message, log file or stack trace
[xslt] Failed to transform document: Failed to transform document: Failed to create directory 

## Environment

* DITA-OT version: 3.7.1, 3.6.1
* Operating system and version:
  Linux, Windows
* How did you run DITA-OT?
  `dita` command
* Transformation type:
  HTML5


Changed files:
src/main/java/org/dita/dost/module/reader/AbstractReaderModule.java, src/main/java/org/dita/dost/ant/DITAOTCopy.java, src/main/java/org/dita/dost/util/XMLUtils.java, src/main/java/org/dita/dost/module/TopicMergeModule.java, src/main/java/org/dita/dost/store/StreamStore.java, src/main/java/org/dita/dost/module/filter/TopicBranchFilterModule.java

diff --git a/src/main/java/org/dita/dost/ant/DITAOTCopy.java b/src/main/java/org/dita/dost/ant/DITAOTCopy.java
index 391df9948..144498391 100644
--- a/src/main/java/org/dita/dost/ant/DITAOTCopy.java
+++ b/src/main/java/org/dita/dost/ant/DITAOTCopy.java
@@ -13,6 +13,8 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -89,9 +91,16 @@ public final class DITAOTCopy extends Task {
         if (destDir == null) {
             throw new BuildException("Destination directory not defined");
         }
-        if (!destDir.exists() && !destDir.mkdirs()) {
-            throw new BuildException(new IOException("Destination directory " + destDir + " cannot be created"));
+        if (!destDir.exists()) {
+            try {
+                Files.createDirectories(destDir.toPath());
+            } catch (FileAlreadyExistsException e) {
+                // Ignore
+            } catch (IOException e) {
+                throw new BuildException(e);
+            }
         }
+
         try {
             final FileUtils fileUtils = FileUtils.newFileUtils();
             final List<String> incs = getIncludes();
diff --git a/src/main/java/org/dita/dost/module/TopicMergeModule.java b/src/main/java/org/dita/dost/module/TopicMergeModule.java
index cc3e72089..bd814e547 100644
--- a/src/main/java/org/dita/dost/module/TopicMergeModule.java
+++ b/src/main/java/org/dita/dost/module/TopicMergeModule.java
@@ -22,6 +22,8 @@ import org.dita.dost.util.Job.FileInfo;
 import javax.xml.transform.stream.StreamSource;
 import java.io.*;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
 
 import static javax.xml.XMLConstants.XMLNS_ATTRIBUTE;
 import static org.dita.dost.util.Constants.*;
@@ -84,8 +86,14 @@ final class TopicMergeModule extends AbstractPipelineModuleImpl {
         }
 
         final File outputDir = out.getParentFile();
-        if (!outputDir.exists() && !outputDir.mkdirs()) {
-            logger.error("Failed to create directory " + outputDir.getAbsolutePath());
+        if (!outputDir.exists()) {
+            try {
+                Files.createDirectories(outputDir.toPath());
+            } catch (FileAlreadyExistsException e) {
+                // Ignore
+            } catch (IOException e) {
+                logger.error("Failed to create directory " + outputDir.getAbsolutePath());
+            }
         }
         try (final OutputStream output = new BufferedOutputStream(job.getStore().getOutputStream(out.toURI()))) {
             if (style != null) {
diff --git a/src/main/java/org/dita/dost/module/filter/TopicBranchFilterModule.java b/src/main/java/org/dita/dost/module/filter/TopicBranchFilterModule.java
index f8ea99b66..485b26714 100644
--- a/src/main/java/org/dita/dost/module/filter/TopicBranchFilterModule.java
+++ b/src/main/java/org/dita/dost/module/filter/TopicBranchFilterModule.java
@@ -21,6 +21,8 @@ import org.xml.sax.XMLFilter;
 import java.io.File;
 import java.io.IOException;
 import java.net.URI;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -128,8 +130,14 @@ public final class TopicBranchFilterModule extends AbstractBranchFilterModule {
                 final List<XMLFilter> pipe = singletonList(writer);
 
                 final File dstDirUri = new File(dstAbsUri.resolve("."));
-                if (!dstDirUri.exists() && !dstDirUri.mkdirs()) {
-                    logger.error("Failed to create directory " + dstDirUri);
+                if (!dstDirUri.exists()) {
+                    try {
+                        Files.createDirectories(dstDirUri.toPath());
+                    } catch (FileAlreadyExistsException e) {
+                        // Ignore
+                    } catch (IOException e) {
+                        logger.error("Failed to create directory " + dstDirUri);
+                    }
                 }
                 try {
                     job.getStore().transform(srcAbsUri, dstAbsUri, pipe);
diff --git a/src/main/java/org/dita/dost/module/reader/AbstractReaderModule.java b/src/main/java/org/dita/dost/module/reader/AbstractReaderModule.java
index f90100b7f..b55d8ca83 100644
--- a/src/main/java/org/dita/dost/module/reader/AbstractReaderModule.java
+++ b/src/main/java/org/dita/dost/module/reader/AbstractReaderModule.java
@@ -29,6 +29,8 @@ import org.xml.sax.helpers.DefaultHandler;
 import javax.xml.namespace.QName;
 import java.io.*;
 import java.net.URI;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
@@ -336,9 +338,15 @@ public abstract class AbstractReaderModule extends AbstractPipelineModuleImpl {
         final URI rel = tempFileNameScheme.generateTempFileName(currentFile);
         outputFile = new File(job.tempDirURI.resolve(rel));
         final File outputDir = outputFile.getParentFile();
-        if (!outputDir.exists() && !outputDir.mkdirs()) {
-            logger.error("Failed to create output directory " + outputDir.getAbsolutePath());
-            return;
+        if (!outputDir.exists()) {
+            try {
+                Files.createDirectories(outputDir.toPath());
+            } catch (FileAlreadyExistsException e) {
+                // Ignore
+            } catch (IOException e) {
+                logger.error("Failed to create output directory " + outputDir.getAbsolutePath());
+                return;
+            }
         }
         validateMap = Collections.emptyMap();
         defaultValueMap = Collections.emptyMap();
diff --git a/src/main/java/org/dita/dost/store/StreamStore.java b/src/main/java/org/dita/dost/store/StreamStore.java
index 2a36e1a7b..862f81ba3 100644
--- a/src/main/java/org/dita/dost/store/StreamStore.java
+++ b/src/main/java/org/dita/dost/store/StreamStore.java
@@ -28,6 +28,7 @@ import javax.xml.transform.TransformerException;
 import javax.xml.transform.stream.StreamSource;
 import java.io.*;
 import java.net.URI;
+import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.List;
@@ -164,11 +165,6 @@ public class StreamStore extends AbstractStore implements Store {
 
     @Override
     void transformURI(final URI input, final URI output, final List<XMLFilter> filters) throws DITAOTException {
-        final File outputFile = new File(output);
-        if (!outputFile.getParentFile().exists() && !outputFile.getParentFile().mkdirs()) {
-            throw new DITAOTException("Failed to create output directory " + outputFile.getParentFile().getAbsolutePath());
-        }
-
         try {
             XMLReader reader = xmlUtils.getXMLReader();
             for (final XMLFilter filter : filters) {
@@ -238,8 +234,12 @@ public class StreamStore extends AbstractStore implements Store {
     Serializer getSerializer(final URI dst) throws IOException {
         final File outputFile = new File(dst);
         final File dir = outputFile.getParentFile();
-        if (!dir.exists() && !dir.mkdirs()) {
-            throw new IOException("Failed to create directory " + dir.getAbsolutePath());
+        if (!dir.exists()) {
+            try {
+                Files.createDirectories(dir.toPath());
+            } catch (FileAlreadyExistsException e) {
+                // Ignore
+            }
         }
         return xmlUtils.getProcessor().newSerializer(outputFile);
     }
diff --git a/src/main/java/org/dita/dost/util/XMLUtils.java b/src/main/java/org/dita/dost/util/XMLUtils.java
index 58e6f800e..75062c71b 100644
--- a/src/main/java/org/dita/dost/util/XMLUtils.java
+++ b/src/main/java/org/dita/dost/util/XMLUtils.java
@@ -32,6 +32,8 @@ import javax.xml.transform.stream.StreamResult;
 import javax.xml.transform.stream.StreamSource;
 import java.io.*;
 import java.net.URI;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
 import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -650,8 +652,14 @@ public final class XMLUtils {
      */
     @Deprecated
     private void transformFile(final File inputFile, final File outputFile, final List<XMLFilter> filters) throws DITAOTException {
-        if (!outputFile.getParentFile().exists() && !outputFile.getParentFile().mkdirs()) {
-            throw new DITAOTException("Failed to create output directory " + outputFile.getParentFile().getAbsolutePath());
+        if (!outputFile.getParentFile().exists()) {
+            try {
+                Files.createDirectories(outputFile.getParentFile().toPath());
+            } catch (FileAlreadyExistsException e) {
+                // Ignore
+            } catch (IOException e) {
+                throw new DITAOTException(e);
+            }
         }
 
         try (final InputStream in = new BufferedInputStream(new FileInputStream(inputFile));
@@ -703,8 +711,14 @@ public final class XMLUtils {
     @Deprecated
     private void transformURI(final URI input, final URI output, final List<XMLFilter> filters) throws DITAOTException {
         final File outputFile = new File(output);
-        if (!outputFile.getParentFile().exists() && !outputFile.getParentFile().mkdirs()) {
-            throw new DITAOTException("Failed to create output directory " + outputFile.getParentFile().getAbsolutePath());
+        if (!outputFile.getParentFile().exists()) {
+            try {
+                Files.createDirectories(outputFile.getParentFile().toPath());
+            } catch (FileAlreadyExistsException e) {
+                // Ignore
+            } catch (IOException e) {
+                throw new DITAOTException(e);
+            }
         }
 
         try {