This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-doxia-sitetools.git


The following commit(s) were added to refs/heads/master by this push:
     new 94fb8bc  [DOXIASITETOOLS-359] Support site directories where 
duplicates are skipped
94fb8bc is described below

commit 94fb8bcae113e51070c1312bfff94597cb8bf173
Author: Konrad Windszus <k...@apache.org>
AuthorDate: Wed Feb 12 13:14:09 2025 +0100

    [DOXIASITETOOLS-359] Support site directories where duplicates are
    skipped
    
    Usually a duplicate relative file path in multiple site directories lead
    to a RendererException (because only one can end up in the built site).
    For some cases it is beneficial to add source directories where
    duplicates should be just silently skipped (for example to allow
    overwriting certain files in previous sources).
---
 .../doxia/siterenderer/DefaultSiteRenderer.java    | 118 ++++++++++++++-------
 .../doxia/siterenderer/SiteRenderingContext.java   |  17 +++
 .../siterenderer/DefaultSiteRendererTest.java      |  46 +++++++-
 3 files changed, 144 insertions(+), 37 deletions(-)

diff --git 
a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java
 
b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java
index 5dd1d47..9e951c5 100644
--- 
a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java
+++ 
b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java
@@ -37,15 +37,16 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Enumeration;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.TimeZone;
+import java.util.function.Function;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipException;
 import java.util.zip.ZipFile;
@@ -171,7 +172,8 @@ public class DefaultSiteRenderer implements Renderer {
                             module,
                             excludes,
                             files,
-                            siteDirectory.isEditable());
+                            siteDirectory.isEditable(),
+                            siteDirectory.isSkipDuplicates());
                 }
             }
         }
