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

cziegeler pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-extension-apiregions.git


The following commit(s) were added to refs/heads/master by this push:
     new 146c81c  SLING-13129 : Add optional library info to deprecated package
146c81c is described below

commit 146c81ccc811a00a084481886aac5001989bdffd
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Fri Feb 27 09:00:42 2026 +0100

    SLING-13129 : Add optional library info to deprecated package
---
 .../apiregions/analyser/CheckDeprecatedApi.java    | 77 +++++++++++++++++-----
 .../extension/apiregions/api/ApiExport.java        | 35 +++++++++-
 .../extension/apiregions/api/package-info.java     |  2 +-
 .../analyser/CheckApiRegionsDuplicatesTest.java    |  1 -
 .../analyser/CheckDeprecatedApiTest.java           | 58 ++++++++++++++++
 5 files changed, 153 insertions(+), 20 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApi.java
 
b/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApi.java
index fdd7d7f..c8cf216 100644
--- 
a/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApi.java
+++ 
b/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApi.java
@@ -26,6 +26,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -120,6 +121,10 @@ public class CheckDeprecatedApi implements AnalyserTask {
 
         for (final BundleDescriptor bd : 
context.getFeatureDescriptor().getBundleDescriptors()) {
             if (isInAllowedRegion(bundleRegions.get(bd), region.getName(), 
allowedNames)) {
+                // Collect all deprecation findings, grouped by library (null 
= no library)
+                final Map<String, String> libraryErrors = new TreeMap<>();
+                final Map<String, String> libraryWarnings = new TreeMap<>();
+
                 for (final PackageInfo pi : bd.getImportedPackages()) {
                     if (!checkOptionalImports && pi.isOptional()) {
                         continue;
@@ -130,19 +135,30 @@ public class CheckDeprecatedApi implements AnalyserTask {
                         deprecationInfo = deprecatedPackage.isDeprecated(pi);
                     }
                     if (deprecationInfo != null) {
-                        String msg = "Usage of deprecated package found : "
-                                .concat(pi.getName())
-                                .concat(" : ")
-                                .concat(deprecationInfo.getMessage());
-                        if (deprecationInfo.getSince() != null) {
-                            msg = msg.concat(" Deprecated since 
").concat(deprecationInfo.getSince());
-                        }
                         boolean isError;
                         if (deprecationInfo.getMode() != null) {
                             isError = deprecationInfo.getMode() == 
DeprecationValidationMode.STRICT;
                         } else {
                             isError = strict;
                         }
+                        String msg;
+                        final String library = 
deprecatedPackage.getExport().getLibrary();
+                        if (library != null) {
+                            msg = "Usage of deprecated library found : "
+                                    .concat(library)
+                                    .concat(", package(s) : <start>");
+                        } else {
+                            msg = "Usage of deprecated package found : ";
+                        }
+                        msg = msg.concat(pi.getName());
+                        if (library != null) {
+                            msg = msg.concat("<end>");
+                        }
+                        msg = msg.concat(" : 
").concat(deprecationInfo.getMessage());
+
+                        if (deprecationInfo.getSince() != null) {
+                            msg = msg.concat(" Deprecated since 
").concat(deprecationInfo.getSince());
+                        }
                         if (deprecationInfo.isForRemoval()) {
                             boolean printRemoval = true;
                             if (checkDate != null) {
@@ -160,13 +176,38 @@ public class CheckDeprecatedApi implements AnalyserTask {
                                 msg = msg.concat(" For removal : 
").concat(deprecationInfo.getForRemoval());
                             }
                         }
-                        if (isError) {
-                            
context.reportArtifactError(bd.getArtifact().getId(), msg);
+                        if (library != null) {
+                            final Map<String, String> target = isError ? 
libraryErrors : libraryWarnings;
+                            // check if entry exists for library
+                            String entry = target.get(library);
+                            if (entry == null) {
+                                entry = msg;
+                            } else {
+                                entry = entry.replace(
+                                        "<end>", ", 
".concat(pi.getName()).concat("<end>"));
+                            }
+                            target.put(library, entry);
                         } else {
-                            
context.reportArtifactWarning(bd.getArtifact().getId(), msg);
+                            if (isError) {
+                                
context.reportArtifactError(bd.getArtifact().getId(), msg);
+                            } else {
+                                
context.reportArtifactWarning(bd.getArtifact().getId(), msg);
+                            }
                         }
                     }
                 }
+
+                // Report grouped messages per library
+                for (final String entry : libraryErrors.values()) {
+                    context.reportArtifactError(
+                            bd.getArtifact().getId(),
+                            entry.replace("<start>", "").replace("<end>", ""));
+                }
+                for (final String entry : libraryWarnings.values()) {
+                    context.reportArtifactWarning(
+                            bd.getArtifact().getId(),
+                            entry.replace("<start>", "").replace("<end>", ""));
+                }
             }
         }
     }
@@ -233,7 +274,7 @@ public class CheckDeprecatedApi implements AnalyserTask {
                 }
             }
         }
-        return new DeprecatedPackage(export.getDeprecation().getPackageInfo(), 
deprecatedList, nonDeprecatedList);
+        return new DeprecatedPackage(export, deprecatedList, 
nonDeprecatedList);
     }
 
     private Set<String> getBundleRegions(final BundleDescriptor info, final 
ApiRegions regions) {
@@ -248,12 +289,16 @@ public class CheckDeprecatedApi implements AnalyserTask {
      * Represents a deprecated package with its deprecation information and 
package versions.
      */
     static final class DeprecatedPackage {
-        private final DeprecationInfo deprecationInfo;
+        private final ApiExport export;
         private final List<PackageInfo> deprecatedList;
         private final List<PackageInfo> nonDeprecatedList;
 
         public DeprecationInfo getDeprecationInfo() {
-            return deprecationInfo;
+            return export.getDeprecation().getPackageInfo();
+        }
+
+        public ApiExport getExport() {
+            return export;
         }
 
         /**
@@ -264,10 +309,10 @@ public class CheckDeprecatedApi implements AnalyserTask {
          * @param nonDeprecatedList list of non-deprecated package versions
          */
         public DeprecatedPackage(
-                final DeprecationInfo deprecationInfo,
+                final ApiExport export,
                 final List<PackageInfo> deprecatedList,
                 final List<PackageInfo> nonDeprecatedList) {
-            this.deprecationInfo = deprecationInfo;
+            this.export = export;
             this.deprecatedList = deprecatedList;
             this.nonDeprecatedList = nonDeprecatedList;
         }
@@ -291,7 +336,7 @@ public class CheckDeprecatedApi implements AnalyserTask {
             for (final PackageInfo deprecated : deprecatedList) {
                 final Version exportVersion = deprecated.getPackageVersion();
                 if (exportVersion == null || importVersion == null || 
importVersion.includes(exportVersion)) {
-                    return deprecationInfo;
+                    return this.getDeprecationInfo();
                 }
             }
             // not found, do not report
diff --git 
a/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiExport.java
 
b/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiExport.java
index 369ecc6..36ba64f 100644
--- 
a/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiExport.java
+++ 
b/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiExport.java
@@ -56,10 +56,15 @@ public class ApiExport implements Comparable<ApiExport> {
 
     private static final String PREVIOUS_ARTIFACT_ID_KEY = 
"previous-artifact-id";
 
+    private static final String LIBRARY_KEY = "library";
+
     private final String name;
 
     private String toggle;
 
+    /** Optional library information */
+    private String library;
+
     /** If the package is behind a toggle, this is the previous artifact 
containing the package not behind a toggle */
     private ArtifactId previousArtifactId;
 
@@ -144,6 +149,24 @@ public class ApiExport implements Comparable<ApiExport> {
         return this.deprecation;
     }
 
+    /**
+     * Get the optional library information.
+     * @return The library or {@code null}
+     * @since 2.1.0
+     */
+    public String getLibrary() {
+        return library;
+    }
+
+    /**
+     * Set the library information.
+     * @param value The library
+     * @since 2.1.0
+     */
+    public void setLibrary(final String value) {
+        this.library = value;
+    }
+
     /**
      * Internal method to parse the extension JSON
      * @param dValue The JSON value
@@ -310,6 +333,10 @@ public class ApiExport implements Comparable<ApiExport> {
             expBuilder.add(DEPRECATED_KEY, depValue);
         }
 
+        if (this.getLibrary() != null) {
+            expBuilder.add(LIBRARY_KEY, this.getLibrary());
+        }
+
         for (final Map.Entry<String, String> entry : 
this.getProperties().entrySet()) {
             expBuilder.add(entry.getKey(), entry.getValue());
         }
@@ -355,6 +382,9 @@ public class ApiExport implements Comparable<ApiExport> {
                     final JsonValue dValue = expObj.get(DEPRECATED_KEY);
                     export.parseDeprecation(dValue);
 
+                } else if (LIBRARY_KEY.equals(key)) {
+                    export.setLibrary(expObj.getString(key));
+
                     // everything else is stored as a string property
                 } else {
                     export.getProperties().put(key, expObj.getString(key));
@@ -375,12 +405,12 @@ public class ApiExport implements Comparable<ApiExport> {
     @Override
     public String toString() {
         return "ApiExport [name=" + name + ", toggle=" + toggle + ", 
previousArtifactId=" + previousArtifactId
-                + ", properties=" + properties + "]";
+                + ", library=" + library + ", properties=" + properties + "]";
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(deprecation, name, previousArtifactId, properties, 
toggle);
+        return Objects.hash(deprecation, name, previousArtifactId, properties, 
toggle, library);
     }
 
     @Override
@@ -399,6 +429,7 @@ public class ApiExport implements Comparable<ApiExport> {
                 && Objects.equals(name, other.name)
                 && Objects.equals(previousArtifactId, other.previousArtifactId)
                 && Objects.equals(properties, other.properties)
+                && Objects.equals(library, other.library)
                 && Objects.equals(toggle, other.toggle);
     }
 }
diff --git 
a/src/main/java/org/apache/sling/feature/extension/apiregions/api/package-info.java
 
b/src/main/java/org/apache/sling/feature/extension/apiregions/api/package-info.java
index bc1d240..6bd8e02 100644
--- 
a/src/main/java/org/apache/sling/feature/extension/apiregions/api/package-info.java
+++ 
b/src/main/java/org/apache/sling/feature/extension/apiregions/api/package-info.java
@@ -17,5 +17,5 @@
  * under the License.
  */
 
[email protected]("2.0.0")
[email protected]("2.1.0")
 package org.apache.sling.feature.extension.apiregions.api;
diff --git 
a/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDuplicatesTest.java
 
b/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDuplicatesTest.java
index 81b99f8..2d789f1 100644
--- 
a/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDuplicatesTest.java
+++ 
b/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDuplicatesTest.java
@@ -40,7 +40,6 @@ public class CheckApiRegionsDuplicatesTest extends 
AbstractApiRegionsAnalyserTas
 
         assertFalse(errors.isEmpty());
         assertEquals(1, errors.size());
-        System.out.println(errors);
         assertTrue(
                 errors.iterator()
                         .next()
diff --git 
a/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApiTest.java
 
b/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApiTest.java
index a16a337..8ea809d 100644
--- 
a/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApiTest.java
+++ 
b/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApiTest.java
@@ -23,6 +23,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.jar.Manifest;
@@ -358,6 +359,63 @@ public class CheckDeprecatedApiTest {
         return ctx;
     }
 
+    @Test
+    public void testLibraryGrouping() throws Exception {
+        final CheckDeprecatedApi analyser = new CheckDeprecatedApi();
+
+        final Feature feature = new 
Feature(ArtifactId.fromMvnId("g:feature:1"));
+        final Extension extension =
+                new Extension(ExtensionType.JSON, ApiRegions.EXTENSION_NAME, 
ExtensionState.OPTIONAL);
+        
extension.setJSON("[{\"name\":\"global\",\"feature-origins\":[\"g:feature:1\"],"
+                + "\"exports\":["
+                + 
"{\"name\":\"org.foo.a\",\"deprecated\":{\"msg\":\"deprecated 
a\"},\"library\":\"com.example:lib:1\"},"
+                + 
"{\"name\":\"org.foo.b\",\"deprecated\":{\"msg\":\"deprecated 
b\"},\"library\":\"com.example:lib:1\"},"
+                + "{\"name\":\"org.foo.c\",\"deprecated\":\"deprecated c\"}"
+                + "]}]");
+        feature.getExtensions().add(extension);
+
+        final FeatureDescriptor fd = new FeatureDescriptorImpl(feature);
+
+        // API bundles exporting the packages
+        final Artifact apiBundle = new 
Artifact(ArtifactId.fromMvnId("g:api:1.0.0"));
+        apiBundle.setFeatureOrigins(feature.getId());
+        final BundleDescriptor apiDesc = new TestBundleDescriptor(apiBundle);
+        apiDesc.getExportedPackages().add(new PackageInfo("org.foo.a", "1.0", 
false, Collections.emptySet()));
+        apiDesc.getExportedPackages().add(new PackageInfo("org.foo.b", "1.0", 
false, Collections.emptySet()));
+        apiDesc.getExportedPackages().add(new PackageInfo("org.foo.c", "1.0", 
false, Collections.emptySet()));
+        fd.getBundleDescriptors().add(apiDesc);
+
+        // Importer bundle importing all three packages
+        final Artifact importBundle = new 
Artifact(ArtifactId.fromMvnId("g:importer:1.0.0"));
+        importBundle.setFeatureOrigins(feature.getId());
+        final BundleDescriptor importBd = new 
TestBundleDescriptor(importBundle);
+        importBd.getImportedPackages().add(new PackageInfo("org.foo.a", "1.0", 
false, Collections.emptySet()));
+        importBd.getImportedPackages().add(new PackageInfo("org.foo.b", "1.0", 
false, Collections.emptySet()));
+        importBd.getImportedPackages().add(new PackageInfo("org.foo.c", "1.0", 
false, Collections.emptySet()));
+        fd.getBundleDescriptors().add(importBd);
+
+        final AnalyserTaskContext ctx = 
Mockito.mock(AnalyserTaskContext.class);
+        Mockito.when(ctx.getFeature()).thenReturn(feature);
+        Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);
+        
Mockito.when(ctx.getConfiguration()).thenReturn(Collections.emptyMap());
+
+        analyser.execute(ctx);
+
+        final ArtifactId importerId = ArtifactId.fromMvnId("g:importer:1.0.0");
+
+        // Exactly two warnings total for this bundle (one grouped, one 
individual)
+        final org.mockito.ArgumentCaptor<String> msgCaptor = 
org.mockito.ArgumentCaptor.forClass(String.class);
+        Mockito.verify(ctx, 
Mockito.times(2)).reportArtifactWarning(Mockito.eq(importerId), 
msgCaptor.capture());
+        Mockito.verify(ctx, never()).reportArtifactError(Mockito.any(), 
Mockito.anyString());
+
+        final List<String> msgs = msgCaptor.getAllValues();
+        // one message must contain the library name and both package names
+        assertTrue(msgs.stream()
+                .anyMatch(m -> m.contains("com.example:lib:1") && 
m.contains("org.foo.a") && m.contains("org.foo.b")));
+        // one message must contain the individual package name without 
library grouping prefix
+        assertTrue(msgs.stream().anyMatch(m -> m.contains("org.foo.c") && 
!m.contains("com.example:lib:1")));
+    }
+
     private static final class TestBundleDescriptor extends BundleDescriptor {
         private final Artifact artifact;
 

Reply via email to