Repository: asciidoctor/asciidoctor-maven-plugin (java)

Issue:
Unexpected warnings "Duplicated destination found: overwriting file: ..."
Thank you for taking your time to talk with us!

**What is this issue about?**

- [ ] Bug report
- [ ] Feature request
- [x] Question (or bug report, not sure yet)

**Description**

My POM looks like this:

```xml
<pluginManagement>
  <plugins>
    <plugin>
      <groupId>org.asciidoctor</groupId>
      <artifactId>asciidoctor-maven-plugin</artifactId>
      <version>2.2.4</version>
      <configuration>
        <attributes>
          <source-highlighter>coderay</source-highlighter>
          <toc>left</toc>
          <icons>font</icons>
        </attributes>
      </configuration>
      <!-- TODO: Remove dependency after https://github.com/asciidoctor/asciidoctor-maven-plugin/issues/553 fix -->
      <dependencies>
        <dependency>
          <groupId>org.jruby</groupId>
          <artifactId>jruby</artifactId>
          <version>9.4.5.0</version>
        </dependency>
      </dependencies>
    </plugin>
  </plugins>
</pluginManagement>

<plugins>
  <plugin>
    <groupId>org.asciidoctor</groupId>
    <artifactId>asciidoctor-maven-plugin</artifactId>
    <executions>
      <execution>
        <id>create-asciidoc</id>
        <phase>generate-resources</phase>
        <goals>
          <goal>process-asciidoc</goal>
        </goals>
        <configuration>
          <sourceDirectory>${project.basedir}</sourceDirectory>
          <outputDirectory>${project.build.directory}/html</outputDirectory>
          <preserveDirectories>true</preserveDirectories>
          <relativeBaseDir>true</relativeBaseDir>
          <resources>
            <resource>
              <!-- Mandatory, even though identical to 'sourceDirectory' -->
              <directory>${project.basedir}</directory>
              <excludes>
                <!-- Generally excluded file extensions -->
                <exclude>**/*.doc</exclude>
                <exclude>**/*.vsd</exclude>
                <!-- Files excluded in root directory -->
                <exclude>*.iml</exclude>
                <exclude>pom.xml*</exclude>
                <exclude>build.xml</exclude>
                <exclude>docs.build.properties</exclude>
                <!-- Files excluded in subdirectories -->
                <exclude>developer/*.txt</exclude>
                <exclude>developer/ajdt/**</exclude>
                <exclude>developer/traces/**</exclude>
                <exclude>install/**</exclude>
                <exclude>sandbox/**</exclude>
                <exclude>src/**</exclude>
                <exclude>teaching/**</exclude>
                <exclude>test/**</exclude>
              </excludes>
            </resource>
          </resources>
        </configuration>
      </execution>
    </executions>
  </plugin>
</plugins>
```

There are a few ADOC file in the module root and a few dozen more in several subdirectories. Per subdirectory, there usually is one main ADOC creating a TOC and simply linking to subpages via `xref:` and another variant of the main ADOC using `include::`directives. As far as I can see, the output HTML files are created correctly. Anyway, I see several log messages like this:

```text
...
[INFO] Converted C:\Users\alexa\Documents\java-src\AspectJ\docs\devguide\devguide.adoc
[WARNING] Duplicated destination found: overwriting file: C:\Users\alexa\Documents\java-src\AspectJ\docs\index.adoc
[INFO] Converted C:\Users\alexa\Documents\java-src\AspectJ\docs\devguide\index.adoc
[WARNING] Duplicated destination found: overwriting file: C:\Users\alexa\Documents\java-src\AspectJ\docs\ltw.adoc
[INFO] Converted C:\Users\alexa\Documents\java-src\AspectJ\docs\devguide\ltw.adoc
[INFO] Converted C:\Users\alexa\Documents\java-src\AspectJ\docs\devguide\tools-intro.adoc
[INFO] Converted C:\Users\alexa\Documents\java-src\AspectJ\docs\examples\spacewar\README.adoc
[INFO] Converted C:\Users\alexa\Documents\java-src\AspectJ\docs\faq\faq.adoc
[WARNING] Duplicated destination found: overwriting file: C:\Users\alexa\Documents\java-src\AspectJ\docs\index.adoc
[INFO] Converted C:\Users\alexa\Documents\java-src\AspectJ\docs\index.adoc
[INFO] Converted C:\Users\alexa\Documents\java-src\AspectJ\docs\LICENSE-AspectJ.adoc
[INFO] Converted C:\Users\alexa\Documents\java-src\AspectJ\docs\pdguide\ajcore.adoc
[WARNING] Duplicated destination found: overwriting file: C:\Users\alexa\Documents\java-src\AspectJ\docs\index.adoc
...
```

