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() < 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());
