diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b76b895 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..6b8ce40 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,2 @@ +_extends: .github +tag-template: plexus-build-api-$NEXT_MINOR_VERSION diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..09feae4 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,31 @@ +# 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. + +name: GitHub CI + +on: [push, pull_request] + +jobs: + build: + name: Build it + uses: codehaus-plexus/.github/.github/workflows/maven.yml@master + + deploy: + name: Deploy + needs: build + uses: codehaus-plexus/.github/.github/workflows/maven-deploy.yml@master + secrets: inherit diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 0000000..4c09c8a --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,12 @@ +name: Release Drafter +on: + push: + branches: + - master +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37eebec --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +target/ +*.iml +.idea/ +.classpath +.project +.settings/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4509abf --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +Plexus Build API +================ + +[![Apache License, Version 2.0, January 2004](https://img.shields.io/github/license/codehaus-plexus/plexus-build-api.svg?label=License)](http://www.apache.org/licenses/) +[![Maven Central](https://img.shields.io/maven-central/v/org.codehaus.plexus/plexus-build-api.svg?label=Maven%20Central)](https://search.maven.org/artifact/org.codehaus.plexus/plexus-build-api) +![Build Status](https://github.com/codehaus-plexus/plexus-build-api/actions/workflows/maven.yml/badge.svg) + +This API allows IDEs to integrate with Maven deeper than it would be possible by just using regular Maven/Mojo API. + +It supports + +- incremental builds e.g. allows to query which files have been touched since last build +- fine-grained error/info markers (referring to specific files in particular line numbers) +- notifications about updated files + +Current Implementations +----------------------- + +### Default Implementation + +The default implementation shipping with this artifact is supposed to impose minimal overhead. It doesn't support incremental build and acts directly on the file system. Errors and warning are just logged through SLF4J. + +### M2Eclipse + +[M2Eclipse](https://www.eclipse.org/m2e/) is using this API for supporting both incremental builds and fully integrated error markers in Eclipse. They maintain information for Mojo developers at [Making Maven Plugins Compatible](https://www.eclipse.org/m2e/documentation/m2e-making-maven-plugins-compat.html). +Currently only versions up to 0.0.7 (with old Maven coordinates `org.sonatype.plexus:plexus-build-api`) are supported, this limitation is tracked in [Issue 944](https://github.com/eclipse-m2e/m2e-core/issues/944). + +History +------- + +The project was relocated from . Also its Maven coordinates changed from `org.sonatype.plexus:plexus-build-api` to `org.codehaus.plexus:plexus-build-api`, the API is still the same, though. + +## Provided APIs + +### 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. + +**Key Features:** +- Builder pattern for flexible message construction +- Clear separation of concerns from resource operations +- Support for error, warning, and info messages +- File path-based message management +- Optional line and column information +- Optional exception cause association + +**Example Usage:** + +```java +@Inject +private Messages messages; + +public void execute() { + // Create an error message + messages.error(Paths.get("/path/to/file.java")) + .line(42) + .column(10) + .cause(exception) + .create("Syntax error"); + + // Create a warning message + messages.warning(Paths.get("/path/to/file.java")) + .line(15) + .create("Deprecated method used"); + + // Clear messages for a specific file + messages.clear(Paths.get("/path/to/file.java")); + + // Clear all messages + messages.clearAll(); +} +``` + +### Progress + +The API allows a mojo to report progress in a way that is suitable to be shown as a progressbar as well as check if the user wants the mojo to gracefully abort its current operation. +This can be useful for example when processing some files in a loop so the user can directly see the amount of progress and possibly ask to abort if it takes to long. + +### IDE connection to maven process + +This API is usually not used by mojos but for IDE integration, if enabled as a maven-core extension plexus-build-api supply a way to communicate with the running maven build and get events. +The default implementation open a tcp connections to a port specified by the system property `plexus.build.ipc.port` using key/value encoded message format. If no such value is given all messages are silently discarded. diff --git a/pom.xml b/pom.xml index 965f9d8..e3aa29f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,3 +1,4 @@ + - 4.0.0 + - org.sonatype.spice - spice-parent - 15 + org.codehaus.plexus + plexus + 24 - org.sonatype.plexus + plexus-build-api - 0.0.7-SNAPSHOT - + 1.2.1-SNAPSHOT + + + scm:git:https://github.com/codehaus-plexus/plexus-build-api.git + ${project.scm.connection} + master + https://github.com/codehaus-plexus/plexus-build-api/tree/master + + org.codehaus.plexus plexus-utils - 1.5.8 + 4.0.2 - org.codehaus.plexus - plexus-container-default - 1.0-alpha-9 + javax.inject + javax.inject + 1 + + + org.slf4j + slf4j-api + 1.7.36 + + + + org.sonatype.plexus + plexus-build-api + 0.0.7 + + + * + * + + + + + org.eclipse.sisu + org.eclipse.sisu.plexus + 0.9.0.M4 + + + * + * + + + + + org.apache.maven + maven-core + 3.9.12 provided + - src/main/resources true + src/main/resources - org.codehaus.plexus - plexus-maven-plugin - 1.3.4 - - - - descriptor - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.4 - 1.4 - + org.eclipse.sisu + sisu-maven-plugin org.apache.maven.plugins maven-surefire-plugin - 2.4.2 true @@ -83,6 +108,25 @@ See the Apache License Version 2.0 for the specific language governing permissio + + org.apache.maven.plugins + maven-scm-publish-plugin + + ${project.reporting.outputDirectory} + + + + + scm-publish + + + publish-scm + + site-deploy + + + + diff --git a/src/main/java/org/codehaus/plexus/build/BuildContext.java b/src/main/java/org/codehaus/plexus/build/BuildContext.java new file mode 100644 index 0000000..f132223 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/BuildContext.java @@ -0,0 +1,241 @@ +/* +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; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +import org.codehaus.plexus.util.Scanner; + +/** + *

BuildContext interface.