This is irritating for several reasons:
  * The paths in the warnings are from the source directory, not the target directory.
  * The paths are mostly fantasy paths, i.e. while e.g. _ltw.adoc_ exists in several subdirectories, it certainly does not exist directly in the source base path _C:\Users\alexa\Documents\java-src\AspectJ\docs´_. Only an _index.adoc_ is there (and also in some subdirectories).
  * If anything would be overwritten - which does not seem to be the case - it should be under _C:\Users\alexa\Documents\java-src\AspectJ\docs\target\html_, as specified in the config.

IMO, the warnings are just false. Even if they were not, the printed fantasy paths do not make any sense. I strongly hope that the plugin does not write any intermediary output into the source folder.

**Environment information**

* asciidoctor-maven-plugin version: 2.2.4
* asciidoctorj version: ?

* Maven, Java and OS version: Maven 3.6.3, 3.9.5, Java 21, Windows 10 Pro


Pull request:
(v2.2.x) Fix empty destination path calculation for validation
**What kind of change does this PR introduce?** (check at least one)
<!-- Update "[ ]" to "[x]" to check a box -->

- [x] Bugfix
- [ ] Feature
- [ ] Documentation
- [ ] Refactor
- [ ] Build improvement
- [ ] Other (please describe)


**What is the goal of this pull request?**

Fix #728 
Also improve message. We cannot get the exact name without duplication asciidoctor core logic, but now when outputFile is not set simply wildcard will be displayed instead of the source extension to avoid confusion:
```
[warn] Duplicated destination found: overwriting file: /.../path/single-output.*
```

**Are there any alternative ways to implement this?**

We could only check for `toDir` (as done in `main` branch) but that would break builds using older AsciidoctorJ versions.

**Are there any implications of this pull request? Anything a user must know?**

no

**Is it related to an existing issue?**
<!-- If Yes, please add a line of the form: `Fixes #Issue` --> 
- [x] Yes
- [ ] No

*Finally, please add a corresponding entry to CHANGELOG.adoc*


