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

cstamas pushed a commit to branch maven-filtering-3.x-issue-453
in repository https://gitbox.apache.org/repos/asf/maven-filtering.git

commit 5cfc1f256167281f7cebb02d6d141fca886ac614
Author: Tamas Cservenak <[email protected]>
AuthorDate: Thu Feb 19 21:22:52 2026 +0100

    Introduce ChangeDetection
    
    Instead to jump between ways how to perform "change detection"
    (decide should existing target file be overwritten), make it
    a configurable strategy.
    
    Before 3.4.0 it was timestamp, post 3.4.0 it was content, but
    in any case users were left short.
    
    Fixes
    https://github.com/apache/maven-resources-plugin/issues/453
---
 pom.xml                                            |  5 +-
 .../maven/shared/filtering/ChangeDetection.java    | 43 +++++++++++
 .../shared/filtering/DefaultMavenFileFilter.java   | 75 +++++++++++++++----
 .../filtering/DefaultMavenResourcesFiltering.java  |  4 +-
 .../maven/shared/filtering/FilteringUtils.java     | 87 +++++++++++++++++++++-
 .../maven/shared/filtering/MavenFileFilter.java    | 51 ++++++++++++-
 .../shared/filtering/MavenResourcesExecution.java  | 35 +++++++--
 7 files changed, 269 insertions(+), 31 deletions(-)

diff --git a/pom.xml b/pom.xml
index 5d1e6d7..21b86f9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,7 +63,6 @@
   <properties>
     <mavenVersion>3.9.12</mavenVersion>
     <slf4jVersion>1.7.36</slf4jVersion>
-    <plexusBuildApiVersion>0.0.7</plexusBuildApiVersion>
     <version.plexus-utils>3.6.0</version.plexus-utils>
     
<project.build.outputTimestamp>2024-08-28T15:27:52Z</project.build.outputTimestamp>
     <!-- don't fail check for some rules that are too hard to enforce (could 
even be told broken for some)
@@ -83,9 +82,9 @@
       <version>${slf4jVersion}</version>
     </dependency>
     <dependency>
-      <groupId>org.sonatype.plexus</groupId>
+      <groupId>org.codehaus.plexus</groupId>
       <artifactId>plexus-build-api</artifactId>
-      <version>${plexusBuildApiVersion}</version>
+      <version>1.2.0</version>
     </dependency>
     <dependency>
       <groupId>org.apache.maven</groupId>
