From 5c1d60c3e8a01f27fbbdb5e2084369543dff9e93 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 28 Oct 2025 15:18:20 +0000
Subject: [PATCH 1/4] Initial plan
From 47df1a977504f9e77b9d3cfbfb202417441a2dcb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 28 Oct 2025 15:26:42 +0000
Subject: [PATCH 2/4] Add new Resources API with Path-based methods
Co-authored-by: laeubi <1331477+laeubi@users.noreply.github.com>
---
.../codehaus/plexus/build/BuildContext.java | 12 ++
.../build/resources/DefaultResources.java | 141 ++++++++++++++
.../plexus/build/resources/Resources.java | 183 ++++++++++++++++++
3 files changed, 336 insertions(+)
create mode 100644 src/main/java/org/codehaus/plexus/build/resources/DefaultResources.java
create mode 100644 src/main/java/org/codehaus/plexus/build/resources/Resources.java
diff --git a/src/main/java/org/codehaus/plexus/build/BuildContext.java b/src/main/java/org/codehaus/plexus/build/BuildContext.java
index f132223..a6605a7 100644
--- a/src/main/java/org/codehaus/plexus/build/BuildContext.java
+++ b/src/main/java/org/codehaus/plexus/build/BuildContext.java
@@ -35,7 +35,9 @@ public interface BuildContext {
*
* @param relpath is path relative to build context basedir
* @return a boolean.
+ * @deprecated Use {@link org.codehaus.plexus.build.resources.Resources#hasDelta(java.nio.file.Path)} instead
*/
+ @Deprecated
boolean hasDelta(String relpath);
/**
@@ -45,7 +47,9 @@ public interface BuildContext {
* @since 0.0.5
* @param file a {@link java.io.File} object.
* @return a boolean.
+ * @deprecated Use {@link org.codehaus.plexus.build.resources.Resources#hasDelta(java.nio.file.Path)} instead
*/
+ @Deprecated
boolean hasDelta(File file);
/**
@@ -54,7 +58,9 @@ public interface BuildContext {
*
* @param relpaths paths relative to build context basedir
* @return a boolean.
+ * @deprecated Use {@link org.codehaus.plexus.build.resources.Resources#hasDelta(java.nio.file.Path)} instead
*/
+ @Deprecated
boolean hasDelta(List relpaths);
/**
@@ -62,7 +68,9 @@ public interface BuildContext {
*
* @see #newFileOutputStream(File)
* @param file a {@link java.io.File} object.
+ * @deprecated Use {@link org.codehaus.plexus.build.resources.Resources#refresh(java.nio.file.Path)} instead
*/
+ @Deprecated
void refresh(File file);
/**
@@ -78,7 +86,9 @@ public interface BuildContext {
* @param file a {@link java.io.File} object.
* @return a {@link java.io.OutputStream} object.
* @throws java.io.IOException if any.
+ * @deprecated Use {@link org.codehaus.plexus.build.resources.Resources#newOutputStream(java.nio.file.Path)} instead
*/
+ @Deprecated
OutputStream newFileOutputStream(File file) throws IOException;
/**
@@ -236,6 +246,8 @@ public interface BuildContext {
* @param target a {@link java.io.File} object.
* @param source a {@link java.io.File} object.
* @return a boolean.
+ * @deprecated Use {@link org.codehaus.plexus.build.resources.Resources#isUptodate(java.nio.file.Path, java.nio.file.Path)} instead
*/
+ @Deprecated
boolean isUptodate(File target, File source);
}
diff --git a/src/main/java/org/codehaus/plexus/build/resources/DefaultResources.java b/src/main/java/org/codehaus/plexus/build/resources/DefaultResources.java
new file mode 100644
index 0000000..e076d93
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/build/resources/DefaultResources.java
@@ -0,0 +1,141 @@
+/*
+Copyright (c) 2008 Sonatype, Inc. All rights reserved.
+
+This program is licensed to you under the Apache License Version 2.0,
+and you may not use this file except in compliance with the Apache License Version 2.0.
+You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the Apache License Version 2.0 is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
+*/
+package org.codehaus.plexus.build.resources;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.codehaus.plexus.build.BuildContext;
+
+/**
+ * Default implementation of the Resources interface.
+ *
+ * This implementation delegates to the BuildContext for compatibility with existing
+ * build infrastructure. It provides a transition path from the File-based API to the
+ * modern Path-based API.
+ *
+ */
+@Named("default")
+@Singleton
+public class DefaultResources implements Resources {
+
+ private final BuildContext buildContext;
+
+ /**
+ * Creates a new DefaultResources instance.
+ *
+ * @param buildContext the BuildContext to which operations will be delegated
+ */
+ @Inject
+ public DefaultResources(BuildContext buildContext) {
+ this.buildContext = buildContext;
+ }
+
+ @Override
+ public boolean hasDelta(Path file) {
+ if (file == null) {
+ return false;
+ }
+ return buildContext.hasDelta(file.toFile());
+ }
+
+ @Override
+ public void refresh(Path file) {
+ if (file != null) {
+ buildContext.refresh(file.toFile());
+ }
+ }
+
+ @Override
+ public OutputStream newOutputStream(Path file) throws IOException {
+ if (file == null) {
+ throw new IllegalArgumentException("file cannot be null");
+ }
+ return buildContext.newFileOutputStream(file.toFile());
+ }
+
+ @Override
+ public OutputStream newOutputStream(Path file, boolean derived) throws IOException {
+ OutputStream outputStream = newOutputStream(file);
+ if (derived) {
+ // Mark the file as derived after creating the output stream
+ // The marking happens here so that implementations can wrap the stream
+ // if needed to defer the marking until after successful write
+ markDerived(file);
+ }
+ return outputStream;
+ }
+
+ @Override
+ public boolean isUptodate(Path target, Path source) {
+ if (target == null || source == null) {
+ return false;
+ }
+ return buildContext.isUptodate(target.toFile(), source.toFile());
+ }
+
+ @Override
+ public Path getPath(String relpath) {
+ if (relpath == null) {
+ throw new IllegalArgumentException("relpath cannot be null");
+ }
+ // Get the basedir from the build context by checking a known file
+ // The BuildContext API doesn't expose basedir directly, so we need to work around this
+ // For the default implementation, we'll resolve against the current working directory
+ // Custom implementations should override this to use the actual basedir
+ return java.nio.file.Paths.get(relpath);
+ }
+
+ @Override
+ public void markDerived(Path file) {
+ // No-op in the default implementation
+ // Custom implementations (e.g., IDE integrations) can override this
+ // to provide actual derived file tracking
+ }
+
+ @Override
+ public void copy(Path source, Path target) throws IOException {
+ if (source == null) {
+ throw new IllegalArgumentException("source cannot be null");
+ }
+ if (target == null) {
+ throw new IllegalArgumentException("target cannot be null");
+ }
+
+ // Only copy if the target is not up-to-date with the source
+ if (!isUptodate(target, source)) {
+ // Ensure parent directory exists
+ Path parentDir = target.getParent();
+ if (parentDir != null && !Files.exists(parentDir)) {
+ Files.createDirectories(parentDir);
+ }
+
+ // Copy using Files API and newOutputStream to ensure proper change detection
+ try (InputStream in = Files.newInputStream(source);
+ OutputStream out = newOutputStream(target)) {
+ byte[] buffer = new byte[8192];
+ int bytesRead;
+ while ((bytesRead = in.read(buffer)) != -1) {
+ out.write(buffer, 0, bytesRead);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/codehaus/plexus/build/resources/Resources.java b/src/main/java/org/codehaus/plexus/build/resources/Resources.java
new file mode 100644
index 0000000..657e5d9
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/build/resources/Resources.java
@@ -0,0 +1,183 @@
+/*
+Copyright (c) 2008 Sonatype, Inc. All rights reserved.
+
+This program is licensed to you under the Apache License Version 2.0,
+and you may not use this file except in compliance with the Apache License Version 2.0.
+You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the Apache License Version 2.0 is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
+*/
+package org.codehaus.plexus.build.resources;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Path;
+
+/**
+ * Resources interface.
+ *
+ * This API provides a modern, flexible way to manage build resources using Path-based methods.
+ * It separates resource management concerns from logging/messaging functionality and provides
+ * better control over file operations during the build process.
+ *
+ *
+ * Example usage:
+ *
+ *
+ * // Convert relative path to absolute Path
+ * Path sourceFile = resources.getPath("src/main/java/Example.java");
+ * Path targetFile = resources.getPath("target/classes/Example.class");
+ *
+ * // Copy source to target only if needed
+ * resources.copy(sourceFile, targetFile);
+ *
+ * // Mark generated file as derived
+ * resources.markDerived(targetFile);
+ *
+ */
+public interface Resources {
+
+ /**
+ * Returns true if the file has changed since last build.
+ *
+ * Important: This is a "best effort" hint and should primarily be used
+ * for user-editable source files where you want to detect changes. For generated
+ * files or when there is a clear input/output relationship, prefer using
+ * {@link #isUptodate(Path, Path)} instead, as it handles cases where target files
+ * may be deleted or modified outside of the build process.
+ *
+ *
+ * @param file the file to check for changes
+ * @return true if the file has changed or if the file is not under basedir
+ */
+ boolean hasDelta(Path file);
+
+ /**
+ * Indicates that the file or folder content has been modified during the build.
+ *
+ * This method should be called when file content is modified through means other
+ * than {@link #newOutputStream(Path)} or {@link #newOutputStream(Path, boolean)}.
+ * Files changed using OutputStreams returned by those methods do not need to be
+ * explicitly refreshed.
+ *
+ *
+ * @param file the file that was modified
+ */
+ void refresh(Path file);
+
+ /**
+ * Returns a new OutputStream that writes to the specified file.
+ *
+ * Files changed using OutputStream returned by this method do not need to be
+ * explicitly refreshed using {@link #refresh(Path)}.
+ *
+ *
+ * As an optimization, OutputStreams created by incremental build contexts will
+ * attempt to avoid writing to the file if the file content has not changed.
+ * This ensures that file timestamps are only updated when actual changes occur,
+ * which is important for incremental build performance.
+ *
+ *
+ * @param file the file to write to
+ * @return an OutputStream for writing to the file
+ * @throws IOException if an I/O error occurs
+ */
+ OutputStream newOutputStream(Path file) throws IOException;
+
+ /**
+ * Returns a new OutputStream that writes to the specified file and optionally
+ * marks it as derived.
+ *
+ * Files changed using OutputStream returned by this method do not need to be
+ * explicitly refreshed using {@link #refresh(Path)}.
+ *
+ *
+ * As an optimization, OutputStreams created by incremental build contexts will
+ * attempt to avoid writing to the file if the file content has not changed.
+ *
+ *
+ * When derived is true, the file is marked as generated/derived,
+ * which can be used by IDEs to warn users if they attempt to edit the file.
+ * This is useful for code generators that will overwrite any manual changes on
+ * subsequent builds.
+ *
+ *
+ * @param file the file to write to
+ * @param derived true to mark the file as derived/generated
+ * @return an OutputStream for writing to the file
+ * @throws IOException if an I/O error occurs
+ */
+ OutputStream newOutputStream(Path file, boolean derived) throws IOException;
+
+ /**
+ * Returns true if the target file exists and is up-to-date compared
+ * to the source file.
+ *
+ * More specifically, this method returns true when both target and
+ * source files exist, do not have changes since last incremental build, and the
+ * target file was last modified later than the source file. Returns false
+ * in all other cases.
+ *
+ *
+ * This method should be preferred over {@link #hasDelta(Path)} when there is a
+ * clear input/output relationship between files, as it properly handles cases
+ * where the target file may be deleted or modified outside of the build process.
+ *
+ *
+ * @param target the target/output file
+ * @param source the source/input file
+ * @return true if the target is up-to-date with the source
+ */
+ boolean isUptodate(Path target, Path source);
+
+ /**
+ * Converts a relative path string to an absolute Path based on the build context basedir.
+ *
+ * This method is useful for converting relative paths (e.g., "src/main/java/Example.java")
+ * to absolute Paths that can be used with other methods in this interface.
+ *
+ *
+ * @param relpath the relative path string
+ * @return the absolute Path resolved against the build context basedir
+ */
+ Path getPath(String relpath);
+
+ /**
+ * Marks a file as derived (generated) by the build process.
+ *
+ * This is useful for code generators and other build plugins that create files
+ * that should not be edited manually. IDEs can use this information to warn
+ * users if they attempt to modify derived files, as such changes will typically
+ * be overwritten on the next build.
+ *
+ *
+ * Note: In the default implementation, this is a no-op. Custom implementations
+ * (e.g., IDE integrations) may provide actual derived file tracking.
+ *
+ *
+ * @param file the file to mark as derived
+ */
+ void markDerived(Path file);
+
+ /**
+ * Copies the source file to the target file if the target is not up-to-date.
+ *
+ * This is a convenience method that combines {@link #isUptodate(Path, Path)}
+ * checking with file copying using {@link #newOutputStream(Path)}. The copy
+ * only occurs when the target does not exist or is not up-to-date with the source.
+ *
+ *
+ * The implementation uses the Files API to open an input stream from the source
+ * and copies it to an output stream obtained from {@link #newOutputStream(Path)},
+ * ensuring that the target's timestamp is only updated if the content actually changes.
+ *
+ *
+ * @param source the source file to copy from
+ * @param target the target file to copy to
+ * @throws IOException if an I/O error occurs during the copy operation
+ */
+ void copy(Path source, Path target) throws IOException;
+}
From b9f0758b3debaf58f4de12b74b55132d6fa1849f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 28 Oct 2025 15:29:43 +0000
Subject: [PATCH 3/4] Address code review feedback on Resources API
documentation
Co-authored-by: laeubi <1331477+laeubi@users.noreply.github.com>
---
.../plexus/build/resources/DefaultResources.java | 15 ++++++++-------
.../plexus/build/resources/Resources.java | 13 +++++++------
2 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/src/main/java/org/codehaus/plexus/build/resources/DefaultResources.java b/src/main/java/org/codehaus/plexus/build/resources/DefaultResources.java
index e076d93..3ba916b 100644
--- a/src/main/java/org/codehaus/plexus/build/resources/DefaultResources.java
+++ b/src/main/java/org/codehaus/plexus/build/resources/DefaultResources.java
@@ -75,9 +75,10 @@ public OutputStream newOutputStream(Path file) throws IOException {
public OutputStream newOutputStream(Path file, boolean derived) throws IOException {
OutputStream outputStream = newOutputStream(file);
if (derived) {
- // Mark the file as derived after creating the output stream
- // The marking happens here so that implementations can wrap the stream
- // if needed to defer the marking until after successful write
+ // Mark the file as derived after creating the output stream.
+ // In the default implementation, markDerived is a no-op, so the timing doesn't matter.
+ // Custom implementations that track derived files should consider wrapping the stream
+ // to defer marking until successful close() to avoid marking files on failed writes.
markDerived(file);
}
return outputStream;
@@ -96,10 +97,10 @@ public Path getPath(String relpath) {
if (relpath == null) {
throw new IllegalArgumentException("relpath cannot be null");
}
- // Get the basedir from the build context by checking a known file
- // The BuildContext API doesn't expose basedir directly, so we need to work around this
- // For the default implementation, we'll resolve against the current working directory
- // Custom implementations should override this to use the actual basedir
+ // Note: The BuildContext API doesn't expose basedir directly.
+ // This default implementation resolves paths relative to the current working directory.
+ // Custom implementations (e.g., IDE integrations) should override this method
+ // to resolve against the actual build context basedir.
return java.nio.file.Paths.get(relpath);
}
diff --git a/src/main/java/org/codehaus/plexus/build/resources/Resources.java b/src/main/java/org/codehaus/plexus/build/resources/Resources.java
index 657e5d9..73a3cb0 100644
--- a/src/main/java/org/codehaus/plexus/build/resources/Resources.java
+++ b/src/main/java/org/codehaus/plexus/build/resources/Resources.java
@@ -75,10 +75,10 @@ public interface Resources {
* explicitly refreshed using {@link #refresh(Path)}.
*
*
- * As an optimization, OutputStreams created by incremental build contexts will
- * attempt to avoid writing to the file if the file content has not changed.
- * This ensures that file timestamps are only updated when actual changes occur,
- * which is important for incremental build performance.
+ * As an optimization, some implementations may attempt to avoid writing to the file
+ * if the file content has not changed. This ensures that file timestamps are only
+ * updated when actual changes occur, which is important for incremental build performance.
+ * This optimization is implementation-dependent and may not apply to all implementations.
*
*
* @param file the file to write to
@@ -95,8 +95,9 @@ public interface Resources {
* explicitly refreshed using {@link #refresh(Path)}.
*
*
- * As an optimization, OutputStreams created by incremental build contexts will
- * attempt to avoid writing to the file if the file content has not changed.
+ * As an optimization, some implementations may attempt to avoid writing to the file
+ * if the file content has not changed. This optimization is implementation-dependent
+ * and may not apply to all implementations.
*
*
* When derived is true, the file is marked as generated/derived,
From 495f4ad5e4f371181477a509b336282c69a8efa9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 28 Oct 2025 16:07:59 +0000
Subject: [PATCH 4/4] Use LegacySupport to get Maven project basedir in
Resources.getPath() and update README
Co-authored-by: laeubi <1331477+laeubi@users.noreply.github.com>
---
README.md | 53 +++++++++++++++++++
.../build/resources/DefaultResources.java | 22 ++++++--
2 files changed, 70 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 4509abf..a25968d 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,59 @@ The project was relocated from . Als
## Provided APIs
+### Resources API
+
+The Resources API provides a modern, Path-based interface for managing build resources. It separates resource management concerns from logging/messaging functionality and provides better control over file operations during the build process.
+
+**Key Features:**
+- Modern `java.nio.file.Path` instead of `java.io.File`
+- Change detection with `hasDelta()` (best effort hint)
+- Reliable input/output freshness checking with `isUptodate()`
+- Optimized output streams that only update files when content changes
+- Support for marking generated/derived files for IDE integration
+- Convenient copy operation that respects up-to-date checks
+- Relative path conversion via `getPath()`
+
+**Example Usage:**
+
+```java
+@Inject
+private Resources resources;
+
+public void execute() {
+ // Convert relative paths to absolute paths
+ Path source = resources.getPath("src/main/java/Example.java");
+ Path target = resources.getPath("target/classes/Example.class");
+
+ // Smart copy - only copies when target is stale
+ resources.copy(source, target);
+
+ // Check if file has changed (best effort hint)
+ if (resources.hasDelta(source)) {
+ // Process the file
+ }
+
+ // Reliable check for input/output scenarios
+ if (!resources.isUptodate(target, source)) {
+ // Regenerate target from source
+ }
+
+ // Write to a file with change detection
+ Path generated = resources.getPath("target/generated/Proto.java");
+ try (OutputStream out = resources.newOutputStream(generated, true)) {
+ // Write content - file marked as derived for IDE warnings
+ out.write(content);
+ }
+
+ // Mark a file as generated/derived
+ resources.markDerived(generated);
+}
+```
+
+**When to use `hasDelta()` vs `isUptodate()`:**
+- Use `hasDelta()` as a hint for user-editable source files where you want to detect changes
+- Use `isUptodate()` when there's a clear input/output relationship, as it handles cases where target files may be deleted or modified outside the build process
+
### Messages API
The Messages API provides a modern, flexible way to create and manage build messages/markers that inform users in an IDE about issues in their files. It uses a builder pattern for constructing messages in a more convenient and extensible way compared to the legacy BuildContext message methods.
diff --git a/src/main/java/org/codehaus/plexus/build/resources/DefaultResources.java b/src/main/java/org/codehaus/plexus/build/resources/DefaultResources.java
index 3ba916b..6f96f55 100644
--- a/src/main/java/org/codehaus/plexus/build/resources/DefaultResources.java
+++ b/src/main/java/org/codehaus/plexus/build/resources/DefaultResources.java
@@ -16,12 +16,15 @@
import javax.inject.Named;
import javax.inject.Singleton;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
+import org.apache.maven.plugin.LegacySupport;
+import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.build.BuildContext;
/**
@@ -37,15 +40,18 @@
public class DefaultResources implements Resources {
private final BuildContext buildContext;
+ private final LegacySupport legacySupport;
/**
* Creates a new DefaultResources instance.
*
* @param buildContext the BuildContext to which operations will be delegated
+ * @param legacySupport the LegacySupport to get the current Maven project
*/
@Inject
- public DefaultResources(BuildContext buildContext) {
+ public DefaultResources(BuildContext buildContext, LegacySupport legacySupport) {
this.buildContext = buildContext;
+ this.legacySupport = legacySupport;
}
@Override
@@ -97,10 +103,16 @@ public Path getPath(String relpath) {
if (relpath == null) {
throw new IllegalArgumentException("relpath cannot be null");
}
- // Note: The BuildContext API doesn't expose basedir directly.
- // This default implementation resolves paths relative to the current working directory.
- // Custom implementations (e.g., IDE integrations) should override this method
- // to resolve against the actual build context basedir.
+ // Get the basedir from the current Maven project via LegacySupport
+ MavenProject project =
+ legacySupport.getSession() != null ? legacySupport.getSession().getCurrentProject() : null;
+ if (project != null) {
+ File basedir = project.getBasedir();
+ if (basedir != null) {
+ return basedir.toPath().resolve(relpath);
+ }
+ }
+ // Fallback to current working directory if project is not available
return java.nio.file.Paths.get(relpath);
}