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

slachiewicz pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/maven-remote-resources-plugin.git

commit 068cc15c7e9753c673be419d3b9c9fae2b3e85c1
Author: Sylwester Lachiewicz <[email protected]>
AuthorDate: Wed Oct 28 01:20:58 2026 +0000

    Map project-specific licenses to SPDX IDs with licenseMappings (#316)
---
 .../spdx-license-id/{ => f-mapped}/pom.xml         | 18 +++---
 src/it/resources/spdx-license-id/pom.xml           |  1 +
 src/it/resources/spdx-license-id/project/pom.xml   | 13 +++++
 .../remote/AbstractProcessRemoteResourcesMojo.java | 32 ++++++++++-
 .../plugin/resources/remote/LicenseMapping.java    | 67 ++++++++++++++++++++++
 .../plugin/resources/remote/SpdxLicenses.java      | 31 +++++++++-
 .../plugin/resources/remote/license-aliases.txt    |  3 +-
 .../plugin/resources/remote/SpdxLicensesTest.java  | 24 ++++++++
 .../resources/remote/it/ITSpdxLicenseId.java       |  1 +
 9 files changed, 175 insertions(+), 15 deletions(-)

diff --git a/src/it/resources/spdx-license-id/pom.xml 
b/src/it/resources/spdx-license-id/f-mapped/pom.xml
similarity index 73%
copy from src/it/resources/spdx-license-id/pom.xml
copy to src/it/resources/spdx-license-id/f-mapped/pom.xml
index cbf7256..3dd5fa1 100644
--- a/src/it/resources/spdx-license-id/pom.xml
+++ b/src/it/resources/spdx-license-id/f-mapped/pom.xml
@@ -18,22 +18,20 @@ specific language governing permissions and limitations
 under the License.
 -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
-  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+<project>
   <modelVersion>4.0.0</modelVersion>
 
   <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId>
-  <artifactId>aggregator</artifactId>
+  <artifactId>f-mapped</artifactId>
   <version>1.0</version>
   <packaging>pom</packaging>
 
-  <modules>
-    <module>a-name</module>
-    <module>b-url</module>
-    <module>c-expression</module>
-    <module>d-unknown</module>
-    <module>e-dual</module>
-  </modules>
+  <licenses>
+    <license>
+      <name>BSD</name>
+      <url>http://example.org/widget/LICENSE.txt</url>
+    </license>
+  </licenses>
 
   <distributionManagement>
     <repository>
diff --git a/src/it/resources/spdx-license-id/pom.xml 
b/src/it/resources/spdx-license-id/pom.xml
index cbf7256..2d340a7 100644
--- a/src/it/resources/spdx-license-id/pom.xml
+++ b/src/it/resources/spdx-license-id/pom.xml
@@ -33,6 +33,7 @@ under the License.
     <module>c-expression</module>
     <module>d-unknown</module>
     <module>e-dual</module>
+    <module>f-mapped</module>
   </modules>
 
   <distributionManagement>
diff --git a/src/it/resources/spdx-license-id/project/pom.xml 
b/src/it/resources/spdx-license-id/project/pom.xml
index 78227d9..35c3b96 100644
--- a/src/it/resources/spdx-license-id/project/pom.xml
+++ b/src/it/resources/spdx-license-id/project/pom.xml
@@ -62,6 +62,12 @@ under the License.
       <version>1.0</version>
       <type>pom</type>
     </dependency>
+    <dependency>
+      <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId>
+      <artifactId>f-mapped</artifactId>
+      <version>1.0</version>
+      <type>pom</type>
+    </dependency>
   </dependencies>
 
   <build>
@@ -78,6 +84,13 @@ under the License.
               <resourceBundles>
                 
<resourceBundle>org.apache.maven.plugin.rresource.it:resource-bundle-with-template:${project.version}</resourceBundle>
               </resourceBundles>
+              <licenseMappings>
+                <licenseMapping>
+                  <spdxId>BSD-3-Clause</spdxId>
+                  <name>BSD</name>
+                  <url>https://example.org/widget/LICENSE</url>
+                </licenseMapping>
+              </licenseMappings>
             </configuration>
           </execution>
         </executions>
diff --git 
a/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java
 
b/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java
index c5493c9..c6cecfb 100644
--- 
a/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java
+++ 
b/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java
@@ -191,6 +191,26 @@ public abstract class AbstractProcessRemoteResourcesMojo 
extends AbstractMojo {
     @Parameter
     private List<String> supplementalModelArtifacts;
 
+    /**
+     * Maps licenses that <code>$spdx</code> does not resolve to SPDX license 
IDs or expressions. A mapping
+     * applies to a declared license when each of its <code>name</code> and 
<code>url</code> that is set matches,
+     * ignoring case, punctuation, the URL scheme, <code>www.</code> and a 
trailing <code>.txt</code> or
+     * <code>.html</code>. Mappings are tried in order, before the built-in 
aliases.
+     * <pre>
+     * &lt;licenseMappings&gt;
+     *   &lt;licenseMapping&gt;
+     *     &lt;spdxId&gt;BSD-3-Clause&lt;/spdxId&gt;
+     *     &lt;name&gt;BSD&lt;/name&gt;
+     *     &lt;url&gt;https://example.org/widget/LICENSE&lt;/url&gt;
+     *   &lt;/licenseMapping&gt;
+     * &lt;/licenseMappings&gt;
+     * </pre>
+     *
+     * @since 3.4.0
+     */
+    @Parameter
+    private List<LicenseMapping> licenseMappings;
+
     /**
      * The resource bundles that will be retrieved and processed,
      * expressed with 
<code>groupId:artifactId:version[:type[:classifier]]</code> format.
@@ -725,6 +745,16 @@ public abstract class AbstractProcessRemoteResourcesMojo 
extends AbstractMojo {
     }
 
     protected void validate() throws MojoExecutionException {
+        if (licenseMappings != null) {
+            for (LicenseMapping mapping : licenseMappings) {
+                if (SpdxLicenses.isBlank(mapping.getSpdxId())
+                        || (SpdxLicenses.isBlank(mapping.getName()) && 
SpdxLicenses.isBlank(mapping.getUrl()))) {
+                    throw new MojoExecutionException(
+                            "Each licenseMapping needs an spdxId and a name, a 
url or both: " + mapping);
+                }
+            }
+        }
+
         int bundleCount = 1;
 
         for (String artifactDescriptor : resourceBundles) {
@@ -809,7 +839,7 @@ public abstract class AbstractProcessRemoteResourcesMojo 
extends AbstractMojo {
         context.put("project", project);
         context.put("presentYear", year);
         context.put("locator", locator);
-        context.put("spdx", new SpdxLicenses());
+        context.put("spdx", new SpdxLicenses(licenseMappings == null ? 
Collections.emptyList() : licenseMappings));
 
         if (inceptionYear.equals(year)) {
             context.put("projectTimespan", year);
diff --git 
a/src/main/java/org/apache/maven/plugin/resources/remote/LicenseMapping.java 
b/src/main/java/org/apache/maven/plugin/resources/remote/LicenseMapping.java
new file mode 100644
index 0000000..2b42603
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugin/resources/remote/LicenseMapping.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugin.resources.remote;
+
+/**
+ * Maps a license, as dependencies declare it, to an SPDX license ID or 
expression for <code>$spdx</code> in
+ * templates. A mapping applies to a declared license when each of its 
<code>name</code> and <code>url</code>
+ * that is set matches, after the same normalisation as the built-in aliases.
+ *
+ * @since 3.4.0
+ */
+public class LicenseMapping {
+
+    /** The SPDX license ID or expression to return, for example 
<code>BSD-3-Clause</code>. */
+    private String spdxId;
+
+    /** The declared license name to match. */
+    private String name;
+
+    /** The declared license URL to match. */
+    private String url;
+
+    public String getSpdxId() {
+        return spdxId;
+    }
+
+    public void setSpdxId(String spdxId) {
+        this.spdxId = spdxId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    @Override
+    public String toString() {
+        return "LicenseMapping{spdxId=" + spdxId + ", name=" + name + ", url=" 
+ url + "}";
+    }
+}
diff --git 
a/src/main/java/org/apache/maven/plugin/resources/remote/SpdxLicenses.java 
b/src/main/java/org/apache/maven/plugin/resources/remote/SpdxLicenses.java
index 80b6234..6e34ce3 100644
--- a/src/main/java/org/apache/maven/plugin/resources/remote/SpdxLicenses.java
+++ b/src/main/java/org/apache/maven/plugin/resources/remote/SpdxLicenses.java
@@ -25,6 +25,7 @@ import java.io.InputStreamReader;
 import java.io.UncheckedIOException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -41,8 +42,8 @@ import org.apache.maven.project.MavenProject;
  * Resolves a license declared in a POM to an <a 
href="https://spdx.org/licenses/";>SPDX license ID</a>.
  * Available in templates as <code>$spdx</code>, for example 
<code>$!spdx.id($license)</code>.
  * <p>
- * The declared name is tried first: as a known SPDX ID, as an SPDX license 
expression made of known IDs,
- * and as a known alias. The declared URL is tried next: as a known alias, and 
as an <code>opensource.org</code>
+ * Configured {@link LicenseMapping}s are tried first. The declared name is 
tried next: as a known SPDX ID, as an SPDX license expression made of known IDs,
+ * and as a known alias. The declared URL is tried last: as a known alias, and 
as an <code>opensource.org</code>
  * or <code>spdx.org</code> URL that ends with a known ID. When the name and 
the URL resolve to different IDs,
  * nothing matches. The known IDs and aliases cover the licenses that POMs 
declare most often; a deprecated
  * SPDX ID such as <code>GPL-2.0</code> is returned as declared.
@@ -72,7 +73,14 @@ public class SpdxLicenses {
     /** Normalised URL to ID or expression. */
     private final Map<String, String> urls = new HashMap<>();
 
+    private final List<LicenseMapping> mappings;
+
     SpdxLicenses() {
+        this(Collections.emptyList());
+    }
+
+    SpdxLicenses(List<LicenseMapping> mappings) {
+        this.mappings = new ArrayList<>(mappings);
         InputStream in = SpdxLicenses.class.getResourceAsStream(RESOURCE);
         if (in == null) {
             throw new IllegalStateException("Missing resource " + RESOURCE);
@@ -163,6 +171,11 @@ public class SpdxLicenses {
      * @return the SPDX ID or expression, or {@code null} when nothing matches
      */
     public String id(String name, String url) {
+        for (LicenseMapping mapping : mappings) {
+            if (matches(mapping.getName(), name, true) && 
matches(mapping.getUrl(), url, false)) {
+                return mapping.getSpdxId().trim();
+            }
+        }
         String byName = isBlank(name) ? null : idOfName(name.trim());
         String byUrl = isBlank(url) ? null : idOfUrl(normaliseUrl(url));
         if (byName == null) {
@@ -281,7 +294,19 @@ public class SpdxLicenses {
         }
     }
 
-    private static boolean isBlank(String s) {
+    private static boolean matches(String expected, String declared, boolean 
isName) {
+        if (isBlank(expected)) {
+            return true;
+        }
+        if (isBlank(declared)) {
+            return false;
+        }
+        return isName
+                ? normaliseName(expected).equals(normaliseName(declared))
+                : normaliseUrl(expected).equals(normaliseUrl(declared));
+    }
+
+    static boolean isBlank(String s) {
         return s == null || s.trim().isEmpty();
     }
 
diff --git 
a/src/main/resources/org/apache/maven/plugin/resources/remote/license-aliases.txt
 
b/src/main/resources/org/apache/maven/plugin/resources/remote/license-aliases.txt
index 34ebde1..01e001e 100644
--- 
a/src/main/resources/org/apache/maven/plugin/resources/remote/license-aliases.txt
+++ 
b/src/main/resources/org/apache/maven/plugin/resources/remote/license-aliases.txt
@@ -26,7 +26,8 @@
 # whether "only" or "or later" applies, and a wrong ID is worse than none. So 
are URLs
 # of pages that cover two licenses at once, such as GlassFish's 
CDDL+GPL_1_1.html, and
 # license pages of individual projects, such as jsoup.org/license: they name 
no license,
-# and keep their address when the project changes its license.
+# and keep their address when the project changes its license. Map those with 
the
+# plugin's licenseMappings parameter instead.
 
 0BSD
 AGPL-3.0-only
diff --git 
a/src/test/java/org/apache/maven/plugin/resources/remote/SpdxLicensesTest.java 
b/src/test/java/org/apache/maven/plugin/resources/remote/SpdxLicensesTest.java
index 80f4af7..97c2453 100644
--- 
a/src/test/java/org/apache/maven/plugin/resources/remote/SpdxLicensesTest.java
+++ 
b/src/test/java/org/apache/maven/plugin/resources/remote/SpdxLicensesTest.java
@@ -18,6 +18,8 @@
  */
 package org.apache.maven.plugin.resources.remote;
 
+import java.util.Arrays;
+
 import org.apache.maven.model.License;
 import org.apache.maven.model.Model;
 import org.apache.maven.project.MavenProject;
@@ -139,6 +141,28 @@ class SpdxLicensesTest {
         return new MavenProject(model);
     }
 
+    @Test
+    void mappings() {
+        SpdxLicenses mapped = new SpdxLicenses(Arrays.asList(
+                mapping("BSD-3-Clause", "BSD", 
"https://example.org/widget/LICENSE";),
+                mapping("LicenseRef-Bouncy-Castle", "Bouncy Castle Licence", 
null),
+                mapping("Apache-2.0 OR MIT", null, 
"https://example.com/LICENSE";)));
+        assertEquals("BSD-3-Clause", mapped.id("bsd", 
"http://www.example.org/widget/LICENSE.txt";));
+        assertNull(mapped.id("BSD", null));
+        assertNull(mapped.id(null, "https://example.org/widget/LICENSE";));
+        assertEquals("LicenseRef-Bouncy-Castle", mapped.id("Bouncy Castle 
Licence", "https://example.org/";));
+        assertEquals("Apache-2.0 OR MIT", mapped.id("Apache 2.0", 
"https://example.com/LICENSE.txt";));
+        assertEquals("MIT", mapped.id("MIT", null));
+    }
+
+    private static LicenseMapping mapping(String spdxId, String name, String 
url) {
+        LicenseMapping mapping = new LicenseMapping();
+        mapping.setSpdxId(spdxId);
+        mapping.setName(name);
+        mapping.setUrl(url);
+        return mapping;
+    }
+
     @Test
     void license() {
         License license = new License();
diff --git 
a/src/test/java/org/apache/maven/plugin/resources/remote/it/ITSpdxLicenseId.java
 
b/src/test/java/org/apache/maven/plugin/resources/remote/it/ITSpdxLicenseId.java
index 7676878..c99e11f 100644
--- 
a/src/test/java/org/apache/maven/plugin/resources/remote/it/ITSpdxLicenseId.java
+++ 
b/src/test/java/org/apache/maven/plugin/resources/remote/it/ITSpdxLicenseId.java
@@ -59,5 +59,6 @@ public class ITSpdxLicenseId extends AbstractIT {
         assertTrue(
                 content.contains("e-dual expression: CDDL-1.1 OR (GPL-2.0-only 
WITH Classpath-exception-2.0)\n"),
                 content);
+        assertTrue(content.contains("f-mapped: BSD-3-Clause\n"), content);
     }
 }

Reply via email to