Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/main/java/org/codehaus/plexus/build/BuildContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/**
Expand All @@ -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);

/**
Expand All @@ -54,15 +58,19 @@ 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<String> relpaths);

/**
* Indicates that the file or folder content has been modified during the build.
*
* @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);

/**
Expand All @@ -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;

/**
Expand Down Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
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.
* <p>
* 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.
* </p>
*/
@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.
// 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;
}

@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");
}
// 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use LegacySupport here to get the current maven project and return the basedir then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to use LegacySupport to get the current Maven project and resolve paths against the project basedir. Falls back to current working directory if project is not available. See commit 495f4ad.

}

@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);
}
}
}
}
}
184 changes: 184 additions & 0 deletions src/main/java/org/codehaus/plexus/build/resources/Resources.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
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;

/**
* <p>Resources interface.</p>
* <p>
* 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.
* </p>
* <p>
* Example usage:
* </p>
* <pre>
* // 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);
* </pre>
*/
public interface Resources {

/**
* Returns <code>true</code> if the file has changed since last build.
* <p>
* <strong>Important:</strong> 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.
* </p>
*
* @param file the file to check for changes
* @return <code>true</code> 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.
* <p>
* 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.
* </p>
*
* @param file the file that was modified
*/
void refresh(Path file);

/**
* Returns a new OutputStream that writes to the specified file.
* <p>
* Files changed using OutputStream returned by this method do not need to be
* explicitly refreshed using {@link #refresh(Path)}.
* </p>
* <p>
* 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.
* </p>
*
* @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.
* <p>
* Files changed using OutputStream returned by this method do not need to be
* explicitly refreshed using {@link #refresh(Path)}.
* </p>
* <p>
* 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.
* </p>
* <p>
* When <code>derived</code> is <code>true</code>, 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.
* </p>
*
* @param file the file to write to
* @param derived <code>true</code> 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 <code>true</code> if the target file exists and is up-to-date compared
* to the source file.
* <p>
* More specifically, this method returns <code>true</code> 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 <code>false</code>
* in all other cases.
* </p>
* <p>
* 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.
* </p>
*
* @param target the target/output file
* @param source the source/input file
* @return <code>true</code> 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.
* <p>
* 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.
* </p>
*
* @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.
* <p>
* 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.
* </p>
* <p>
* Note: In the default implementation, this is a no-op. Custom implementations
* (e.g., IDE integrations) may provide actual derived file tracking.
* </p>
*
* @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.
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
*
* @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;
}