Patch:
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 2bc3420..c44774d 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -16,6 +16,7 @@ For a detailed view of what has changed, refer to the {uri-repo}/commits/main[co
 Bug Fixes::
 
   * Remove Java 'requires open access' module warning in modern Java versions with JRuby v9.4.5.0 (#553)
+  * Check for both destinationDir and toDir when to avoid invalid "Duplicated destination found" messages, and improve warning message (#728)
 
 Build / Infrastructure::
 
diff --git a/src/main/java/org/asciidoctor/maven/AsciidoctorMojo.java b/src/main/java/org/asciidoctor/maven/AsciidoctorMojo.java
index 95782c7..1703715 100644
--- a/src/main/java/org/asciidoctor/maven/AsciidoctorMojo.java
+++ b/src/main/java/org/asciidoctor/maven/AsciidoctorMojo.java
@@ -1,5 +1,6 @@
 package org.asciidoctor.maven;
 
+import org.apache.commons.io.FilenameUtils;
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.model.Resource;
 import org.apache.maven.plugin.AbstractMojo;
@@ -243,9 +244,16 @@ public class AsciidoctorMojo extends AbstractMojo {
 
         final Set<File> uniquePaths = new HashSet<>();
         for (final File source : sourceFiles) {
-            final File destinationPath = setDestinationPaths(source, optionsBuilder, sourceDir, this);
-            if (!uniquePaths.add(destinationPath))
-                getLog().warn("Duplicated destination found: overwriting file: " + destinationPath.getAbsolutePath());
+            final Destination destination = setDestinationPaths(source, optionsBuilder, sourceDir, this);
+            final File destinationPath = destination.path;
+            if (!uniquePaths.add(destinationPath)) {
+                String destinationFile = destinationPath.getAbsolutePath();
+                if (!destination.isOutput) {
+                    String baseName = FilenameUtils.getBaseName(destinationPath.getName());
+                    destinationFile = destinationPath.getParentFile().getAbsolutePath() + File.separator + baseName + ".*";
+                }
+                getLog().warn("Duplicated destination found: overwriting file: " + destinationFile);
+            }
 
             convertFile(asciidoctor, optionsBuilder.asMap(), source);
 
@@ -357,8 +365,8 @@ public class AsciidoctorMojo extends AbstractMojo {
      * @return the final destination file path.
      * @throws MojoExecutionException If output is not valid
      */
-    public File setDestinationPaths(final File sourceFile, final OptionsBuilder optionsBuilder, final File sourceDirectory,
-                                    final AsciidoctorMojo configuration) throws MojoExecutionException {
+    public Destination setDestinationPaths(final File sourceFile, final OptionsBuilder optionsBuilder, final File sourceDirectory,
+                                           final AsciidoctorMojo configuration) throws MojoExecutionException {
         try {
             if (configuration.getBaseDir() != null) {
                 optionsBuilder.baseDir(configuration.getBaseDir());
@@ -379,19 +387,37 @@ public class AsciidoctorMojo extends AbstractMojo {
                 optionsBuilder.toDir(outputDir).destinationDir(outputDir);
             }
             final File outputFile = configuration.getOutputFile();
-            final String destinationDir = (String) optionsBuilder.asMap().get(Options.DESTINATION_DIR);
+            final String destinationDir = getDestinationDir(optionsBuilder);
             if (outputFile != null) {
-                // allow overriding the output file name
                 optionsBuilder.toFile(outputFile);
-                return outputFile.isAbsolute() ? outputFile : new File(destinationDir, outputFile.getPath());
+                return outputFile.isAbsolute()
+                        ? new Destination(outputFile, true)
+                        : new Destination(new File(destinationDir, outputFile.getPath()), true);
             } else {
-                return new File(destinationDir, sourceFile.getName());
+                return new Destination(new File(destinationDir, sourceFile.getName()), false);
             }
         } catch (IOException e) {
             throw new MojoExecutionException("Unable to locate output directory", e);
         }
     }
 
+    class Destination {
+        final File path;
+        // Whether path is the actual output file or an approximation
+        final boolean isOutput;
+
+        Destination(File destination, boolean isSource) {
+            this.path = destination;
+            this.isOutput = isSource;
+        }
+    }
+
+    private static String getDestinationDir(OptionsBuilder optionsBuilder) {
+        final Map<String, Object> options = optionsBuilder.asMap();
+        return (String) Optional.ofNullable(options.get(Options.DESTINATION_DIR))
+                .orElse(options.get(Options.TO_DIR));
+    }
+
     protected Asciidoctor getAsciidoctorInstance(String gemPath) throws MojoExecutionException {
         Asciidoctor asciidoctor = null;
         if (gemPath == null) {


Test patch:
diff --git a/src/test/java/org/asciidoctor/maven/AsciidoctorMojoTest.java b/src/test/java/org/asciidoctor/maven/AsciidoctorMojoTest.java
index d6342af..c2dab28 100644
--- a/src/test/java/org/asciidoctor/maven/AsciidoctorMojoTest.java
+++ b/src/test/java/org/asciidoctor/maven/AsciidoctorMojoTest.java
@@ -5,12 +5,18 @@ import org.apache.commons.io.FileUtils;
 import org.apache.maven.model.Resource;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
+import org.asciidoctor.Options;
+import org.asciidoctor.OptionsBuilder;
+import org.asciidoctor.maven.AsciidoctorMojo.Destination;
 import org.asciidoctor.maven.extensions.ExtensionConfiguration;
 import org.asciidoctor.maven.io.AsciidoctorFileScanner;
 import org.asciidoctor.maven.io.ConsoleHolder;
 import org.asciidoctor.maven.test.processors.RequireCheckerTreeprocessor;
 import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
 
 import java.io.*;
 import java.nio.file.Files;
@@ -1069,4 +1075,23 @@ public class AsciidoctorMojoTest {
         consoleHolder.release();
     }
 
+    @ParameterizedTest
+    @NullSource
+    @ValueSource(strings = {"output.html"})
+    public void should_return_absolute_path_when_calculating_destination(String outputFile) throws MojoExecutionException {
+        // given
+        final AsciidoctorMojo mojo = mockAsciidoctorMojo();
+        mojo.setOutputDirectory(newOutputTestDirectory("overlapping-outputFile"));
+        if (outputFile != null) mojo.setOutputFile(outputFile);
+        final File source = new File("source.adoc");
+        final OptionsBuilder builder = Options.builder();
+        final File sourceDir = new File(".");
+
+        // when
+        Destination file = mojo.setDestinationPaths(source, builder, sourceDir, mojo);
+
+        // then
+        Assertions.assertThat(file.path).isAbsolute();
+        Assertions.assertThat(file.isOutput).isEqualTo(outputFile != null);
+    }
 }