@@ -198,7 +200,8 @@ public class DefaultSiteRenderer implements Renderer {
             ParserModule module,
             String excludes,
             Map<String, DocumentRenderer> files,
-            boolean editable)
+            boolean editable,
+            boolean skipDuplicates)
             throws IOException, RendererException {
         if (!moduleBasedir.exists() || 
ArrayUtils.isEmpty(module.getExtensions())) {
             return;
@@ -209,8 +212,6 @@ public class DefaultSiteRenderer implements Renderer {
 
         List<String> allFiles = FileUtils.getFileNames(moduleBasedir, "**/*", 
excludes, false);
 
-        Map<String, String> caseInsensitiveFiles = new HashMap<>();
-
         for (String extension : module.getExtensions()) {
             String fullExtension = "." + extension;
 
@@ -230,44 +231,89 @@ public class DefaultSiteRenderer implements Renderer {
                     docRenderingContext.setAttribute("velocity", "true");
                 }
 
-                String key = docRenderingContext.getOutputName();
-
-                if (files.containsKey(key)) {
-                    DocumentRenderer docRenderer = files.get(key);
+                if (!checkForDuplicate(docRenderingContext, files, 
skipDuplicates)) {
+                    String key = docRenderingContext.getOutputName();
+                    files.put(key, new 
DoxiaDocumentRenderer(docRenderingContext));
+                }
+            }
+        }
+    }
 
-                    DocumentRenderingContext originalDocRenderingContext = 
docRenderer.getRenderingContext();
+    @FunctionalInterface
+    private interface DuplicateCallback {
+        /**
+         * Callback for handling duplicate files.
+         * @param message
+         * @return {@code false} if the duplicate should be ignored, {@code 
true} otherwise
+         * @throws RendererException
+         */
+        boolean onDuplicate(String message) throws RendererException;
+    }
 
-                    File originalDoc = new File(
-                            originalDocRenderingContext.getBasedir(), 
originalDocRenderingContext.getInputName());
+    /**
+     * Checks if the newDocRenderingContext clashes with an existing document 
renderer.
+     * This check involves checking for duplicates both case-sensitive and 
case-insensitive.
+     * @param newDocRenderingContext the doc rendering context of a new file
+     * @param existingDocumentRenderers the map of already existing renderers
+     * @return {@code true} if no duplicates were found, {@code false} 
otherwise
+     * @throws RendererException
+     */
+    private boolean checkForDuplicate(
+            DocumentRenderingContext newDocRenderingContext,
+            Map<String, DocumentRenderer> existingDocumentRenderers,
+            boolean skipDuplicates)
+            throws RendererException {
+        DuplicateCallback duplicateCallback = (message) -> {
+            if (skipDuplicates) {
+                LOGGER.debug(message + " (ignored due to flag 
'skipDuplicates').");
+            } else {
+                throw new RendererException(message + ".");
+            }
+            return true;
+        };
 
-                    throw new RendererException("File '" + 
module.getSourceDirectory() + File.separator + doc
-                            + "' clashes with existing '" + originalDoc + 
"'.");
+        DuplicateCallback caseInsensitiveDuplicateCallback = (message) -> {
+            if (Os.isFamily(Os.FAMILY_WINDOWS)) {
+                return duplicateCallback.onDuplicate(message);
+            } else {
+                if (LOGGER.isWarnEnabled()) {
+                    LOGGER.warn(message + " in case a case-insensitive 
filesystem is used.");
                 }
-                // 
-----------------------------------------------------------------------
-                // Handle key without case differences
-                // 
-----------------------------------------------------------------------
-                String originalKey = 
caseInsensitiveFiles.put(key.toLowerCase(Locale.ROOT), key);
-                if (originalKey != null) {
-                    DocumentRenderingContext originalDocRenderingContext =
-                            files.get(originalKey).getRenderingContext();
-
-                    File originalDoc = new File(
-                            originalDocRenderingContext.getBasedir(), 
originalDocRenderingContext.getInputName());
-
-                    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
-                        throw new RendererException("File '" + 
module.getSourceDirectory() + File.separator + doc
-                                + "' clashes with existing '" + originalDoc + 
"'.");
-                    }
+                return false;
+            }
+        };
+
+        if (!checkForDuplicate(newDocRenderingContext, 
existingDocumentRenderers::get, duplicateCallback)) {
+            // also check for case-insensitive duplicates
+            return checkForDuplicate(
+                    newDocRenderingContext,
+                    key -> existingDocumentRenderers.entrySet().stream()
+                            .filter(e -> e.getKey().equalsIgnoreCase(key))
+                            .findFirst()
+                            .map(Entry::getValue)
+                            .orElse(null),
+                    caseInsensitiveDuplicateCallback);
+        }
+        return true;
+    }
 
-                    if (LOGGER.isWarnEnabled()) {
-                        LOGGER.warn("File '" + module.getSourceDirectory() + 
File.separator + doc
-                                + "' could clash with existing '" + 
originalDoc + "'.");
-                    }
-                }
+    private boolean checkForDuplicate(
+            DocumentRenderingContext newDocRenderingContext,
+            Function<String, DocumentRenderer> lookupFunction,
+            DuplicateCallback callback)
+            throws RendererException {
+        DocumentRenderer originalDocRenderer = 
lookupFunction.apply(newDocRenderingContext.getOutputName());
+        if (originalDocRenderer != null) {
+            DocumentRenderingContext originalDocRenderingContext = 
originalDocRenderer.getRenderingContext();
 
-                files.put(key, new DoxiaDocumentRenderer(docRenderingContext));
-            }
+            File originalFile =
+                    new File(originalDocRenderingContext.getBasedir(), 
originalDocRenderingContext.getInputName());
+
+            File newFile = new File(newDocRenderingContext.getBasedir(), 
newDocRenderingContext.getInputName());
+            String message = "File '" + newFile + "' clashes with existing '" 
+ originalFile + "'";
+            return callback.onDuplicate(message);
         }
+        return false;
     }
 
     /** {@inheritDoc} */
diff --git 
a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java
 
b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java
index 03c2d28..64db4c7 100644
--- 
a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java
+++ 
b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java
@@ -43,10 +43,23 @@ public class SiteRenderingContext {
     public static class SiteDirectory {
         private File path;
         private boolean editable;
+        private boolean skipDuplicates;
 
         public SiteDirectory(File path, boolean editable) {
+            this(path, editable, false);
+        }
+
+        /**
+         *
+         * @param path
+         * @param editable
+         * @param skipDuplicates flag indicating if duplicates in this 
directory should be skipped ({@code true}) or lead to an exception ({@code 
false})
+         * @since 2.1
+         */
+        public SiteDirectory(File path, boolean editable, boolean 
skipDuplicates) {
             this.path = path;
             this.editable = editable;
+            this.skipDuplicates = skipDuplicates;
         }
 
         public File getPath() {
@@ -56,6 +69,10 @@ public class SiteRenderingContext {
         public boolean isEditable() {
             return editable;
         }
+
+        public boolean isSkipDuplicates() {
+            return skipDuplicates;
+        }
     }
 
     private String inputEncoding = ReaderFactory.FILE_ENCODING;
diff --git 
a/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java
 
b/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java
index 8a2c04e..0a086b3 100644
--- 
a/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java
+++ 
b/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java
@@ -31,9 +31,11 @@ import java.io.StringWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.jar.JarOutputStream;
 import java.util.zip.ZipEntry;
 
@@ -67,9 +69,15 @@ import static 
org.codehaus.plexus.testing.PlexusExtension.getTestFile;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
-import static org.mockito.Mockito.*;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 /**
  * @author <a href="mailto:vincent.sive...@gmail.com";>Vincent Siveton</a>
@@ -319,6 +327,42 @@ public class DefaultSiteRendererTest {
         assertFalse(r.matchVersion("1.7", "1.8"));
     }
 
+    @Test
+    public void testLocateDocumentFiles() throws IOException, 
RendererException {
+        SiteRenderingContext context = new SiteRenderingContext();
+        File sourceDirectory = getTestFile("src/test/resources/site-validate");
+        context.setRootDirectory(sourceDirectory);
+        context.addSiteDirectory(new SiteDirectory(sourceDirectory, true));
+        Set<String> outputFiles = 
siteRenderer.locateDocumentFiles(context).keySet();
+        Set<String> expectedOutputFiles = new HashSet<>();
+        expectedOutputFiles.add("entityTest.html");
+        assertEquals(expectedOutputFiles, outputFiles);
+    }
+
+    @Test
+    public void testLocateDocumentFilesWithNameClashes() throws IOException, 
RendererException {
+        SiteRenderingContext context = new SiteRenderingContext();
+        File sourceDirectory = getTestFile("src/test/resources/site-validate");
+        context.setRootDirectory(sourceDirectory);
+        context.addSiteDirectory(new SiteDirectory(sourceDirectory, true));
+        context.addSiteDirectory(new SiteDirectory(sourceDirectory, true));
+        assertThrows(RendererException.class, () -> 
siteRenderer.locateDocumentFiles(context));
+    }
+
+    @Test
+    public void 
testLocateDocumentFilesWithNameClashesInSkippingDuplicatesDirectory()
+            throws IOException, RendererException {
+        SiteRenderingContext context = new SiteRenderingContext();
+        File sourceDirectory = getTestFile("src/test/resources/site-validate");
+        context.setRootDirectory(sourceDirectory);
+        context.addSiteDirectory(new SiteDirectory(sourceDirectory, true));
+        context.addSiteDirectory(new SiteDirectory(sourceDirectory, true, 
true));
+        Set<String> outputFiles = 
siteRenderer.locateDocumentFiles(context).keySet();
+        Set<String> expectedOutputFiles = new HashSet<>();
+        expectedOutputFiles.add("entityTest.html");
+        assertEquals(expectedOutputFiles, outputFiles);
+    }
+
     private SiteRenderingContext getSiteRenderingContext(SiteModel siteModel, 
String siteDir, boolean validate)
             throws RendererException, IOException {
         File skinFile = minimalSkinJar;

Reply via email to