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

asf-gitbox-commits pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ant-antlibs-cyclonedx.git


The following commit(s) were added to refs/heads/main by this push:
     new 581ae00  extract SBOM link reading from Component class
581ae00 is described below

commit 581ae00b98e7e050a6eab32b53b6b7a49a42ca1e
Author: Stefan Bodewig <[email protected]>
AuthorDate: Sun Jul 19 16:40:07 2026 +0200

    extract SBOM link reading from Component class
---
 docs/component.html                                |   2 +-
 src/main/org/apache/ant/cyclonedx/Component.java   | 123 ++++------------
 .../ant/cyclonedx/SbomLinkComponentResolver.java   | 158 +++++++++++++++++++++
 3 files changed, 185 insertions(+), 98 deletions(-)

diff --git a/docs/component.html b/docs/component.html
index 6aa3f9c..3a77bd8 100644
--- a/docs/component.html
+++ b/docs/component.html
@@ -168,7 +168,7 @@ <h4 id="sbomLink">sbomLink</h4>
         component unless it is explicitly specified on the component
         element itself or <code>supplierismanufacturer</code>
         is <code>true</code>.</li>
-      <li>Tags are merged wiht those of the SBOM's metadata
+      <li>Tags are merged with those of the SBOM's metadata
         component.</li>
       <li><code>author</code>s, <code>license</code>s, 
<code>externalReference</code>s,
         <code>dependency</code>s and nested <code>components</code>
diff --git a/src/main/org/apache/ant/cyclonedx/Component.java 
b/src/main/org/apache/ant/cyclonedx/Component.java
index 146e998..d7c783c 100644
--- a/src/main/org/apache/ant/cyclonedx/Component.java
+++ b/src/main/org/apache/ant/cyclonedx/Component.java
@@ -17,10 +17,8 @@
  */
 package org.apache.ant.cyclonedx;
 
-import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -39,18 +37,13 @@ import org.apache.tools.ant.types.Reference;
 import org.apache.tools.ant.types.Resource;
 import org.apache.tools.ant.types.resources.FileProvider;
 import org.apache.tools.ant.types.resources.Union;
-import org.apache.tools.ant.types.resources.URLProvider;
 
 import org.cyclonedx.Version;
-import org.cyclonedx.exception.ParseException;
-import org.cyclonedx.model.Bom;
 import org.cyclonedx.model.LicenseChoice;
 import org.cyclonedx.model.OrganizationalContact;
 import org.cyclonedx.model.OrganizationalEntity;
 import org.cyclonedx.model.Property;
 import org.cyclonedx.model.component.Tags;