+ */ +public interface BuildContext { + /** Constant SEVERITY_WARNING=1 */ + int SEVERITY_WARNING = 1; + + /** Constant SEVERITY_ERROR=2 */ + int SEVERITY_ERROR = 2; + + /** + * Returns true if file or folder identified by relpath has + * changed since last build. + * + * @param relpath is path relative to build context basedir + * @return a boolean. + */ + boolean hasDelta(String relpath); + + /** + * Returns true if the file has changed since last build or is not + * under basedir. + * + * @since 0.0.5 + * @param file a {@link java.io.File} object. + * @return a boolean. + */ + boolean hasDelta(File file); + + /** + * Returns true if any file or folder identified by relpaths has + * changed since last build. + * + * @param relpaths paths relative to build context basedir + * @return a boolean. + */ + boolean hasDelta(List 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. + */ + void refresh(File file); + + /** + * Returns new OutputStream that writes to the file. + * + * Files changed using OutputStream returned by this method do not need to be + * explicitly refreshed using {@link #refresh(File)}. + * + * As an optional optimisation, OutputStreams created by incremental build + * context will attempt to avoid writing to the file if file content + * has not changed. + * + * @param file a {@link java.io.File} object. + * @return a {@link java.io.OutputStream} object. + * @throws java.io.IOException if any. + */ + OutputStream newFileOutputStream(File file) throws IOException; + + /** + * Convenience method, fully equal to newScanner(basedir, false) + * + * @param basedir a {@link java.io.File} object. + * @return a {@link org.codehaus.plexus.util.Scanner} object. + */ + Scanner newScanner(File basedir); + + /** + * Returned Scanner scans basedir for files and directories + * deleted since last build. Returns empty Scanner if basedir + * is not under this build context basedir. + * + * @param basedir a {@link java.io.File} object. + * @return a {@link org.codehaus.plexus.util.Scanner} object. + */ + Scanner newDeleteScanner(File basedir); + + /** + * Returned Scanner scans files and folders under basedir. + * + * If this is an incremental build context and ignoreDelta + * is false, the scanner will only "see" files and folders with + * content changes since last build. + * + * If ignoreDelta is true, the scanner will "see" all + * files and folders. + * + * Please beware that ignoreDelta=false does NOT work reliably for operations + * that copy resources from source to target locations. Returned Scanner + * only scans changed source resources and it does not consider changed or deleted + * target resources. This results in missing or stale target resources. + * Starting with 0.5.0, recommended way to process resources is to use + * #newScanner(basedir,true) to locate all source resources and {@link #isUptodate(File, File)} + * to optimized processing of uptodate target resources. + * + * Returns empty Scanner if basedir is not under this build context basedir. + * + * https://issues.apache.org/jira/browse/MSHARED-125 + * + * @param basedir a {@link java.io.File} object. + * @param ignoreDelta a boolean. + * @return a {@link org.codehaus.plexus.util.Scanner} object. + */ + Scanner newScanner(File basedir, boolean ignoreDelta); + + /** + * Returns true if this build context is incremental. + * + * Scanners created by {@link #newScanner(File)} of an incremental build context + * will ignore files and folders that were not changed since last build. + * Additionally, {@link #newDeleteScanner(File)} will scan files and directories + * deleted since last build. + * + * @return a boolean. + */ + boolean isIncremental(); + + /** + * Associate specified key with specified value + * in the build context. + * + * Primary (and the only) purpose of this method is to allow preservation of + * state needed for proper incremental behaviour between consecutive executions + * of the same mojo needed to. + * + * For example, maven-plugin-plugin:descriptor mojo + * can store collection of extracted MojoDescritpor during first invocation. Then + * on each consecutive execution maven-plugin-plugin:descriptor will only need + * to extract MojoDescriptors for changed files. + * + * @see #getValue(String) + * @param key a {@link java.lang.String} object. + * @param value a {@link java.lang.Object} object. + */ + void setValue(String key, Object value); + + /** + * Returns value associated with key during previous mojo execution. + * + * This method always returns null for non-incremental builds + * (i.e., {@link #isIncremental()} returns false) and mojos are + * expected to fall back to full, non-incremental behaviour. + * + * @see #setValue(String, Object) + * @see #isIncremental() + * @param key a {@link java.lang.String} object. + * @return a {@link java.lang.Object} object. + */ + Object getValue(String key); + + /** + *

addWarning.

+ * + * @deprecated Use addMessage with severity=SEVERITY_ERROR instead + * @since 0.0.5 + * @param file a {@link java.io.File} object. + * @param line a int. + * @param column a int. + * @param message a {@link java.lang.String} object. + * @param cause a {@link java.lang.Throwable} object. + */ + void addWarning(File file, int line, int column, String message, Throwable cause); + + /** + *

addError.

+ * + * @deprecated Use addMessage with severity=SEVERITY_WARNING instead + * @since 0.0.5 + * @param file a {@link java.io.File} object. + * @param line a int. + * @param column a int. + * @param message a {@link java.lang.String} object. + * @param cause a {@link java.lang.Throwable} object. + */ + void addError(File file, int line, int column, String message, Throwable cause); + + /** + * Adds a message to the build context. The message is associated with a file and a location inside that file. + * + * @param file The file or folder with which the message is associated. Should not be null and it is recommended to be + * an absolute path. + * @param line The line number inside the file. Use 1 (not 0) for the first line. Use 0 for unknown/unspecified. + * @param column The column number inside the file. Use 1 (not 0) for the first column. Use 0 for unknown/unspecified. + * @param severity The severity of the message: SEVERITY_WARNING or SEVERITY_ERROR. + * @param cause A Throwable object associated with the message. Can be null. + * @since 0.0.7 + * @param message a {@link java.lang.String} object. + * @deprecated Use {@link org.codehaus.plexus.build.messages.Messages} API instead + */ + @Deprecated + void addMessage(File file, int line, int column, String message, int severity, Throwable cause); + + /** + * Removes all messages associated with a file or folder during a previous build. It does not affect the messages + * added during the current build. + * + * @since 0.0.7 + * @param file a {@link java.io.File} object. + * @deprecated Use {@link org.codehaus.plexus.build.messages.Messages#clear(java.nio.file.Path)} instead + */ + @Deprecated + void removeMessages(File file); + + /** + * Returns true, if the target file exists and is uptodate 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. + * + * @since 0.0.5 + * @param target a {@link java.io.File} object. + * @param source a {@link java.io.File} object. + * @return a boolean. + */ + boolean isUptodate(File target, File source); +} diff --git a/src/main/java/org/codehaus/plexus/build/DefaultBuildContext.java b/src/main/java/org/codehaus/plexus/build/DefaultBuildContext.java new file mode 100644 index 0000000..2b34e71 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/DefaultBuildContext.java @@ -0,0 +1,208 @@ +/* +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; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.maven.plugin.LegacySupport; +import org.codehaus.plexus.build.connect.BuildConnection; +import org.codehaus.plexus.build.connect.messages.RefreshMessage; +import org.codehaus.plexus.logging.AbstractLogEnabled; +import org.codehaus.plexus.util.Scanner; +import org.codehaus.plexus.util.io.CachingOutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Filesystem based non-incremental build context implementation which behaves + * as if all files were just created. More specifically, + * + *
    + *
  1. hasDelta returns true for all paths
  2. + *
  3. newScanner returns Scanner that scans all files under provided + * basedir
  4. + *
  5. newDeletedScanner always returns empty scanner
  6. + *
  7. isIncremental returns false
  8. + *
  9. getValue always returns the last set value in this session and only + * stores to memory
  10. + *
+ */ +@Named("default") +@Singleton +public class DefaultBuildContext implements BuildContext { + + private final Logger logger = LoggerFactory.getLogger(DefaultBuildContext.class); + // the legacy API requires the AbstractLogEnabled we just have it here to get + // compile errors in case it is missing from the classpath! + @SuppressWarnings("unused") + private static final AbstractLogEnabled DUMMY = null; + + private final Map contextMap = new ConcurrentHashMap<>(); + private org.sonatype.plexus.build.incremental.BuildContext legacy; + private BuildConnection connection; + private LegacySupport legacySupport; + + /** + * @param legacy the legacy API we delegate to by default, this allow us + * to support "older" plugins and implementors of the API + * while still having a way to move forward! + * @param connection the connection we use to forward refresh events + * @param legacySupport legacy support to get the current session + */ + @Inject + public DefaultBuildContext( + org.sonatype.plexus.build.incremental.BuildContext legacy, + BuildConnection connection, + LegacySupport legacySupport) { + this.legacy = legacy; + this.connection = connection; + this.legacySupport = legacySupport; + } + + /** {@inheritDoc} */ + public boolean hasDelta(String relpath) { + return legacy.hasDelta(relpath); + } + + /** + *

hasDelta.

+ * + * @param file a {@link java.io.File} object. + * @return a boolean. + */ + public boolean hasDelta(File file) { + return legacy.hasDelta(file); + } + + /** + *

hasDelta.

+ * + * @param relpaths a {@link java.util.List} object. + * @return a boolean. + */ + public boolean hasDelta(List relpaths) { + return legacy.hasDelta(relpaths); + } + + /** {@inheritDoc} */ + public OutputStream newFileOutputStream(File file) throws IOException { + if (isDefaultImplementation()) { + return new CachingOutputStream(file.toPath()); + } + return legacy.newFileOutputStream(file); + } + + /** + * @return true if the legacy is the default implementation and we + * can safely override/change behavior here, or false if a + * custom implementation is used and full delegation is required. + */ + private boolean isDefaultImplementation() { + return legacy.getClass().equals(org.sonatype.plexus.build.incremental.DefaultBuildContext.class); + } + + /** {@inheritDoc} */ + public Scanner newScanner(File basedir) { + return legacy.newScanner(basedir); + } + + /** {@inheritDoc} */ + public void refresh(File file) { + legacy.refresh(file); + connection.send(new RefreshMessage(file.toPath()), legacySupport.getSession()); + } + + /** {@inheritDoc} */ + public Scanner newDeleteScanner(File basedir) { + return legacy.newDeleteScanner(basedir); + } + + /** {@inheritDoc} */ + public Scanner newScanner(File basedir, boolean ignoreDelta) { + return legacy.newScanner(basedir, ignoreDelta); + } + + /** + *

isIncremental.

+ * + * @return a boolean. + */ + public boolean isIncremental() { + return legacy.isIncremental(); + } + + /** {@inheritDoc} */ + public Object getValue(String key) { + return contextMap.get(key); + } + + /** {@inheritDoc} */ + public void setValue(String key, Object value) { + contextMap.put(key, value); + } + + /** {@inheritDoc} */ + public void addError(File file, int line, int column, String message, Throwable cause) { + addMessage(file, line, column, message, SEVERITY_ERROR, cause); + } + + /** {@inheritDoc} */ + public void addWarning(File file, int line, int column, String message, Throwable cause) { + addMessage(file, line, column, message, SEVERITY_WARNING, cause); + } + + private String getMessage(File file, int line, int column, String message) { + return file.getAbsolutePath() + " [" + line + ':' + column + "]: " + message; + } + + /** {@inheritDoc} */ + public void addMessage(File file, int line, int column, String message, int severity, Throwable cause) { + if (isDefaultImplementation()) { + switch (severity) { + case BuildContext.SEVERITY_ERROR: + logger.error(getMessage(file, line, column, message), cause); + return; + case BuildContext.SEVERITY_WARNING: + logger.warn(getMessage(file, line, column, message), cause); + return; + default: + logger.debug(getMessage(file, line, column, message), cause); + return; + } + } + legacy.addMessage(file, line, column, message, severity, cause); + } + + /** {@inheritDoc} */ + public void removeMessages(File file) { + if (isDefaultImplementation()) { + return; + } + legacy.removeMessages(file); + } + + /** {@inheritDoc} */ + public boolean isUptodate(File target, File source) { + return legacy.isUptodate(target, source); + } +} diff --git a/src/main/java/org/codehaus/plexus/build/connect/BuildConnection.java b/src/main/java/org/codehaus/plexus/build/connect/BuildConnection.java new file mode 100644 index 0000000..921115e --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/connect/BuildConnection.java @@ -0,0 +1,45 @@ +/* +Copyright (c) 2025 Christoph Läubrich 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.connect; + +import org.apache.maven.execution.MavenSession; +import org.codehaus.plexus.build.connect.messages.Message; + +/** + * A {@link BuildConnection} allow communication between a an IDE and a maven + * build to observe the state of the build and act on certain events. This is + * usually not used directly by mojos but invoked internally by other APIs. + */ +public interface BuildConnection { + + /** + * Send a message and returns the reply from the other endpoint, should only be + * called from a maven thread! + * + * @param message the message to send + * @param mavenSession the maven session to reference + * @return the reply message or null if this connection is not + * enabled and the message was discarded. + */ + Message send(Message message, MavenSession mavenSession); + + /** + * This method allows code to perform an eager check if a buildconnection is + * present to send messages. This can be used to guard operations to prevent + * allocate resources or objects if the message will be dropped. + * + * @return true if the connection can be used to send messages or + * if they will be discarded + */ + boolean isEnabled(); +} diff --git a/src/main/java/org/codehaus/plexus/build/connect/Configuration.java b/src/main/java/org/codehaus/plexus/build/connect/Configuration.java new file mode 100644 index 0000000..2444145 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/connect/Configuration.java @@ -0,0 +1,48 @@ +/* +Copyright (c) 2025 Christoph Läubrich 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.connect; + +import org.codehaus.plexus.build.connect.messages.Message; + +/** + * Provides access to the configuration provided by the server + */ +public interface Configuration { + + /** + * If this property is set to true in reply to a InitMessage + */ + public static final String CONFIG_SEND_PROJECTS = "sendProjectInfos"; + + /** + * @return true if {@link #CONFIG_SEND_PROJECTS} is + * provided + */ + public boolean isSendProjects(); + + /** + * Creates a Configuration from a message + * + * @param message + * @return the configuration backed by the message payload + */ + public static Configuration of(Message message) { + return new Configuration() { + + @Override + public boolean isSendProjects() { + return message.getBooleanProperty(CONFIG_SEND_PROJECTS, false); + } + }; + } +} diff --git a/src/main/java/org/codehaus/plexus/build/connect/EventListener.java b/src/main/java/org/codehaus/plexus/build/connect/EventListener.java new file mode 100644 index 0000000..1eb5e8d --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/connect/EventListener.java @@ -0,0 +1,100 @@ +/* +Copyright (c) 2025 Christoph Läubrich 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.connect; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.apache.maven.eventspy.EventSpy; +import org.apache.maven.execution.ExecutionEvent; +import org.apache.maven.execution.ExecutionEvent.Type; +import org.apache.maven.execution.MavenSession; +import org.codehaus.plexus.build.connect.messages.InitMessage; +import org.codehaus.plexus.build.connect.messages.Message; +import org.codehaus.plexus.build.connect.messages.MojoMessage; +import org.codehaus.plexus.build.connect.messages.ProjectMessage; +import org.codehaus.plexus.build.connect.messages.ProjectsMessage; +import org.codehaus.plexus.build.connect.messages.SessionMessage; + +/** + * Listen to all maven events and forward them to the endpoint + */ +@Named +@Singleton +public class EventListener implements EventSpy { + + private BuildConnection connection; + private Configuration configuration; + + /** + * Creates endpoint for the given connection + * + * @param connection injected + */ + @Inject + public EventListener(BuildConnection connection) { + this.connection = connection; + } + + @Override + public void init(Context context) throws Exception { + Message message = connection.send(new InitMessage(context), null); + if (message != null) { + configuration = Configuration.of(message); + } + } + + @Override + public void onEvent(Object event) throws Exception { + if (configuration == null) { + return; + } + if (event instanceof ExecutionEvent) { + handleExecutionEvent((ExecutionEvent) event); + } + } + + private void handleExecutionEvent(ExecutionEvent event) { + MavenSession session = event.getSession(); + Type type = event.getType(); + switch (type) { + case SessionStarted: + connection.send(new SessionMessage(session, true), session); + if (configuration.isSendProjects()) { + connection.send(new ProjectsMessage(session.getProjects()), session); + } + break; + case SessionEnded: + connection.send(new SessionMessage(session, false), session); + break; + case ProjectStarted: + case ProjectFailed: + case ProjectSkipped: + case ProjectSucceeded: + connection.send(new ProjectMessage(event.getProject(), type), session); + break; + case MojoStarted: + case MojoFailed: + case MojoSkipped: + case MojoSucceeded: + connection.send(new MojoMessage(event.getMojoExecution(), type), session); + break; + default: + break; + } + } + + @Override + public void close() throws Exception {} +} diff --git a/src/main/java/org/codehaus/plexus/build/connect/TcpBuildConnection.java b/src/main/java/org/codehaus/plexus/build/connect/TcpBuildConnection.java new file mode 100644 index 0000000..339487e --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/connect/TcpBuildConnection.java @@ -0,0 +1,253 @@ +/* +Copyright (c) 2025 Christoph Läubrich 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.connect; + +import javax.inject.Named; +import javax.inject.Singleton; + +import java.io.Closeable; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.WeakHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import org.apache.maven.execution.MavenSession; +import org.codehaus.plexus.build.connect.messages.Message; + +/** + * Default implementation using the system property + * plexus.build.ipc.port to communicate with an endpoint to + * exchange messages + */ +@Named("default") +@Singleton +public class TcpBuildConnection implements BuildConnection { + private static final String PLEXUS_BUILD_IPC_PORT = "plexus.build.ipc.port"; + + private static final int PORT = Integer.getInteger(PLEXUS_BUILD_IPC_PORT, 0); + + private final Map sessionMap = new WeakHashMap<>(); + + private final ThreadLocal connections = + ThreadLocal.withInitial(() -> new TcpClientConnection()); + + @Override + public boolean isEnabled() { + return PORT > 0; + } + + @Override + public Message send(Message message, MavenSession mavenSession) { + if (isEnabled()) { + String sessionId = getId(mavenSession); + byte[] messageBytes = message.serialize(sessionId); + byte[] replyBytes = connections.get().send(messageBytes); + if (replyBytes.length > 0) { + return Message.decode(replyBytes); + } + } + return null; + } + + private synchronized String getId(MavenSession session) { + if (session == null) { + return Thread.currentThread().getName(); + } + return sessionMap.computeIfAbsent(session, x -> UUID.randomUUID().toString()); + } + + /** + * Creates a new server that will receive messages from a remote endpoint and + * inform the consumer + * + * @param consumer the consumer of messages, might be called by different + * threads, if the consumer throws an exception while handling a + * message it will maybe no longer receive some messages. The + * returned map is used as a payload for the reply to the + * server, if null is returned a simple + * acknowledgement without any payload will be send to the + * endpoint. If the consumer performs blocking operations the + * further execution of the maven process might be halted + * depending on the message type, if that is not desired work + * should be offloaded by the consumer to a different thread. + * @return a {@link ServerConnection} that can be used to shutdown the server + * and get properties that needs to be passed to the maven process + * @throws IOException if no local socket can be opened + */ + public static ServerConnection createServer(Function> consumer) throws IOException { + return new ServerConnection(new ServerSocket(0), consumer); + } + + /** + * Represents a server connection that must be created to communicate with the + * maven process using the {@link TcpBuildConnection} + */ + public static final class ServerConnection implements AutoCloseable { + + private ServerSocket socket; + private ExecutorService executor = Executors.newCachedThreadPool(); + private List connections = new ArrayList<>(); + + ServerConnection(ServerSocket socket, Function> consumer) { + this.socket = socket; + executor.execute(() -> { + while (!Thread.currentThread().isInterrupted()) { + try { + TcpServerConnection connection = new TcpServerConnection(socket.accept(), consumer); + connections.add(connection); + executor.execute(connection); + } catch (IOException e) { + return; + } + } + }); + } + + @Override + public void close() { + executor.shutdownNow(); + for (TcpServerConnection connection : connections) { + connection.close(); + } + try { + socket.close(); + } catch (IOException e) { + } + } + + /** + * Given a consumer publishes required properties for a process to launch + * + * @param consumer the consumer for system properties + */ + public void setupProcess(BiConsumer consumer) { + // currently only one but might become more later (e.g. timeout, reconnects, + // ...) + consumer.accept(PLEXUS_BUILD_IPC_PORT, Integer.toString(socket.getLocalPort())); + } + } + + private static final class TcpServerConnection implements Runnable, Closeable { + + private Socket socket; + private Function> consumer; + private DataInputStream in; + private DataOutputStream out; + private AtomicBoolean closed = new AtomicBoolean(); + + public TcpServerConnection(Socket socket, Function> consumer) throws IOException { + this.socket = socket; + this.consumer = consumer; + in = new DataInputStream(socket.getInputStream()); + out = new DataOutputStream(socket.getOutputStream()); + } + + @Override + public void run() { + try { + while (!closed.get() && !Thread.currentThread().isInterrupted()) { + try { + int length = in.readInt(); + if (length == 0) { + return; + } + byte[] bytes = new byte[length]; + in.readFully(bytes); + Message message = Message.decode(bytes); + Map payload = consumer.apply(message); + Message reply = Message.replyTo(message, payload); + byte[] responseBytes = reply.serialize(); + synchronized (out) { + out.writeInt(responseBytes.length); + out.write(responseBytes); + out.flush(); + } + } catch (Exception e) { + return; + } + } + } finally { + close(); + } + } + + @Override + public void close() { + if (closed.compareAndSet(false, true)) { + try { + synchronized (out) { + out.writeInt(0); + out.flush(); + } + } catch (IOException e) { + } + try { + socket.close(); + } catch (IOException e) { + } + } + } + } + + private static final class TcpClientConnection { + + private Socket socket; + private boolean closed; + private DataInputStream in; + private DataOutputStream out; + + public byte[] send(byte[] messageBytes) { + if (!closed) { + try { + if (socket == null) { + socket = new Socket("localhost", PORT); + in = new DataInputStream(socket.getInputStream()); + out = new DataOutputStream(socket.getOutputStream()); + } + out.writeInt(messageBytes.length); + out.write(messageBytes); + out.flush(); + int length = in.readInt(); + if (length == 0) { + socket.close(); + closed = true; + } else { + byte[] bytes = new byte[length]; + in.readFully(bytes); + return bytes; + } + } catch (IOException e) { + closed = true; + if (socket != null) { + try { + socket.close(); + } catch (IOException e1) { + } + } + } + } + return new byte[0]; + } + } +} diff --git a/src/main/java/org/codehaus/plexus/build/connect/messages/InitMessage.java b/src/main/java/org/codehaus/plexus/build/connect/messages/InitMessage.java new file mode 100644 index 0000000..e8cabcb --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/connect/messages/InitMessage.java @@ -0,0 +1,54 @@ +/* +Copyright (c) 2025 Christoph Läubrich 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.connect.messages; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; + +import org.apache.maven.eventspy.EventSpy.Context; + +/** + * Message send to init the inital communication with the endpoints + */ +public class InitMessage extends Message { + + /** + * Creates a message with inital information about the running maven system + * + * @param context the context settings to send to the endpoint + */ + public InitMessage(Context context) { + super(toMap(context)); + } + + private static Map toMap(Context context) { + Map data = new LinkedHashMap<>(); + context.getData().forEach((k, v) -> { + if (v instanceof String) { + data.put(k, (String) v); + } + if (v instanceof Properties) { + Properties properties = (Properties) v; + for (String p : properties.stringPropertyNames()) { + data.put(k + "." + p, properties.getProperty(p)); + } + } + }); + return data; + } + + InitMessage(String sessionId, long threadId, Map payload) { + super(sessionId, threadId, payload); + } +} diff --git a/src/main/java/org/codehaus/plexus/build/connect/messages/Message.java b/src/main/java/org/codehaus/plexus/build/connect/messages/Message.java new file mode 100644 index 0000000..ddc1430 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/connect/messages/Message.java @@ -0,0 +1,265 @@ +/* +Copyright (c) 2025 Christoph Läubrich 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.connect.messages; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; + +/** + * A message exchanged between two endpoints, usually an IDE and a maven build + */ +public class Message { + private static final ThreadLocal ID = new ThreadLocal() { + private final AtomicLong generator = new AtomicLong(); + + @Override + protected Long initialValue() { + return generator.getAndIncrement(); + } + }; + private final long threadId; + private final Map properties; + private final String sessionId; + + Message(Map payload) { + this(null, ID.get(), payload); + } + + Message(String sessionId, long threadId, Map payload) { + this.sessionId = sessionId; + this.properties = Objects.requireNonNull(payload); + this.threadId = threadId; + } + + /** + * @return the keys stored in this message + */ + public Stream keys() { + return properties.keySet().stream(); + } + + /** + * Get a String property from the payload + * + * @param key the key to fetch + * @return the value + */ + public String getProperty(String key) { + return properties.get(key); + } + + /** + * Get a String property from the payload + * + * @param key the key to fetch + * @param defaultValue default value to use when no value is present + * @return the value + */ + public String getProperty(String key, String defaultValue) { + return properties.getOrDefault(key, defaultValue); + } + + /** + * Get a boolean property from the payload + * + * @param key the key to fetch + * @return the value + */ + public boolean getBooleanProperty(String key) { + return Boolean.parseBoolean(properties.get(key)); + } + + /** + * Get a boolean property from the payload + * + * @param key the key to fetch + * @param defaultValue the value to use if not value is present + * @return the value + */ + public boolean getBooleanProperty(String key, boolean defaultValue) { + String property = getProperty(key); + if (property == null) { + return defaultValue; + } + return Boolean.parseBoolean(property); + } + + /** + * @return the remote session id for this message, only valid for messages not + * created locally + */ + public String getSessionId() { + if (sessionId == null) { + throw new IllegalStateException("can not be called on a local message!"); + } + return sessionId; + } + + /** + * @return the bytes using the message session id + */ + public byte[] serialize() { + return serialize(getSessionId()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [" + sessionId + "][" + threadId + "] " + properties; + } + + /** + * Creates bytes for this message using the session id + * + * @param sessionId + * @return the bytes using the supplied message id + */ + public byte[] serialize(String sessionId) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(stream); + try { + writeString(sessionId, out); + out.writeLong(threadId); + writeString(getClass().getSimpleName(), out); + if (properties.isEmpty()) { + out.writeInt(0); + } else { + Set> set = properties.entrySet(); + out.writeInt(set.size()); + for (Entry entry : set) { + writeString(entry.getKey(), out); + writeString(entry.getValue(), out); + } + } + } catch (IOException e) { + // should never happen, but if it happens something is wrong! + throw new RuntimeException("Internal Error: Write data failed", e); + } + return stream.toByteArray(); + } + + /** + * Creates a reply to a message using the thread id and session id from the + * original but with the provided payload + * + * @param message the reply message to inherit from + * @param payload the new payload + * @return the message + */ + public static Message replyTo(Message message, Map payload) { + if (payload == null) { + payload = Collections.emptyMap(); + } + return new Message(message.sessionId, message.threadId, payload); + } + + /** + * Decodes a message from its bytes + * + * @param bytes the bytes to decode + * @return the message or null if decoding failed + */ + public static Message decode(byte[] bytes) { + ByteArrayInputStream stream = new ByteArrayInputStream(bytes); + DataInputStream in = new DataInputStream(stream); + try { + String sessionId = readString(in); + long threadId = in.readLong(); + String messageType = readString(in); + int size = in.readInt(); + Map payload = new LinkedHashMap<>(size); + for (int i = 0; i < size; i++) { + payload.put(readString(in), readString(in)); + } + if ("SessionMessage".equals(messageType)) { + return new SessionMessage(sessionId, threadId, payload); + } + if ("ProjectsMessage".equals(messageType)) { + return new ProjectsMessage(sessionId, threadId, payload); + } + if ("RefreshMessage".equals(messageType)) { + return new RefreshMessage(sessionId, threadId, payload); + } + if ("InitMessage".equals(messageType)) { + return new InitMessage(sessionId, threadId, payload); + } + if ("ProjectMessage".equals(messageType)) { + return new ProjectMessage(sessionId, threadId, payload); + } + if ("MojoMessage".equals(messageType)) { + return new MojoMessage(sessionId, threadId, payload); + } + return new Message(sessionId, threadId, payload); + } catch (IOException e) { + // should never happen, but if it happens something is wrong! + System.err.println("Internal Error: Message decoding failed: " + e); + } + return null; + } + + private static String readString(DataInputStream in) throws IOException { + int length = in.readInt(); + if (length < 0) { + return null; + } + if (length == 0) { + return ""; + } + byte[] bs = new byte[length]; + in.readFully(bs); + return new String(bs, StandardCharsets.UTF_8); + } + + private static void writeString(String string, DataOutputStream stream) throws IOException { + if (string == null) { + stream.writeInt(-1); + } else { + byte[] bytes = string.getBytes(StandardCharsets.UTF_8); + stream.writeInt(bytes.length); + stream.write(bytes); + } + } + + @Override + public int hashCode() { + return Objects.hash(properties, sessionId, threadId); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Message other = (Message) obj; + return Objects.equals(properties, other.properties) + && Objects.equals(sessionId, other.sessionId) + && threadId == other.threadId; + } +} diff --git a/src/main/java/org/codehaus/plexus/build/connect/messages/MojoMessage.java b/src/main/java/org/codehaus/plexus/build/connect/messages/MojoMessage.java new file mode 100644 index 0000000..e1beb34 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/connect/messages/MojoMessage.java @@ -0,0 +1,138 @@ +/* +Copyright (c) 2025 Christoph Läubrich 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.connect.messages; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.maven.execution.ExecutionEvent.Type; +import org.apache.maven.plugin.MojoExecution; + +/** + * Mesaage generated when a mojo is executed + */ +public class MojoMessage extends Message { + + private static final String GOAL = "goal"; + private static final String LIFECYCLE_PHASE = "lifecyclePhase"; + private static final String EXECUTION_ID = "executionId"; + private static final String VERSION = "version"; + private static final String ARTIFACT_ID = "artifactId"; + private static final String GROUP_ID = "groupId"; + private static final String EVENT_TYPE = "eventType"; + + MojoMessage(String sessionId, long threadId, Map payload) { + super(sessionId, threadId, payload); + } + + /** + * Creates a Mojo message from execution and type + * + * @param mojoExecution + * @param type + */ + public MojoMessage(MojoExecution mojoExecution, Type type) { + super(toMap(mojoExecution, type)); + } + + /** + * @return the group id + */ + public String getGroupId() { + return getProperty(GROUP_ID); + } + + /** + * @return the artifact id + */ + public String getArtifactId() { + return getProperty(ARTIFACT_ID); + } + + /** + * @return the version + */ + public String getVersion() { + return getProperty(VERSION); + } + + /** + * @return the execution id + */ + public String getExecutionId() { + return getProperty(EXECUTION_ID); + } + + /** + * @return the lifecycle phase + */ + public String getLifecyclePhase() { + return getProperty(LIFECYCLE_PHASE); + } + + /** + * @return the lifecycle phase + */ + public String getGoal() { + return getProperty(GOAL); + } + + /** + * @return the type of event + */ + public EventType getType() { + try { + return EventType.valueOf(getProperty(EVENT_TYPE)); + } catch (RuntimeException e) { + return EventType.Unknown; + } + } + + private static Map toMap(MojoExecution mojoExecution, Type type) { + Map map = new HashMap<>(); + map.put(EVENT_TYPE, type.name()); + map.put(GROUP_ID, mojoExecution.getGroupId()); + map.put(ARTIFACT_ID, mojoExecution.getArtifactId()); + map.put(VERSION, mojoExecution.getVersion()); + map.put(EXECUTION_ID, mojoExecution.getExecutionId()); + map.put(LIFECYCLE_PHASE, mojoExecution.getLifecyclePhase()); + map.put(GOAL, mojoExecution.getGoal()); + return map; + } + + /** + * create the event type + */ + public static enum EventType { + /** + * the mojo was started + */ + MojoStarted, + /** + * The mojo failed + */ + MojoFailed, + /** + * The mojo was skipped + */ + MojoSkipped, + /** + * The mojo succeed + */ + MojoSucceeded, + /** + * the type is unknown + */ + Unknown; + } +} diff --git a/src/main/java/org/codehaus/plexus/build/connect/messages/ProjectMessage.java b/src/main/java/org/codehaus/plexus/build/connect/messages/ProjectMessage.java new file mode 100644 index 0000000..0ec2ef1 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/connect/messages/ProjectMessage.java @@ -0,0 +1,122 @@ +/* +Copyright (c) 2025 Christoph Läubrich 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.connect.messages; + +import java.io.File; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import org.apache.maven.execution.ExecutionEvent.Type; +import org.apache.maven.project.MavenProject; + +/** + * Send to inform about project changes + */ +public class ProjectMessage extends Message { + + private static final String BASE_DIR = "baseDir"; + private static final String VERSION = "version"; + private static final String ARTIFACT_ID = "artifactId"; + private static final String GROUP_ID = "groupId"; + private static final String EVENT_TYPE = "eventType"; + + ProjectMessage(String sessionId, long threadId, Map payload) { + super(sessionId, threadId, payload); + } + + /** + * Constructs a new event based on project and type + * + * @param mavenProject + * @param eventtype + */ + public ProjectMessage(MavenProject mavenProject, Type eventtype) { + super(toMap(mavenProject, eventtype)); + } + + /** + * @return the group id of the project + */ + public String getGroupId() { + return getProperty(GROUP_ID); + } + + /** + * @return the artifact id of the project + */ + public String getArtifactId() { + return getProperty(ARTIFACT_ID); + } + + /** + * @return the version of the project + */ + public String getVersion() { + return getProperty(VERSION); + } + + /** + * @return the basedir of the project + */ + public Path getBaseDir() { + return new File(getProperty(BASE_DIR)).toPath(); + } + + /** + * @return the type of the event + */ + public EventType getType() { + try { + return EventType.valueOf(getProperty(EVENT_TYPE)); + } catch (RuntimeException e) { + return EventType.Unknown; + } + } + + private static Map toMap(MavenProject mavenProject, Type eventtype) { + Map map = new HashMap<>(); + map.put(EVENT_TYPE, eventtype.name()); + map.put(GROUP_ID, mavenProject.getGroupId()); + map.put(ARTIFACT_ID, mavenProject.getArtifactId()); + map.put(VERSION, mavenProject.getVersion()); + map.put(BASE_DIR, mavenProject.getBasedir().getAbsolutePath()); + return map; + } + + /** + * Describe the type of the event + */ + public static enum EventType { + /** + * The project was started + */ + ProjectStarted, + /** + * The project failed + */ + ProjectFailed, + /** + * The project was skipped + */ + ProjectSkipped, + /** + * the project succeed + */ + ProjectSucceeded, + /** + * the type of event is unknown + */ + Unknown; + } +} diff --git a/src/main/java/org/codehaus/plexus/build/connect/messages/ProjectsMessage.java b/src/main/java/org/codehaus/plexus/build/connect/messages/ProjectsMessage.java new file mode 100644 index 0000000..93bfa5a --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/connect/messages/ProjectsMessage.java @@ -0,0 +1,143 @@ +/* +Copyright (c) 2025 Christoph Läubrich 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.connect.messages; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.file.Path; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import org.apache.maven.model.Model; +import org.apache.maven.model.io.DefaultModelWriter; +import org.apache.maven.project.MavenProject; + +/** + * Message send to inform about reactor project in the build and their effective + * model + */ +public class ProjectsMessage extends Message { + + private static final DefaultModelWriter MODEL_WRITER = new DefaultModelWriter(); + + ProjectsMessage(String sessionId, long threadId, Map payload) { + super(sessionId, threadId, payload); + } + + /** + * @param projects the projects to send + */ + public ProjectsMessage(Collection projects) { + super(buildMap(projects)); + } + + /** + * @return a stream of project infos + */ + public Stream projects() { + return keys().map(key -> { + String[] gav = key.split("\t"); + if (gav.length == 5 && "p".equals(gav[0])) { + ProjectInfo pi = new ProjectInfo() { + + @Override + public String getGroupId() { + return gav[1]; + } + + @Override + public String getArtifactId() { + return gav[2]; + } + + @Override + public String getVersion() { + return gav[3]; + } + + @Override + public String getModel() { + return getProperty(key); + } + + @Override + public Path getBaseDir() { + return new File(gav[4]).toPath(); + } + }; + return pi; + } + return null; + }) + .filter(Objects::nonNull); + } + + private static Map buildMap(Collection projects) { + Map map = new HashMap<>(); + for (MavenProject project : projects) { + String key = String.format( + "p\t%s\t%s\t%s\t%s", + project.getGroupId(), + project.getArtifactId(), + project.getVersion(), + project.getBasedir().getAbsolutePath()); + map.put(key, getEffectiveModel(project)); + } + return map; + } + + private static String getEffectiveModel(MavenProject project) { + Model model = project.getModel(); + StringWriter writer = new StringWriter(); + try { + MODEL_WRITER.write(writer, null, model); + } catch (IOException e) { + } + String string = writer.toString(); + return string; + } + + /** + * Holds basic project info + */ + public static interface ProjectInfo { + /** + * @return the group id of the reactor project + */ + String getGroupId(); + + /** + * @return the artifact id of the reactor project + */ + String getArtifactId(); + + /** + * @return the version of the reactor project + */ + String getVersion(); + + /** + * @return the effective model of the project + */ + String getModel(); + + /** + * @return the basedir of the project + */ + Path getBaseDir(); + } +} diff --git a/src/main/java/org/codehaus/plexus/build/connect/messages/RefreshMessage.java b/src/main/java/org/codehaus/plexus/build/connect/messages/RefreshMessage.java new file mode 100644 index 0000000..a473a02 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/connect/messages/RefreshMessage.java @@ -0,0 +1,47 @@ +/* +Copyright (c) 2025 Christoph Läubrich 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.connect.messages; + +import java.io.File; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; + +/** + * A message that indicates a path should be refreshed (e.g. because new files + * are placed in a generated folder) + */ +public class RefreshMessage extends Message { + + private static final String PATH_KEY = "path"; + + /** + * Create a new message to refresh a path + * + * @param path the path to refresh + */ + public RefreshMessage(Path path) { + super(Collections.singletonMap(PATH_KEY, path.toFile().getAbsolutePath())); + } + + /** + * @return the path to refresh + */ + public Path getPath() { + return new File(getProperty(PATH_KEY)).toPath(); + } + + RefreshMessage(String sessionId, long threadId, Map payload) { + super(sessionId, threadId, payload); + } +} diff --git a/src/main/java/org/codehaus/plexus/build/connect/messages/SessionMessage.java b/src/main/java/org/codehaus/plexus/build/connect/messages/SessionMessage.java new file mode 100644 index 0000000..2f9e0a6 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/connect/messages/SessionMessage.java @@ -0,0 +1,63 @@ +/* +Copyright (c) 2025 Christoph Läubrich 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.connect.messages; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.maven.execution.MavenSession; + +/** + * Event that is received / send when a session starts/end + */ +public class SessionMessage extends Message { + + private static final String SESSION_EXECUTION_ROOT_DIRECTORY = "sessionExecutionRootDirectory"; + private static final String SESSION_START = "sessionStart"; + + /** + * Creates a new session message + * + * @param session the session to use + * @param start true if it is a start of the session or + * false if it is the end of a session + */ + public SessionMessage(MavenSession session, boolean start) { + super(buildMap(session, start)); + } + + SessionMessage(String sessionId, long threadId, Map payload) { + super(sessionId, threadId, payload); + } + + /** + * @return true if this is a session start event + */ + public boolean isSessionStart() { + return getBooleanProperty(SESSION_START); + } + + /** + * @return the value of the ExecutionRootDirectory of this session + */ + public String getExecutionRootDirectory() { + return getProperty(SESSION_EXECUTION_ROOT_DIRECTORY); + } + + private static Map buildMap(MavenSession session, boolean start) { + Map map = new HashMap<>(2); + map.put(SESSION_START, Boolean.toString(start)); + map.put(SESSION_EXECUTION_ROOT_DIRECTORY, session.getExecutionRootDirectory()); + return map; + } +} diff --git a/src/main/java/org/codehaus/plexus/build/messages/DefaultMessages.java b/src/main/java/org/codehaus/plexus/build/messages/DefaultMessages.java new file mode 100644 index 0000000..b10d36f --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/messages/DefaultMessages.java @@ -0,0 +1,134 @@ +/* +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.messages; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import java.nio.file.Path; + +import org.codehaus.plexus.build.BuildContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Default implementation of the Messages interface. + *

+ * This implementation delegates to the BuildContext for compatibility with existing + * message handling infrastructure. It logs messages and calls the legacy BuildContext + * message API. + *

+ */ +@Named("default") +@Singleton +public class DefaultMessages implements Messages { + + private static final Logger logger = LoggerFactory.getLogger(DefaultMessages.class); + + private final BuildContext buildContext; + + /** + * Creates a new DefaultMessages instance. + * + * @param buildContext the BuildContext to which messages will be delegated + */ + @Inject + public DefaultMessages(BuildContext buildContext) { + this.buildContext = buildContext; + } + + @Override + public void clearAll() { + // This is a no-op in the default implementation + // Custom implementations may provide actual clearing functionality + } + + @Override + public void clear(Path path) { + if (path != null) { + buildContext.removeMessages(path.toFile()); + } + } + + @Override + public MessageBuilder error(Path path) { + return build(MessageType.ERROR, path); + } + + @Override + public MessageBuilder warning(Path path) { + return build(MessageType.WARNING, path); + } + + @Override + public MessageBuilder info(Path path) { + return build(MessageType.INFO, path); + } + + @Override + public MessageBuilder build(MessageType type, Path path) { + return new MessageBuilder(type, path, this::handleMessage); + } + + /** + * Handles a message by logging it and delegating to the BuildContext. + * + * @param message the message to handle + */ + private void handleMessage(Message message) { + // Log the message + String logMessage = message.toString(); + + switch (message.getType()) { + case ERROR: + logger.error(logMessage, message.getCause()); + break; + case WARNING: + logger.warn(logMessage, message.getCause()); + break; + case INFO: + logger.info(logMessage, message.getCause()); + break; + } + + // Delegate to BuildContext for compatibility + if (message.getPath() != null) { + int severity = mapTypeToSeverity(message.getType()); + buildContext.addMessage( + message.getPath().toFile(), + message.getLine(), + message.getColumn(), + message.getMessage(), + severity, + message.getCause()); + } + } + + /** + * Maps a MessageType to a BuildContext severity level. + * + * @param type the message type + * @return the corresponding BuildContext severity + */ + private int mapTypeToSeverity(MessageType type) { + switch (type) { + case ERROR: + return BuildContext.SEVERITY_ERROR; + case WARNING: + return BuildContext.SEVERITY_WARNING; + case INFO: + default: + // BuildContext supports 0 as an info severity level (undocumented) + return 0; + } + } +} diff --git a/src/main/java/org/codehaus/plexus/build/messages/Message.java b/src/main/java/org/codehaus/plexus/build/messages/Message.java new file mode 100644 index 0000000..3709b27 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/messages/Message.java @@ -0,0 +1,126 @@ +/* +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.messages; + +import java.nio.file.Path; + +/** + * Represents a message with all its parameters. + * This class holds the collected parameters for a message that can be created through the MessageBuilder. + */ +public class Message { + private final MessageType type; + private final Path path; + private final int line; + private final int column; + private final String message; + private final Throwable cause; + + /** + * Creates a new message with the specified parameters. + * + * @param type the message type + * @param path the file path associated with this message + * @param line the line number (1-based, 0 for unknown) + * @param column the column number (1-based, 0 for unknown) + * @param message the message text + * @param cause the exception cause, can be null + */ + Message(MessageType type, Path path, int line, int column, String message, Throwable cause) { + this.type = type; + this.path = path; + this.line = line; + this.column = column; + this.message = message; + this.cause = cause; + } + + /** + * @return the message type + */ + public MessageType getType() { + return type; + } + + /** + * @return the file path + */ + public Path getPath() { + return path; + } + + /** + * @return the line number (1-based, 0 for unknown) + */ + public int getLine() { + return line; + } + + /** + * @return the column number (1-based, 0 for unknown) + */ + public int getColumn() { + return column; + } + + /** + * @return the message text + */ + public String getMessage() { + return message; + } + + /** + * @return the exception cause, or null if none + */ + public Throwable getCause() { + return cause; + } + + /** + * Returns a string representation of this message. + * The format is: path [line:column]: message + * + * @return the formatted message string + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + if (path != null) { + sb.append(path.toAbsolutePath()); + } + + if (line > 0 && column > 0) { + sb.append(" ["); + sb.append(line); + sb.append(':').append(column); + sb.append("]"); + } else if (line > 0) { + sb.append(" ["); + sb.append(line); + sb.append("]"); + } else if (column > 0) { + sb.append(" [:"); + sb.append(column); + sb.append("]"); + } + + if (message != null) { + if (sb.length() > 0) { + sb.append(": "); + } + sb.append(message); + } + + return sb.toString(); + } +} diff --git a/src/main/java/org/codehaus/plexus/build/messages/MessageBuilder.java b/src/main/java/org/codehaus/plexus/build/messages/MessageBuilder.java new file mode 100644 index 0000000..55c8491 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/messages/MessageBuilder.java @@ -0,0 +1,96 @@ +/* +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.messages; + +import java.nio.file.Path; +import java.util.function.Consumer; + +/** + * Builder class for constructing messages. + *

+ * This class implements the builder pattern for creating messages with various parameters. + * It is typically not called directly by client code, but is used internally by the Messages API + * implementations. + *

+ */ +public class MessageBuilder { + private final MessageType type; + private final Path path; + private final Consumer consumer; + + private int line = 0; + private int column = 0; + private Throwable cause; + + /** + * Creates a new MessageBuilder. + *

+ * Note: This constructor is usually not called by client code. Use the builder methods + * provided by the {@link Messages} interface instead (e.g., buildError, buildWarning, buildInfo). + *

+ * + * @param type the type of message to build + * @param path the file path for which the message should be created + * @param consumer the consumer that will receive the constructed message + */ + public MessageBuilder(MessageType type, Path path, Consumer consumer) { + this.type = type; + this.path = path; + this.consumer = consumer; + } + + /** + * Sets the line number for the message. + * + * @param line the line number (1-based, use 0 for unknown) + * @return this builder for method chaining + */ + public MessageBuilder line(int line) { + this.line = line; + return this; + } + + /** + * Sets the column number for the message. + * + * @param column the column number (1-based, use 0 for unknown) + * @return this builder for method chaining + */ + public MessageBuilder column(int column) { + this.column = column; + return this; + } + + /** + * Sets the exception cause for the message. + * + * @param cause the exception that caused this message + * @return this builder for method chaining + */ + public MessageBuilder cause(Throwable cause) { + this.cause = cause; + return this; + } + + /** + * Creates the message object with all collected parameters and informs the consumer. + * This method finalizes the builder and creates the message. + * + * @param message the message text (must not be null or blank) + */ + public void create(String message) { + if (message == null || message.trim().isEmpty()) { + throw new IllegalArgumentException("Message text must not be null or blank"); + } + Message msg = new Message(type, path, line, column, message, cause); + consumer.accept(msg); + } +} diff --git a/src/main/java/org/codehaus/plexus/build/messages/MessageType.java b/src/main/java/org/codehaus/plexus/build/messages/MessageType.java new file mode 100644 index 0000000..69a859c --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/messages/MessageType.java @@ -0,0 +1,31 @@ +/* +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.messages; + +/** + * Enum representing the type/severity of a message. + */ +public enum MessageType { + /** + * Informational message + */ + INFO, + + /** + * Warning message + */ + WARNING, + + /** + * Error message + */ + ERROR +} diff --git a/src/main/java/org/codehaus/plexus/build/messages/Messages.java b/src/main/java/org/codehaus/plexus/build/messages/Messages.java new file mode 100644 index 0000000..52030d2 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/messages/Messages.java @@ -0,0 +1,81 @@ +/* +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.messages; + +import java.nio.file.Path; + +/** + *

Messages interface.

+ *

+ * This 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. + *

+ *

+ * Example usage: + *

+ *
+ * messages.error(Paths.get("/path/to/file.java"))
+ *     .line(42)
+ *     .column(10)
+ *     .create("Syntax error");
+ * 
+ */ +public interface Messages { + + /** + * Clears all messages. + * This removes all messages that were previously created through this API. + */ + void clearAll(); + + /** + * Clears messages associated with a specific path. + * + * @param path the file path for which to clear messages + */ + void clear(Path path); + + /** + * Creates a builder for an error message. + * + * @param path the file path for which the error message should be created + * @return a MessageBuilder for constructing the error message + */ + MessageBuilder error(Path path); + + /** + * Creates a builder for a warning message. + * + * @param path the file path for which the warning message should be created + * @return a MessageBuilder for constructing the warning message + */ + MessageBuilder warning(Path path); + + /** + * Creates a builder for an informational message. + * + * @param path the file path for which the info message should be created + * @return a MessageBuilder for constructing the info message + */ + MessageBuilder info(Path path); + + /** + * Creates a builder for a message of a specific type. + * This is the generic method that the other build methods delegate to. + * + * @param type the type of message to build + * @param path the file path for which the message should be created + * @return a MessageBuilder for constructing the message + */ + MessageBuilder build(MessageType type, Path path); +} diff --git a/src/main/java/org/codehaus/plexus/build/progress/DefaultProgress.java b/src/main/java/org/codehaus/plexus/build/progress/DefaultProgress.java new file mode 100644 index 0000000..e912c3f --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/progress/DefaultProgress.java @@ -0,0 +1,64 @@ +package org.codehaus.plexus.build.progress; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.apache.maven.execution.scope.MojoExecutionScoped; +import org.apache.maven.plugin.MojoExecution; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The default implementation simply log to debug and check for thread + * interruption + */ +@Named("default") +@Singleton +@MojoExecutionScoped +public class DefaultProgress implements Progress { + + private final Logger logger = LoggerFactory.getLogger(DefaultProgress.class); + private MojoExecution execution; + private int work; + + @Inject + public DefaultProgress(MojoExecution execution) { + this.execution = execution; + } + + @Override + public void startTask(String task, int work) { + setRemaining(work); + logger.debug(execution.getExecutionId() + ": " + task); + } + + @Override + public void worked(int work) { + if (work == 0) { + return; + } + if (work < 0) { + logger.warn(execution.getExecutionId() + " reported negative amount of work!"); + } + if (this.work < 0) { + return; + } + if (work > this.work) { + this.work = -1; + logger.warn(execution.getExecutionId() + " reported more work than expected!"); + } else { + this.work -= work; + } + } + + @Override + public void setRemaining(int work) { + this.work = work <= 0 ? -1 : work; + } + + @Override + public boolean isCancelRequested() { + return Thread.currentThread().isInterrupted(); + } +} diff --git a/src/main/java/org/codehaus/plexus/build/progress/Progress.java b/src/main/java/org/codehaus/plexus/build/progress/Progress.java new file mode 100644 index 0000000..27c3427 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/build/progress/Progress.java @@ -0,0 +1,48 @@ +package org.codehaus.plexus.build.progress; + +/** + * The {@link Progress} allows a mojo to report its current state and check if + * the user has requested cancellation. + */ +public interface Progress { + + /** + * Reports that the mojo has started the given task with the given amount of + * work. + * + * @param task + * @param work the amount of work that the mojo plan to report, if a value + * <= 0 is given the amount of work is assumed to be + * unknown. + */ + void startTask(String task, int work); + + /** + * Reports a given amount of work was processed + * + * @param work + */ + void worked(int work); + + /** + * Reports one unit of work to be processed + */ + default void worked() { + worked(1); + } + + /** + * Notifies the remaining amount of work that will be reported + * + * @param work + */ + void setRemaining(int work); + + /** + * This method should be used to check if the user has requested to finish the + * current work and break out early. + * + * @return true if a cancel request is currently pending. + */ + boolean isCancelRequested(); +} diff --git a/src/main/java/org/sonatype/plexus/build/incremental/BuildContext.java b/src/main/java/org/sonatype/plexus/build/incremental/BuildContext.java deleted file mode 100644 index dc8ae5d..0000000 --- a/src/main/java/org/sonatype/plexus/build/incremental/BuildContext.java +++ /dev/null @@ -1,192 +0,0 @@ -/* -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.sonatype.plexus.build.incremental; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.util.List; - -import org.codehaus.plexus.util.Scanner; - - -// TODO should it be BuildWorkspace or something like that? -public interface BuildContext { - public static final int SEVERITY_WARNING = 1; - - public static final int SEVERITY_ERROR = 2; - - // TODO should we add File getBasedir()? - - /** - * Returns true if file or folder identified by relpath has - * changed since last build. - * - * @param relpath is path relative to build context basedir - */ - boolean hasDelta(String relpath); - - /** - * Returns true if the file has changed since last build or is not - * under basedir. - * - * @since 0.0.5 - */ - boolean hasDelta(File file); - - /** - * Returns true if any file or folder identified by relpaths has - * changed since last build. - * - * @param relpaths List are paths relative to build context basedir - */ - boolean hasDelta(List relpaths); - - /** - * Indicates that the file or folder content has been modified during the build. - * - * @see #newFileOutputStream(File) - */ - void refresh(File file); - - /** - * Returns new OutputStream that writes to the file. - * - * Files changed using OutputStream returned by this method do not need to be - * explicitly refreshed using {@link #refresh(File)}. - * - * As an optional optimisation, OutputStreams created by incremental build - * context will attempt to avoid writing to the file if file content - * has not changed. - */ - OutputStream newFileOutputStream(File file) throws IOException; - - /** - * Convenience method, fully equal to newScanner(basedir, false) - */ - Scanner newScanner(File basedir); - - /** - * Returned Scanner scans basedir for files and directories - * deleted since last build. Returns empty Scanner if basedir - * is not under this build context basedir. - */ - Scanner newDeleteScanner(File basedir); - - /** - * Returned Scanner scans files and folders under basedir. - * - * If this is an incremental build context and ignoreDelta - * is false, the scanner will only "see" files and folders with - * content changes since last build. - * - * If ignoreDelta is true, the scanner will "see" all - * files and folders. - * - * Please beware that ignoreDelta=false does NOT work reliably for operations - * that copy resources from source to target locations. Returned Scanner - * only scans changed source resources and it does not consider changed or deleted - * target resources. This results in missing or stale target resources. - * Starting with 0.5.0, recommended way to process resources is to use - * #newScanner(basedir,true) to locate all source resources and {@link #isUptodate(File, File)} - * to optimized processing of uptodate target resources. - * - * Returns empty Scanner if basedir is not under this build context basedir. - * - * @see http://jira.codehaus.org/browse/MSHARED-125 - */ - Scanner newScanner(File basedir, boolean ignoreDelta); - - /** - * Returns true if this build context is incremental. - * - * Scanners created by {@link #newScanner(File)} of an incremental build context - * will ignore files and folders that were not changed since last build. - * Additionally, {@link #newDeleteScanner(File)} will scan files and directories - * deleted since last build. - */ - boolean isIncremental(); - - /** - * Associate specified key with specified value - * in the build context. - * - * Primary (and the only) purpose of this method is to allow preservation of - * state needed for proper incremental behaviour between consecutive executions - * of the same mojo needed to. - * - * For example, maven-plugin-plugin:descriptor mojo - * can store collection of extracted MojoDescritpor during first invocation. Then - * on each consecutive execution maven-plugin-plugin:descriptor will only need - * to extract MojoDescriptors for changed files. - * - * @see #getValue(String) - */ - void setValue(String key, Object value); - - /** - * Returns value associated with key during previous mojo execution. - * - * This method always returns null for non-incremental builds - * (i.e., {@link #isIncremental()} returns false) and mojos are - * expected to fall back to full, non-incremental behaviour. - * - * @see #setValue(String, Object) - * @see #isIncremental() - */ - Object getValue(String key); - - /** - * @deprecated Use addMessage with severity=SEVERITY_ERROR instead - * @since 0.0.5 - */ - void addWarning(File file, int line, int column, String message, Throwable cause); - - /** - * @deprecated Use addMessage with severity=SEVERITY_WARNING instead - * @since 0.0.5 - */ - void addError(File file, int line, int column, String message, Throwable cause); - - /** - * Adds a message to the build context. The message is associated with a file and a location inside that file. - * - * @param file The file or folder with which the message is associated. Should not be null and it is recommended to be - * an absolute path. - * @param line The line number inside the file. Use 1 (not 0) for the first line. Use 0 for unknown/unspecified. - * @param column The column number inside the file. Use 1 (not 0) for the first column. Use 0 for unknown/unspecified. - * @param severity The severity of the message: SEVERITY_WARNING or SEVERITY_ERROR. - * @param cause A Throwable object associated with the message. Can be null. - * @since 0.0.7 - */ - void addMessage(File file, int line, int column, String message, int severity, Throwable cause); - - /** - * Removes all messages associated with a file or folder during a previous build. It does not affect the messages - * added during the current build. - * - * @since 0.0.7 - */ - void removeMessages(File file); - - /** - * Returns true, if the target file exists and is uptodate 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. - * - * @since 0.0.5 - */ - boolean isUptodate(File target, File source); -} diff --git a/src/main/java/org/sonatype/plexus/build/incremental/DefaultBuildContext.java b/src/main/java/org/sonatype/plexus/build/incremental/DefaultBuildContext.java deleted file mode 100644 index f82cfd9..0000000 --- a/src/main/java/org/sonatype/plexus/build/incremental/DefaultBuildContext.java +++ /dev/null @@ -1,126 +0,0 @@ -/* -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.sonatype.plexus.build.incremental; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.List; - -import org.codehaus.plexus.logging.AbstractLogEnabled; -import org.codehaus.plexus.util.DirectoryScanner; -import org.codehaus.plexus.util.Scanner; - -/** - * Filesystem based non-incremental build context implementation which behaves as if all files - * were just created. More specifically, - * - * hasDelta returns true for all paths - * newScanner returns Scanner that scans all files under provided basedir - * newDeletedScanner always returns empty scanner. - * isIncremental returns falsenull - * - * @plexus.component role="org.sonatype.plexus.build.incremental.BuildContext" - * role-hint="default" - */ -public class DefaultBuildContext extends AbstractLogEnabled implements BuildContext { - - public boolean hasDelta(String relpath) { - return true; - } - - public boolean hasDelta(File file) { - return true; - } - - public boolean hasDelta(List relpaths) { - return true; - } - - public OutputStream newFileOutputStream(File file) throws IOException { - return new FileOutputStream(file); - } - - public Scanner newScanner(File basedir) { - DirectoryScanner ds = new DirectoryScanner(); - ds.setBasedir(basedir); - return ds; - } - - public void refresh(File file) { - // do nothing - } - - public Scanner newDeleteScanner(File basedir) { - return new EmptyScanner(basedir); - } - - public Scanner newScanner(File basedir, boolean ignoreDelta) { - return newScanner(basedir); - } - - public boolean isIncremental() { - return false; - } - - public Object getValue(String key) { - return null; - } - - public void setValue(String key, Object value) { - } - - private String getMessage(File file, int line, int column, String message) { - StringBuffer sb = new StringBuffer(); - sb.append(file.getAbsolutePath()).append(" [").append(line).append(':').append(column).append("]: "); - sb.append(message); - return sb.toString(); - } - - /** - * @deprecated Use addMessage with severity=SEVERITY_ERROR instead - */ - public void addError(File file, int line, int column, String message, Throwable cause) { - addMessage(file, line, column, message, SEVERITY_ERROR, cause); - } - - /** - * @deprecated Use addMessage with severity=SEVERITY_WARNING instead - */ - public void addWarning(File file, int line, int column, String message, Throwable cause) { - addMessage(file, line, column, message, SEVERITY_WARNING, cause); - } - - public void addMessage(File file, int line, int column, String message, int severity, Throwable cause) { - switch(severity) { - case BuildContext.SEVERITY_ERROR: - getLogger().error(getMessage(file, line, column, message), cause); - return; - case BuildContext.SEVERITY_WARNING: - getLogger().warn(getMessage(file, line, column, message), cause); - return; - } - throw new IllegalArgumentException("severity=" + severity); - } - - public void removeMessages(File file) { - } - - public boolean isUptodate(File target, File source) { - return target != null && target.exists() && source != null && source.exists() - && target.lastModified() > source.lastModified(); - } -} diff --git a/src/main/java/org/sonatype/plexus/build/incremental/EmptyScanner.java b/src/main/java/org/sonatype/plexus/build/incremental/EmptyScanner.java deleted file mode 100644 index 7cce37d..0000000 --- a/src/main/java/org/sonatype/plexus/build/incremental/EmptyScanner.java +++ /dev/null @@ -1,57 +0,0 @@ -/* -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.sonatype.plexus.build.incremental; - -import java.io.File; - -import org.codehaus.plexus.util.Scanner; - -/** - * Scanner implementation never finds any files/directories. - */ -public class EmptyScanner implements Scanner { - - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - - private final File basedir; - - public EmptyScanner(File basedir) { - this.basedir = basedir; - } - - public void addDefaultExcludes() { - } - - public String[] getIncludedDirectories() { - return EMPTY_STRING_ARRAY; - } - - public String[] getIncludedFiles() { - return EMPTY_STRING_ARRAY; - } - - public void scan() { - } - - public void setExcludes(String[] excludes) { - } - - public void setIncludes(String[] includes) { - } - - public File getBasedir() { - return basedir; - } - -} diff --git a/src/main/java/org/sonatype/plexus/build/incremental/ThreadBuildContext.java b/src/main/java/org/sonatype/plexus/build/incremental/ThreadBuildContext.java deleted file mode 100644 index abf6995..0000000 --- a/src/main/java/org/sonatype/plexus/build/incremental/ThreadBuildContext.java +++ /dev/null @@ -1,119 +0,0 @@ -/* -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.sonatype.plexus.build.incremental; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.util.List; - -import org.codehaus.plexus.util.Scanner; - -/** - * BuildContext implementation that delegates actual work to thread-local - * build context set using {@link #setThreadBuildContext(BuildContext)}. - * {@link DefaultBuildContext} is used if no thread local build context was set. - * - * Note that plexus component metadata is not generated for this implementation. - * Apparently, older version of plexus used by maven-filtering and likely - * other projects, does not honour "default" role-hint. - */ -public class ThreadBuildContext implements BuildContext { - - private static final ThreadLocal threadContext = new ThreadLocal(); - - private static final DefaultBuildContext defaultContext = new DefaultBuildContext(); - - public static BuildContext getContext() { - BuildContext context = (BuildContext) threadContext.get(); - if(context == null) { - context = defaultContext; - } - return context; - } - - public static void setThreadBuildContext(BuildContext context) { - threadContext.set(context); - } - - public boolean hasDelta(String relPath) { - return getContext().hasDelta(relPath); - } - - public boolean hasDelta(File file) { - return getContext().hasDelta(file); - } - - public boolean hasDelta(List relPaths) { - return getContext().hasDelta(relPaths); - } - - public Scanner newDeleteScanner(File basedir) { - return getContext().newDeleteScanner(basedir); - } - - public OutputStream newFileOutputStream(File file) throws IOException { - return getContext().newFileOutputStream(file); - } - - public Scanner newScanner(File basedir) { - return getContext().newScanner(basedir); - } - - public Scanner newScanner(File basedir, boolean ignoreDelta) { - return getContext().newScanner(basedir, ignoreDelta); - } - - public void refresh(File file) { - getContext().refresh(file); - } - - public Object getValue(String key) { - return getContext().getValue(key); - } - - public boolean isIncremental() { - return getContext().isIncremental(); - } - - public void setValue(String key, Object value) { - getContext().setValue(key, value); - } - - public void addMessage(File file, int line, int column, String message, int severity, Throwable cause) { - getContext().addMessage(file, line, column, message, severity, cause); - } - - public void removeMessages(File file) { - getContext().removeMessages(file); - } - - /** - * @deprecated Use addMessage with severity=SEVERITY_WARNING instead - */ - public void addWarning(File file, int line, int column, String message, Throwable cause) { - addMessage(file, line, column, message, BuildContext.SEVERITY_WARNING, cause); - } - - /** - * @deprecated Use addMessage with severity=SEVERITY_ERROR instead - */ - public void addError(File file, int line, int column, String message, Throwable cause) { - addMessage(file, line, column, message, BuildContext.SEVERITY_ERROR, cause); - } - - public boolean isUptodate(File target, File source) { - return getContext().isUptodate(target, source); - } -} diff --git a/src/main/resources/META-INF/maven/extension.xml b/src/main/resources/META-INF/maven/extension.xml new file mode 100644 index 0000000..181a211 --- /dev/null +++ b/src/main/resources/META-INF/maven/extension.xml @@ -0,0 +1,11 @@ + + + + + org.codehaus.plexus.build + + + + org.codehaus.plexus:plexus-build-api + + diff --git a/src/main/resources/org/sonatype/plexus/build/incremental/version.properties b/src/main/resources/org/codehaus/plexus/build/version.properties similarity index 100% rename from src/main/resources/org/sonatype/plexus/build/incremental/version.properties rename to src/main/resources/org/codehaus/plexus/build/version.properties diff --git a/src/test/java/org/sonatype/plexus/build/incremental/test/TestFullBuildContext.java b/src/test/java/org/sonatype/plexus/build/incremental/test/TestFullBuildContext.java deleted file mode 100644 index 6e00af9..0000000 --- a/src/test/java/org/sonatype/plexus/build/incremental/test/TestFullBuildContext.java +++ /dev/null @@ -1,33 +0,0 @@ -/* -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.sonatype.plexus.build.incremental.test; - -import java.util.Map; - -import org.sonatype.plexus.build.incremental.DefaultBuildContext; - - -public class TestFullBuildContext extends DefaultBuildContext { - - private final Map context; - - public TestFullBuildContext(Map context) { - this.context = context; - } - - public void setValue(String key, Object value) { - context.put(key, value); - } - -} diff --git a/src/test/java/org/sonatype/plexus/build/incremental/test/TestIncrementalBuildContext.java b/src/test/java/org/sonatype/plexus/build/incremental/test/TestIncrementalBuildContext.java deleted file mode 100644 index 14c1e26..0000000 --- a/src/test/java/org/sonatype/plexus/build/incremental/test/TestIncrementalBuildContext.java +++ /dev/null @@ -1,198 +0,0 @@ -/* -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.sonatype.plexus.build.incremental.test; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.codehaus.plexus.util.DirectoryScanner; -import org.codehaus.plexus.util.Scanner; -import org.sonatype.plexus.build.incremental.BuildContext; - -public class TestIncrementalBuildContext implements BuildContext { - - private final File basedir; - - private final HashSet refresh = new HashSet(); - - private static final class TestScanner implements Scanner { - private final File basedir; - private final Set files; - - private TestScanner(File basedir, Set files) { - this.basedir = basedir; - this.files = files; - } - - public void addDefaultExcludes() { - } - - public String[] getIncludedDirectories() { - return new String[0]; - } - - public String[] getIncludedFiles() { - return (String[]) files.toArray(new String[files.size()]); - } - - public void scan() { - } - - public void setExcludes(String[] excludes) { - } - - public void setIncludes(String[] includes) { - } - - public File getBasedir() { - return basedir; - } - } - - private final Set changedFiles; - - private final Set deletedFiles; - - private final Map context; - - private final List warnings; - - private final List errors; - - public TestIncrementalBuildContext(File basedir, Set changedFiles, Map context) { - this(basedir, changedFiles, new HashSet(), context); - } - - public TestIncrementalBuildContext(File basedir, Set changedFiles, Set deletedFiles, Map context) { - this(basedir, changedFiles, new HashSet(), context, new ArrayList(), new ArrayList()); - } - - public TestIncrementalBuildContext(File basedir, Set changedFiles, Set deletedFiles, Map context, List warnings, List errors) { - this.basedir = basedir; - this.changedFiles = changedFiles; - this.deletedFiles = deletedFiles; - this.context = context; - this.warnings = warnings; - this.errors = errors; - } - - public boolean hasDelta(String relpath) { - String basepath = basedir.getAbsolutePath(); - - if (relpath.startsWith(basepath)) { - relpath = relpath.substring(basepath.length() + 1); - } - - return changedFiles.contains(relpath) || deletedFiles.contains(relpath); - } - - public boolean hasDelta(List relpaths) { - for(Iterator i = relpaths.iterator(); i.hasNext();) { - String relpath = (String) i.next(); - if(hasDelta(relpath)) { - return true; - } - } - return false; - } - - public boolean hasDelta(File file) { - String relpath = getRelpath(file); - return relpath == null || hasDelta(relpath); - } - - private String getRelpath(File file) { - try { - String path = file.getCanonicalPath(); - String basepath = basedir.getCanonicalPath(); - if (path.startsWith(basepath) && !path.equals(basepath)) { - return path.substring(basepath.length()); - } else { - return null; - } - } catch (IOException e) { - // this is a test implementation, we can be little loose here - throw new IllegalArgumentException(e); - } - } - -public boolean isIncremental() { - return true; - } - - public Scanner newDeleteScanner(File basedir) { - return new TestScanner(basedir, deletedFiles); - } - - public OutputStream newFileOutputStream(File file) throws IOException { - refresh(file); - return new FileOutputStream(file); - } - - public Scanner newScanner(final File basedir) { - return new TestScanner(basedir, changedFiles); - } - - public Scanner newScanner(File basedir, boolean ignoreDelta) { - if(ignoreDelta) { - DirectoryScanner directoryScanner = new DirectoryScanner(); - directoryScanner.setBasedir(basedir); - return directoryScanner; - } - - return newScanner(basedir); - } - - public void refresh(File file) { - refresh.add(file.getAbsoluteFile()); - } - - public Object getValue(String key) { - return context.get(key); - } - - public void setValue(String key, Object value) { - context.put(key, value); - } - - public Set getRefreshFiles() { - return refresh; - } - - public void addError(File file, int line, int column, String message, Throwable cause) { - } - - public void addWarning(File file, int line, int column, String message, Throwable cause) { - } - - public void addMessage(File file, int line, int column, String message, int severity, Throwable cause) { - } - - public void removeMessages(File file) { - } - - public boolean isUptodate(File target, File source) { - return target != null && target.exists() && !hasDelta(target) - && source != null && source.exists() && !hasDelta(source) - && target.lastModified() > source.lastModified(); - } -}