diff --git 
a/src/main/java/org/apache/maven/shared/filtering/ChangeDetection.java 
b/src/main/java/org/apache/maven/shared/filtering/ChangeDetection.java
new file mode 100644
index 0000000..f4c832b
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/filtering/ChangeDetection.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.shared.filtering;
+
+/**
+ * Change detection strategies: to decide whether an existing target file has 
changed and needs to be overwritten or not.
+ *
+ * @since 3.5.0
+ */
+public enum ChangeDetection {
+    /**
+     * Only consider the timestamp of the file to determine if it has changed. 
This was default before 3.4.0.
+     */
+    TIMESTAMP,
+    /**
+     * Consider the content of the file to determine if it has changed. This 
is the default since 3.4.0.
+     */
+    CONTENT,
+    /**
+     * Combine timestamp and content change detection.
+     */
+    COMBINED,
+    /**
+     * Disable change detection; always overwrite.
+     */
+    ALWAYS;
+}
diff --git 
a/src/main/java/org/apache/maven/shared/filtering/DefaultMavenFileFilter.java 
b/src/main/java/org/apache/maven/shared/filtering/DefaultMavenFileFilter.java
index c6b0354..479d649 100644
--- 
a/src/main/java/org/apache/maven/shared/filtering/DefaultMavenFileFilter.java
+++ 
b/src/main/java/org/apache/maven/shared/filtering/DefaultMavenFileFilter.java
@@ -56,12 +56,37 @@ public class DefaultMavenFileFilter extends BaseFilter 
implements MavenFileFilte
             String encoding,
             MavenSession mavenSession)
             throws MavenFilteringException {
+        copyFile(
+                from,
+                to,
+                filtering,
+                mavenProject,
+                filters,
+                escapedBackslashesInFilePath,
+                encoding,
+                mavenSession,
+                ChangeDetection.CONTENT);
+    }
+
+    @Override
+    public void copyFile(
+            File from,
+            File to,
+            boolean filtering,
+            MavenProject mavenProject,
+            List<String> filters,
+            boolean escapedBackslashesInFilePath,
+            String encoding,
+            MavenSession mavenSession,
+            ChangeDetection changeDetection)
+            throws MavenFilteringException {
         MavenResourcesExecution mre = new MavenResourcesExecution();
         mre.setMavenProject(mavenProject);
         mre.setFileFilters(filters);
         mre.setEscapeWindowsPaths(escapedBackslashesInFilePath);
         mre.setMavenSession(mavenSession);
         mre.setInjectProjectBuildFilters(true);
+        mre.setChangeDetection(changeDetection);
 
         List<FilterWrapper> filterWrappers = getDefaultFilterWrappers(mre);
         copyFile(from, to, filtering, filterWrappers, encoding);
@@ -82,18 +107,50 @@ public class DefaultMavenFileFilter extends BaseFilter 
implements MavenFileFilte
     @Override
     public void copyFile(File from, File to, boolean filtering, 
List<FilterWrapper> filterWrappers, String encoding)
             throws MavenFilteringException {
+        copyFile(from, to, filtering, filterWrappers, encoding, 
ChangeDetection.CONTENT);
+    }
+
+    @Override
+    @Deprecated
+    public void copyFile(
+            File from,
+            File to,
+            boolean filtering,
+            List<FilterWrapper> filterWrappers,
+            String encoding,
+            boolean overwrite)
+            throws MavenFilteringException {
+        // overwrite forced to false to preserve backward comp
+        copyFile(
+                from,
+                to,
+                filtering,
+                filterWrappers,
+                encoding,
+                overwrite ? ChangeDetection.ALWAYS : ChangeDetection.CONTENT);
+    }
+
+    @Override
+    public void copyFile(
+            File from,
+            File to,
+            boolean filtering,
+            List<FilterWrapper> filterWrappers,
+            String encoding,
+            ChangeDetection changeDetection)
+            throws MavenFilteringException {
         try {
             if (filtering) {
                 if (getLogger().isDebugEnabled()) {
                     getLogger().debug("filtering " + from.getPath() + " to " + 
to.getPath());
                 }
                 FilterWrapper[] array = filterWrappers.toArray(new 
FilterWrapper[0]);
-                FilteringUtils.copyFile(from, to, encoding, array, false);
+                FilteringUtils.copyFile(from, to, encoding, array, 
changeDetection);
             } else {
                 if (getLogger().isDebugEnabled()) {
                     getLogger().debug("copy " + from.getPath() + " to " + 
to.getPath());
                 }
-                FilteringUtils.copyFile(from, to, encoding, new 
FilterWrapper[0], false);
+                FilteringUtils.copyFile(from, to, encoding, new 
FilterWrapper[0], changeDetection);
             }
 
             buildContext.refresh(to);
@@ -104,18 +161,4 @@ public class DefaultMavenFileFilter extends BaseFilter 
implements MavenFileFilte
                     e);
         }
     }
-
-    @Override
-    @Deprecated
-    public void copyFile(
-            File from,
-            File to,
-            boolean filtering,
-            List<FilterWrapper> filterWrappers,
-            String encoding,
-            boolean overwrite)
-            throws MavenFilteringException {
-        // overwrite forced to false to preserve backward comp
-        copyFile(from, to, filtering, filterWrappers, encoding);
-    }
 }
diff --git 
a/src/main/java/org/apache/maven/shared/filtering/DefaultMavenResourcesFiltering.java
 