-import org.cyclonedx.parsers.BomParserFactory;
-import org.cyclonedx.parsers.Parser;
 import org.cyclonedx.util.BomUtils;
 
 /**
@@ -563,6 +556,20 @@ public class Component extends DataType {
         return result;
     }
 
+    /**
+     * Gets the dependencies of the component.
+     *
+     * @return component's dependencies
+     * @since CycloneDX Antlib 0.2
+     */
+    Collection<org.cyclonedx.model.ExternalReference> getExternalReferences() {
+        if (isReference()) {
+            return getRef().getExternalReferences();
+        }
+        dieOnCircularReference();
+        return externalReferences;
+    }
+
     /**
      * Read the linked SBOM (if any) and merge its content with the
      * one already defined for this component.
@@ -580,35 +587,8 @@ public class Component extends DataType {
         if (sbomLink != null && !sbomLinkResolved) {
             sbomLinkResolved = true;
 
-            Bom bom = readLinkedSbom();
-            if (bom.getMetadata() == null) {
-                throw new BuildException("referenced SBOM file lacks 
metadata");
-            }
-            org.cyclonedx.model.Component real = 
bom.getMetadata().getComponent();
-            if (real == null) {
-                throw new BuildException("referenced SBOM file lacks 
component");
-            }
-            List<org.cyclonedx.model.Dependency> allDependencies = 
bom.getDependencies();
-            fillFromBomLink(real, allDependencies);
-            if (sbomLink.getCreateBomExternalReference()
-                && !externalReferences.stream()
-                .anyMatch(e -> 
e.getType().equals(org.cyclonedx.model.ExternalReference.Type.BOM))) {
-                Resource sbom = sbomLink.iterator().next();
-                URLProvider up = sbom.as(URLProvider.class);
-                if (up != null) {
-                    ExternalReference e = new ExternalReference();
-                    e.setUrl(up.getURL().toExternalForm());
-                    
e.setType(org.cyclonedx.model.ExternalReference.Type.BOM.name());
-                    addConfiguredExternalReference(e);
-                }
-            }
-
-            if (!areDependenciesUnknown() && !dependencies.isEmpty()) {
-                List<org.cyclonedx.model.Component> additionalComponents = 
bom.getComponents();
-                if (additionalComponents != null) {
-                    return 
extractComponentsThatAreDirectDependencies(additionalComponents);
-                }
-            }
+            SbomLinkComponentResolver resolver = new 
SbomLinkComponentResolver(getProject(), sbomLink);
+            return resolver.resolve(this);
         }
 
         return Collections.emptyList();
@@ -769,31 +749,16 @@ public class Component extends DataType {
         return component;
     }
 
-    private List<Component> 
extractComponentsThatAreDirectDependencies(List<org.cyclonedx.model.Component> 
cs) {
-        List<Component> toReturn = new ArrayList<>();
-        for (org.cyclonedx.model.Component c : cs) {
-            Component dep = from(c, Collections.emptyList());
-            if (dependencies.stream().anyMatch(d -> 
Objects.equals(dep.getBomRef(), d.getBomRef()))) {
-                // only include "additional components" this component depends 
on directly.
-                // we don't want to resolve transitive dependencies 
automatically
-                dep.setUnknownDependencies(true);
-                toReturn.add(dep);
-            }
-        }
-        return toReturn;
-    }
-
-    private static Component from(
+    static Component from(
         org.cyclonedx.model.Component real,
         List<org.cyclonedx.model.Dependency> dependencies) {
         Component c = new Component();
-        c.fillFromBomLink(real, dependencies);
+        c.fillFrom(real, dependencies);
         return c;
     }
 
-    private void fillFromBomLink(
-        org.cyclonedx.model.Component real,
-        List<org.cyclonedx.model.Dependency> allDependencies) {
+    void fillFrom(org.cyclonedx.model.Component real,
+                  List<org.cyclonedx.model.Dependency> allDependencies) {
         if (type == null) {
             setType(ComponentType.from(real.getType()));
         }
@@ -878,13 +843,15 @@ public class Component extends DataType {
                                         .collect(Collectors.toList()));
             }
         }
-        if (dependencies.isEmpty() && allDependencies != null) {
-            fillDependenciesFromBomLink(allDependencies);
+        if (!areDependenciesUnknown()) {
+            fillDependencies(allDependencies);
         }
     }
 
-    private void 
fillDependenciesFromBomLink(List<org.cyclonedx.model.Dependency> 
allDependencies) {
-        setUnknownDependencies(true);
+    private void fillDependencies(List<org.cyclonedx.model.Dependency> 
allDependencies) {
+        if (dependencies.isEmpty()) {
+            setUnknownDependencies(true);
+        }
         org.cyclonedx.model.Dependency myDependencies = allDependencies
             .stream()
             .filter(d -> Objects.equals(d.getRef(), getBomRef()))
@@ -931,44 +898,6 @@ public class Component extends DataType {
         component.setHashes(BomUtils.calculateHashes(file, bomVersion));
     }
 
-    private Bom readLinkedSbom() throws IOException {
-        if (sbomLink.size() != 1) {
-            throw new BuildException("sbomLink requires exactly one nested 
resource");
-        }
-        Resource sbom = sbomLink.iterator().next();
-        logSbom(sbom);
-        try (InputStream data = sbom.getInputStream();
-             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
-            byte[] buf = new byte[4096];
-            int count = data.read(buf, 0, buf.length);
-            while (count >= 0) {
-                baos.write(buf, 0, count);
-                count = data.read(buf, 0, buf.length);
-            }
-            byte[] content = baos.toByteArray();
-            try {
-                Parser parser = BomParserFactory.createParser(content);
-                return parser.parse(content);
-            } catch (ParseException ex) {
-                throw new BuildException("failed to parse sbomlink " + 
sbom.getName(), ex);
-            }
-        }
-    }
-
-    private void logSbom(Resource r) {
-        String name = r.getName();
-        FileProvider fp = r.as(FileProvider.class);
-        if (fp != null) {
-            name = fp.getFile().getAbsolutePath();
-        } else {
-            URLProvider up = r.as(URLProvider.class);
-            if (up != null) {
-                name = up.getURL().toExternalForm();
-            }
-        }
-        log("reading SBOM from " + name, Project.MSG_VERBOSE);
-    }
-
     /**
      * Represents a dependency of a component.
      */
diff --git a/src/main/org/apache/ant/cyclonedx/SbomLinkComponentResolver.java 
b/src/main/org/apache/ant/cyclonedx/SbomLinkComponentResolver.java
new file mode 100644
index 0000000..4142921
--- /dev/null
+++ b/src/main/org/apache/ant/cyclonedx/SbomLinkComponentResolver.java
@@ -0,0 +1,158 @@
+/*
+ *  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
+ *
+ *      https://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.ant.cyclonedx;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.types.Resource;
+import org.apache.tools.ant.types.resources.FileProvider;
+import org.apache.tools.ant.types.resources.URLProvider;
+
+import org.cyclonedx.exception.ParseException;
+import org.cyclonedx.model.Bom;
+import org.cyclonedx.parsers.BomParserFactory;
+import org.cyclonedx.parsers.Parser;
+
+/**
+ * Reads a linked SBOM and configures the component containing the link.
+ *
+ * @since CycloneDX Antlib 0.2
+ */
+class SbomLinkComponentResolver {
+    private final Project project;
+    private final Component.SbomLink sbomLink;
+
+    SbomLinkComponentResolver(Project project, Component.SbomLink sbomLink) {
+        this.project = project;
+        this.sbomLink = sbomLink;
+    }
+
+    /**
+     * Configures the given component and only adds dependencies if the 
component doesn't already know about any
+     * dependencies itself.
+     *
+     * @param parent component the link applies to
+     * @return Components that are direct dependencies of the parent component
+     */
+    Collection<Component> resolve(Component parent) throws IOException {
+        Bom bom = readLinkedSbom();
+        if (bom.getMetadata() == null) {
+            throw new BuildException("referenced SBOM file lacks metadata");
+        }
+        org.cyclonedx.model.Component real = bom.getMetadata().getComponent();
+        if (real == null) {
+            throw new BuildException("referenced SBOM file lacks component");
+        }
+        List<org.cyclonedx.model.Dependency> allDependencies = 
bom.getDependencies();
+
+        // only add dependencies if the component doesn't already have any 
dependency configuration itself
+        if (!parent.areDependenciesUnknown()
+            && !parent.getDependencies().iterator().hasNext()
+            && allDependencies != null) {
+            parent.fillFrom(real, allDependencies);
+        } else {
+            parent.fillFrom(real, Collections.emptyList());
+        }
+
+        if (sbomLink.getCreateBomExternalReference()
+            && !parent.getExternalReferences().stream()
+            .anyMatch(e -> 
e.getType().equals(org.cyclonedx.model.ExternalReference.Type.BOM))) {
+            Resource sbom = sbomLink.iterator().next();
+            URLProvider up = sbom.as(URLProvider.class);
+            if (up != null) {
+                ExternalReference e = new ExternalReference();
+                e.setUrl(up.getURL().toExternalForm());
+                
e.setType(org.cyclonedx.model.ExternalReference.Type.BOM.name());
+                parent.addConfiguredExternalReference(e);
+            }
+        }
+
+        if (!parent.areDependenciesUnknown() && 
parent.getDependencies().iterator().hasNext()) {
+            List<org.cyclonedx.model.Component> additionalComponents = 
bom.getComponents();
+            if (additionalComponents != null) {
+                return extractComponentsThatAreDirectDependencies(parent, 
additionalComponents);
+            }
+        }
+
+        return Collections.emptyList();
+    }
+
+    private Bom readLinkedSbom() throws IOException {
+        if (sbomLink.size() != 1) {
+            throw new BuildException("sbomLink requires exactly one nested 
resource");
+        }
+        Resource sbom = sbomLink.iterator().next();
+        logSbom(sbom);
+        try (InputStream data = sbom.getInputStream();
+             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            byte[] buf = new byte[4096];
+            int count = data.read(buf, 0, buf.length);
+            while (count >= 0) {
+                baos.write(buf, 0, count);
+                count = data.read(buf, 0, buf.length);
+            }
+            byte[] content = baos.toByteArray();
+            try {
+                Parser parser = BomParserFactory.createParser(content);
+                return parser.parse(content);
+            } catch (ParseException ex) {
+                throw new BuildException("failed to parse sbomlink " + 
sbom.getName(), ex);
+            }
+        }
+    }
+
+    private void logSbom(Resource r) {
+        String name = r.getName();
+        FileProvider fp = r.as(FileProvider.class);
+        if (fp != null) {
+            name = fp.getFile().getAbsolutePath();
+        } else {
+            URLProvider up = r.as(URLProvider.class);
+            if (up != null) {
+                name = up.getURL().toExternalForm();
+            }
+        }
+        project.log("reading SBOM from " + name, Project.MSG_VERBOSE);
+    }
+
+    private List<Component> 
extractComponentsThatAreDirectDependencies(Component parent,
+            List<org.cyclonedx.model.Component> cs) {
+        List<Component> toReturn = new ArrayList<>();
+        List<Component.Dependency> dependencies = new ArrayList<>();
+        
parent.getDependencies().iterator().forEachRemaining(dependencies::add);
+        for (org.cyclonedx.model.Component c : cs) {
+            Component dep = Component.from(c, Collections.emptyList());
+            if (dependencies.stream().anyMatch(d -> 
Objects.equals(dep.getBomRef(), d.getBomRef()))) {
+                // only include "additional components" the parent component 
depends on directly.
+                // we don't want to resolve transitive dependencies 
automatically here
+                dep.setUnknownDependencies(true);
+                toReturn.add(dep);
+            }
+        }
+        return toReturn;
+    }
+}

Reply via email to