b/src/main/java/org/apache/maven/shared/filtering/DefaultMavenResourcesFiltering.java
index 0c8688f..dd62d12 100644
--- 
a/src/main/java/org/apache/maven/shared/filtering/DefaultMavenResourcesFiltering.java
+++ 
b/src/main/java/org/apache/maven/shared/filtering/DefaultMavenResourcesFiltering.java
@@ -280,7 +280,7 @@ public class DefaultMavenResourcesFiltering implements 
MavenResourcesFiltering {
                 File destinationFile = getDestinationFile(outputDirectory, 
targetPath, name, mavenResourcesExecution);
 
                 if (mavenResourcesExecution.isFlatten() && 
destinationFile.exists()) {
-                    if (mavenResourcesExecution.isOverwrite()) {
+                    if (mavenResourcesExecution.getChangeDetection() == 
ChangeDetection.ALWAYS) {
                         LOGGER.warn("existing file " + 
destinationFile.getName() + " will be overwritten by " + name);
                     } else {
                         throw new MavenFilteringException("existing file " + 
destinationFile.getName()
@@ -303,7 +303,7 @@ public class DefaultMavenResourcesFiltering implements 
MavenResourcesFiltering {
                         resource.isFiltering() && filteredExt,
                         mavenResourcesExecution.getFilterWrappers(),
                         encoding,
-                        mavenResourcesExecution.isOverwrite());
+                        mavenResourcesExecution.getChangeDetection());
             }
 
             // deal with deleted source files
diff --git 
a/src/main/java/org/apache/maven/shared/filtering/FilteringUtils.java 
b/src/main/java/org/apache/maven/shared/filtering/FilteringUtils.java
index b68e3cd..4c3b5f7 100644
--- a/src/main/java/org/apache/maven/shared/filtering/FilteringUtils.java
+++ b/src/main/java/org/apache/maven/shared/filtering/FilteringUtils.java
@@ -308,10 +308,59 @@ public final class FilteringUtils {
      * @throws IOException if an IO error occurs during copying or filtering
      */
     public static void copyFile(File from, File to, String encoding, 
FilterWrapper[] wrappers) throws IOException {
-        setReadWritePermissions(to);
+        copyFile(from, to, encoding, wrappers, ChangeDetection.CONTENT);
+    }
+
+    /**
+     * <b>If wrappers is null or empty, the file will be copy only if 
to.lastModified() &lt; from.lastModified() or if
+     * overwrite is true</b>.
+     *
+     * @param from the file to copy
+     * @param to the destination file
+     * @param encoding the file output encoding (only if wrappers is not empty)
+     * @param wrappers array of {@link FilterWrapper}
+     * @throws IOException if an IO error occurs during copying or filtering
+     */
+    public static void copyFile(
+            File from, File to, String encoding, FilterWrapper[] wrappers, 
ChangeDetection changeDetection)
+            throws IOException {
+        if (!to.isFile()) {
+            changeDetection = ChangeDetection.ALWAYS;
+        }
+        boolean needsCopy = false;
+        boolean unconditionally = false;
+        switch (changeDetection) {
+            case ALWAYS:
+                needsCopy = true;
+                unconditionally = true;
+                break;
+            case TIMESTAMP:
+                needsCopy = to.lastModified() < from.lastModified();
+                unconditionally = true;
+                break;
+            case CONTENT:
+                needsCopy = true;
+                break;
+            case COMBINED:
+                needsCopy = to.lastModified() < from.lastModified();
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported change 
detection mode: " + changeDetection);
+        }
+        if (needsCopy) {
+            if (unconditionally) {
+                copyUnconditionally(from, to, encoding, wrappers);
+            } else {
+                copyIfContentsChanged(from, to, encoding, wrappers);
+            }
+        }
+    }
 
+    public static boolean copyUnconditionally(File from, File to, String 
encoding, FilterWrapper[] wrappers)
+            throws IOException {
+        setReadWritePermissions(to);
         if (wrappers == null || wrappers.length == 0) {
-            try (OutputStream os = new CachingOutputStream(to.toPath())) {
+            try (OutputStream os = Files.newOutputStream(to.toPath())) {
                 Files.copy(from.toPath(), os);
             }
         } else {
@@ -321,7 +370,7 @@ public final class FilteringUtils {
                 for (FilterWrapper wrapper : wrappers) {
                     wrapped = wrapper.getReader(wrapped);
                 }
-                try (Writer writer = new CachingWriter(to.toPath(), charset)) {
+                try (Writer writer = Files.newBufferedWriter(to.toPath(), 
charset)) {
                     char[] buffer = new char[COPY_BUFFER_LENGTH];
                     int nRead;
                     while ((nRead = wrapped.read(buffer, 0, 
COPY_BUFFER_LENGTH)) >= 0) {
@@ -330,8 +379,38 @@ public final class FilteringUtils {
                 }
             }
         }
+        copyFilePermissions(from, to);
+        return true;
+    }
 
+    public static boolean copyIfContentsChanged(File from, File to, String 
encoding, FilterWrapper[] wrappers)
+            throws IOException {
+        setReadWritePermissions(to);
+        boolean copied = false;
+        if (wrappers == null || wrappers.length == 0) {
+            try (CachingOutputStream os = new 
CachingOutputStream(to.toPath())) {
+                Files.copy(from.toPath(), os);
+                copied = os.isModified();
+            }
+        } else {
+            Charset charset = charset(encoding);
+            try (Reader fileReader = Files.newBufferedReader(from.toPath(), 
charset)) {
+                Reader wrapped = fileReader;
+                for (FilterWrapper wrapper : wrappers) {
+                    wrapped = wrapper.getReader(wrapped);
+                }
+                try (CachingWriter writer = new CachingWriter(to.toPath(), 
charset)) {
+                    char[] buffer = new char[COPY_BUFFER_LENGTH];
+                    int nRead;
+                    while ((nRead = wrapped.read(buffer, 0, 
COPY_BUFFER_LENGTH)) >= 0) {
+                        writer.write(buffer, 0, nRead);
+                    }
+                    copied = writer.isModified();
+                }
+            }
+        }
         copyFilePermissions(from, to);
+        return copied;
     }
 
     /**
@@ -349,7 +428,7 @@ public final class FilteringUtils {
     @Deprecated
     public static void copyFile(File from, File to, String encoding, 
FilterWrapper[] wrappers, boolean overwrite)
             throws IOException {
-        copyFile(from, to, encoding, wrappers);
+        copyFile(from, to, encoding, wrappers, overwrite ? 
ChangeDetection.ALWAYS : ChangeDetection.CONTENT);
     }
 
     /**
diff --git 
a/src/main/java/org/apache/maven/shared/filtering/MavenFileFilter.java 
b/src/main/java/org/apache/maven/shared/filtering/MavenFileFilter.java
index a68db1d..7badf11 100644
--- a/src/main/java/org/apache/maven/shared/filtering/MavenFileFilter.java
+++ b/src/main/java/org/apache/maven/shared/filtering/MavenFileFilter.java
@@ -42,6 +42,7 @@ public interface MavenFileFilter extends DefaultFilterInfo {
      * @param encoding The encoding which is used during the filtering process.
      * @throws MavenFilteringException in case of failure.
      * @see DefaultFilterInfo#getDefaultFilterWrappers(MavenProject, List, 
boolean,MavenSession, MavenResourcesExecution)
+     * @deprecated Use copyFile(File, File, boolean, MavenProject, 
List<String>, boolean, String, ChangeDetection) instead.
      */
     void copyFile(
             File from,
@@ -54,6 +55,33 @@ public interface MavenFileFilter extends DefaultFilterInfo {
             MavenSession mavenSession)
             throws MavenFilteringException;
 
+    /**
+     * Will copy a file with some filtering using defaultFilterWrappers.
+     *
+     * @param from file to copy/filter
+     * @param to destination file
+     * @param filtering enable or not filtering
+     * @param mavenProject {@link MavenProject}
+     * @param mavenSession {@link MavenSession}
+     * @param escapedBackslashesInFilePath escape backslashes in file path.
+     * @param filters {@link List} of String which are path to a Property file
+     * @param encoding The encoding which is used during the filtering process.
+     * @throws MavenFilteringException in case of failure.
+     * @see DefaultFilterInfo#getDefaultFilterWrappers(MavenProject, List, 
boolean,MavenSession, MavenResourcesExecution)
+     * @since 3.5.0
+     */
+    void copyFile(
+            File from,
+            File to,
+            boolean filtering,
+            MavenProject mavenProject,
+            List<String> filters,
+            boolean escapedBackslashesInFilePath,
+            String encoding,
+            MavenSession mavenSession,
+            ChangeDetection changeDetection)
+            throws MavenFilteringException;
+
     /**
      * @param mavenFileFilterRequest the request
      * @throws MavenFilteringException in case of failure.
@@ -68,7 +96,9 @@ public interface MavenFileFilter extends DefaultFilterInfo {
      * @param filterWrappers {@link List} of FileUtils.FilterWrapper
      * @param encoding The encoding used during the filtering.
      * @throws MavenFilteringException In case of an error.
+     * @deprecated use {@link #copyFile(File, File, boolean, List, String, 
ChangeDetection)} instead
      */
+    @Deprecated
     void copyFile(File from, File to, boolean filtering, List<FilterWrapper> 
filterWrappers, String encoding)
             throws MavenFilteringException;
 
@@ -81,7 +111,7 @@ public interface MavenFileFilter extends DefaultFilterInfo {
      * @param overwrite unused
      * @throws MavenFilteringException In case of an error.
      * @since 1.0-beta-2
-     * @deprecated use {@link #copyFile(File, File, boolean, List, String)} 
instead
+     * @deprecated use {@link #copyFile(File, File, boolean, List, String, 
ChangeDetection)} instead
      */
     @Deprecated
     void copyFile(
@@ -92,4 +122,23 @@ public interface MavenFileFilter extends DefaultFilterInfo {
             String encoding,
             boolean overwrite)
             throws MavenFilteringException;
+
+    /**
+     * @param from The source file
+     * @param to The destination file
+     * @param filtering true to apply filtering
+     * @param filterWrappers The filters to be applied.
+     * @param encoding The encoding to use
+     * @param changeDetection The change detection mode to use to determine if 
the file should be copied/filtered.
+     * @throws MavenFilteringException In case of an error.
+     * @since 3.5.0
+     */
+    void copyFile(
+            File from,
+            File to,
+            boolean filtering,
+            List<FilterWrapper> filterWrappers,
+            String encoding,
+            ChangeDetection changeDetection)
+            throws MavenFilteringException;
 }
diff --git 
a/src/main/java/org/apache/maven/shared/filtering/MavenResourcesExecution.java 
b/src/main/java/org/apache/maven/shared/filtering/MavenResourcesExecution.java
index 460aeea..4e271a0 100644
--- 
a/src/main/java/org/apache/maven/shared/filtering/MavenResourcesExecution.java
+++ 
b/src/main/java/org/apache/maven/shared/filtering/MavenResourcesExecution.java
@@ -29,6 +29,8 @@ import org.apache.maven.project.MavenProject;
 import org.codehaus.plexus.interpolation.StringSearchInterpolator;
 import org.codehaus.plexus.interpolation.ValueSource;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * A bean to configure a resources filtering execution.
  *
@@ -91,7 +93,7 @@ public class MavenResourcesExecution extends 
AbstractMavenFilteringRequest {
      *
      * @since 1.0-beta-2
      */
-    private boolean overwrite = false;
+    private ChangeDetection changeDetection = ChangeDetection.CONTENT;
 
     /**
      * Copy any empty directories included in the Resources.
@@ -352,11 +354,13 @@ public class MavenResourcesExecution extends 
AbstractMavenFilteringRequest {
     /**
      * Overwrite existing files even if the destination files are newer.
      *
-     * @return {@link #overwrite}
+     * @return {@code true} if operation always overwrites.
      * @since 1.0-beta-2
+     * @deprecated Use #getChangeDetection() instead.
      */
+    @Deprecated
     public boolean isOverwrite() {
-        return overwrite;
+        return changeDetection == ChangeDetection.ALWAYS;
     }
 
     /**
@@ -364,9 +368,30 @@ public class MavenResourcesExecution extends 
AbstractMavenFilteringRequest {
      *
      * @param overwrite overwrite true or false.
      * @since 1.0-beta-2
+     * @deprecated Use #setChangeDetection(ChangeDetection) instead.
      */
+    @Deprecated
     public void setOverwrite(boolean overwrite) {
-        this.overwrite = overwrite;
+        this.changeDetection = overwrite ? ChangeDetection.ALWAYS : 
ChangeDetection.CONTENT;
+    }
+
+    /**
+     * Change detection strategy to determine whether an existing file should 
be overwritten.
+     *
+     * @since 3.5.0
+     */
+    public ChangeDetection getChangeDetection() {
+        return changeDetection;
+    }
+
+    /**
+     * Sets the change detection strategy to determine whether an existing 
file should be overwritten.
+     *
+     * @param changeDetection the change detection strategy to use, must not 
be {@code null}.
+     * @since 3.5.0
+     */
+    public void setChangeDetection(ChangeDetection changeDetection) {
+        this.changeDetection = requireNonNull(changeDetection);
     }
 
     /**
@@ -440,7 +465,7 @@ public class MavenResourcesExecution extends 
AbstractMavenFilteringRequest {
         mre.setMavenSession(this.getMavenSession());
         
mre.setNonFilteredFileExtensions(copyList(this.getNonFilteredFileExtensions()));
         mre.setOutputDirectory(this.getOutputDirectory());
-        mre.setOverwrite(this.isOverwrite());
+        mre.setChangeDetection(this.getChangeDetection());
         
mre.setProjectStartExpressions(copyList(this.getProjectStartExpressions()));
         mre.setResources(copyList(this.getResources()));
         mre.setResourcesBaseDirectory(this.getResourcesBaseDirectory());

Reply via email to