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 ac4767a  document the simple part of component and add some tests
ac4767a is described below

commit ac4767a2e11def1a2602c3280b03a93f6ab623f6
Author: Stefan Bodewig <[email protected]>
AuthorDate: Thu May 14 21:54:04 2026 +0200

    document the simple part of component and add some tests
---
 docs/component.html                                | 311 ++++++++++++++++++++
 docs/externalreferenceset.html                     |   2 +-
 docs/organization.html                             |   2 +-
 src/main/org/apache/ant/cyclonedx/Component.java   | 317 +++++++++++++++------
 .../org/apache/ant/cyclonedx/ComponentBomTask.java |   2 +-
 .../org/apache/ant/cyclonedx/Organization.java     |   4 +-
 src/tests/antunit/component-test.xml               | 273 ++++++++++++++++++
 src/tests/antunit/componentbom-test.xml            |  97 -------
 8 files changed, 823 insertions(+), 185 deletions(-)

diff --git a/docs/component.html b/docs/component.html
new file mode 100644
index 0000000..e64e743
--- /dev/null
+++ b/docs/component.html
@@ -0,0 +1,311 @@
+<!--
+   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.
+-->
+<html>
+  <head>
+    <meta http-equiv="Content-Language" content="en-us"></meta>
+    <link rel="stylesheet" type="text/css" href="style.css">
+    <title>Apache CycloneDX Ant Library - component</title>
+  </head>
+
+  <body>
+    <h2 id="component">component</h2>
+
+    <p>Components are central to CycloneDX SBOMs. There is <b>the</b>
+      component the whole SBOM is about - and there are more
+      components specified inside the SBOM as (transitive)
+      dependencies or parts of the other components.</p>
+
+    <p>The component elements can be used as top-level elements and be
+      given an id so they can be later referred to via
+      the <code>refid</code> attribute -
+      see <a href="https://ant.apache.org/manual/using.html#references";>the
+      Ant manual</a>. The can also be referred to by
+      a <a href="#dependency">dependency</a> of another component
+      via <code>componentRef</code>.</p>
+
+    <h3>Attributes</h3>
+
+    <table class="attr">
+      <tr>
+        <th scope="col">Attribute</th>
+        <th scope="col">Description</th>
+        <th scope="col">Required</th>
+      </tr>
+      <tr>
+        <td>name</td>
+        <td>The name of the component.</td>
+        <td>Yes</td>
+      </tr>
+      <tr>
+        <td>type</td>
+        <td>The type of the component. Valid types are defined by the
+          <a 
href="https://cyclonedx.org/docs/1.7/json/#metadata_tools_oneOf_i0_components_items_type";>CycloneDX
+          specification</a>.</td>
+        <td>No, the default is "library"</td>
+      </tr>
+      <tr>
+        <td>group</td>
+        <td>The group of the component.</td>
+        <td>No</td>
+      </tr>
+      <tr>
+        <td>version</td>
+        <td>The version of the component.</td>
+        <td>No</td>
+      </tr>
+      <tr>
+        <td>purl</td>
+        <td>The <a href="https://github.com/package-url/purl-spec";>Package-URL
+            (purl)</a> of the component.</td>
+        <td>No - if not set but <code>name</code>, <code>group</code>
+          and <code>version</code> are present an implicit purl is
+          calculated as <code>pkg:maven</code> purl from these
+          values.</td>
+      </tr>
+      <tr>
+        <td>bomRef</td>
+        <td>The bom-ref of the component.</td>
+        <td>No - if not set, the component's - potentially calculated
+          - purl will be used.</td>
+      </tr>
+      <tr>
+        <td>scope</td>
+        <td>The scope of the component. Valid scopes are defined by the
+          <a 
href="https://cyclonedx.org/docs/1.7/json/#metadata_tools_oneOf_i0_components_items_scope";>CycloneDX
+          specification</a>.</td>
+        <td>No, <code>scope</code> is prohibited for the main
+          component of the SBOM.</td>
+      </tr>
+      <tr>
+        <td>isExternal</td>
+        <td>Whether the component is external. The CycloneDX
+          Specification says:
+          <cite>An external component is one that is not part of an
+            assembly, but is expected to be provided by the
+            environment, regardless of the component's
+            scope.</cite><br/>
+          This attribute doesn't have any effect as long as CycloneDX
+          Core doesn't support version 1.7 of the specification.
+        </td>
+        <td>No, <code>isExternal</code> must not be <code>true</code>
+          for the main component of the SBOM.</td>
+      </tr>
+      <tr>
+        <td>description</td>
+        <td>The description of the component.</td>
+        <td>No</td>
+      </tr>
+      <tr>
+        <td>publisher</td>
+        <td>The publisher of the component.</td>
+        <td>No</td>
+      </tr>
+      <tr>
+        <td>copyright</td>
+        <td>The copyright of the component.</td>
+        <td>No</td>
+      </tr>
+      <tr>
+        <td>mimetype</td>
+        <td>The MIME-type of the component.</td>
+        <td>No</td>
+      </tr>
+      <tr>
+        <td>manufacturerIsSupplier</td>
+        <td>Whether the
+          nested <a href="#manufacturer">manufacturer</a> shall be used
+          as supplier as well.</td>
+        <td>No - defaults to <code>false</code>. Must not
+          be <code>true</code> if a
+          nested <a href="#supplier">supplier</a> exists.</td>
+      </tr>
+      <tr>
+        <td>unknownDependencies</td>
+        <td>Whether the dependencies of this component are unknown. If
+          set to <code>false</code> then a component without any
+          nested
+          <a href="#dependency">dependency</a> elements is treated as
+          having no dependencies at all. </td>
+        <td>No - defaults to <code>false</code>.</td>
+      </tr>
+    </table>
+
+    <h3>Nested elements</h3>
+
+    <h4>any
+      file-system <a 
href="https://ant.apache.org/manual/Types/resources.html";>resource</a></h4>
+
+    <p>At most one nested resource specifies the file the component
+      describes. This is required if you want to include hashes for
+      the component in your SBOM.</p>
+
+    <h4 id="manufacturer">manufacturer</h4>
+
+    <p>At most one nested <a href="organization.html">organization</a>
+      specifies the manufacturer of the component.</p>
+
+    <h4 id="supplier">supplier</h4>
+
+    <p>At most one nested <a href="organization.html">organization</a>
+      specifies the supplier of the component.</p>
+
+    <h4>author</h4>
+
+    <p>Adds an author to the component.</p>
+
+    <h5>Attributes</h5>
+
+    <table class="attr">
+      <tr>
+        <th scope="col">Attribute</th>
+        <th scope="col">Description</th>
+        <th scope="col">Required</th>
+      </tr>
+      <tr>
+        <td>bomRef</td>
+        <td>The bom-ref of the author.</td>
+        <td>No</td>
+      </tr>
+      <tr>
+        <td>name</td>
+        <td>The name of the author.</td>
+        <td>No</td>
+      </tr>
+      <tr>
+        <td>email</td>
+        <td>The email of the author.</td>
+        <td>No</td>
+      </tr>
+      <tr>
+        <td>phone</td>
+        <td>The phone of the author.</td>
+        <td>No</td>
+      </tr>
+    </table>
+
+    <h4>tag</h4>
+
+    <p>Adds a tag to the component. Tags haven't got any attributes or
+      nested elements, the nested text is used as tag.</p>
+
+    <h4>property</h4>
+
+    <p>Adds a property to the component.</p>
+
+    <h5>Attributes</h5>
+
+    <table class="attr">
+      <tr>
+        <th scope="col">Attribute</th>
+        <th scope="col">Description</th>
+        <th scope="col">Required</th>
+      </tr>
+      <tr>
+        <td>name</td>
+        <td>The name of the property.</td>
+        <td>No</td>
+      </tr>
+      <tr>
+        <td>value</td>
+        <td>The value of the property.</td>
+        <td>No</td>
+      </tr>
+    </table>
+
+    <h4>license</h4>
+
+    <p>A nested <a href="license.html">license</a> specifies the
+      license information of the component.</p>
+
+    <h4>externalReference</h4>
+
+    <p>A
+      nested <a 
href="externalreferenceset.html#externalReference">externalReference</a>
+      specifies an external reference for the component.</p>
+
+    <h4>externalReferenceSet</h4>
+
+    <p>A
+      nested <a href="externalreferenceset.html">externalreferenceset</a>
+      specifies external references for the component.</p>
+
+    <h4 id="dependency">dependency</h4>
+
+    <p>Adds a dependency to the component.</p>
+
+    <h5>Attributes</h5>
+
+    <table class="attr">
+      <tr>
+        <th scope="col">Attribute</th>
+        <th scope="col">Description</th>
+        <th scope="col">Required</th>
+      </tr>
+      <tr>
+        <td>bomRef</td>
+        <td>References the dependency by
+          its <code>bom-ref</code>.</td>
+        <td rowspan="2">Exactly one of the two.</td>
+      </tr>
+      <tr>
+        <td>componentRef</td>
+        <td>References the dependency by its Ant <code>id</code>
+          attribute. The referenced component must have
+          a <code>bom-ref</code>.</td>
+      </tr>
+    </table>
+
+    <h3>Examples</h3>
+
+    <p>Below is a component that could describe this Antlib.</p>
+
+    <pre>
+      &lt;cdx:component
+          name="ant"
+          group="org.apache.ant"
+          version="1.10.17"
+          isExternal="true"
+          unknownDependencies="true"
+          id="ant"
+          xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+        &lt;manufacturer refid="ant-team"/>
+        &lt;license refid="apache-2"/>
+        &lt;externalReference
+            type="VCS"
+            url="https://github.com/apache/ant"/>
+        &lt;externalReference
+            type="WEBSITE"
+            url="https://ant.apache.org/"/>
+      &lt;/cdx:component>
+      &lt;cdx:component
+          name="ant-cyclonedx"
+          group="org.apache.ant"
+          version="0.1"
+          description="Apache CycloneDX Antlib"
+          publisher="The Apache Software Foundation"
+          manufacturerIsSupplier="true"
+          xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+        &lt;file file="ant-antlibs-cyclonedx-0.1.jar"/>
+        &lt;manufacturer refid="ant-team"/>
+        &lt;license refid="apache-2"/>
+        &lt;externalReferenceSet refid="antlib-ext-refs"/>
+        &lt;dependency componentRef="ant"/>
+        &lt;dependency 
bomRef="pkg:maven/org.cyclonedx/[email protected]?type=jar"/>
+      &lt;/cdx:component>
+    </pre>
+  </body>
+</html>
diff --git a/docs/externalreferenceset.html b/docs/externalreferenceset.html
index ed51828..a8352dc 100644
--- a/docs/externalreferenceset.html
+++ b/docs/externalreferenceset.html
@@ -37,7 +37,7 @@ <h2 id="externalreferenceset">externalreferenceset</h2>
 
     <h3>Nested elements</h3>
 
-    <h4>externalReference</h4>
+    <h4 id="externalReference">externalReference</h4>
 
     <p>Represents a single external reference to be added to the
       BOM.</p>
diff --git a/docs/organization.html b/docs/organization.html
index 2355747..d3fbf2e 100644
--- a/docs/organization.html
+++ b/docs/organization.html
@@ -25,7 +25,7 @@
     <h2 id="organization">organization</h2>
 
     <p>Organizations can be attached to components as well as the SBOM
-      itself using several roles (manufacturer, supplier, publisher)
+      itself using several roles (manufacturer, supplier, ...)
       in CycloneDX SBOMs.</p>
 
     <p>The organization elements can be used as top-level elements and
diff --git a/src/main/org/apache/ant/cyclonedx/Component.java 
b/src/main/org/apache/ant/cyclonedx/Component.java
index e44b0c8..6b06df2 100644
--- a/src/main/org/apache/ant/cyclonedx/Component.java
+++ b/src/main/org/apache/ant/cyclonedx/Component.java
@@ -14,8 +14,8 @@ import java.util.stream.Collectors;
 
 import org.apache.tools.ant.BuildException;
 import org.apache.tools.ant.Project;
-import org.apache.tools.ant.ProjectComponent;
 import org.apache.tools.ant.types.DataType;
+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;
@@ -32,6 +32,17 @@ import org.cyclonedx.parsers.BomParserFactory;
 import org.cyclonedx.parsers.Parser;
 import org.cyclonedx.util.BomUtils;
 
+/**
+ * The central concept of an SBOM which describes components and their 
dependencies.
+ *
+ * <p>The CycloneDX specification supports more information for a
+ * component than this type currently exposes.</p>
+ *
+ * <p>This class is a type exposed by this Ant Library. When using the
+ * inherited {@code refid} attribute it can reference an instance
+ * defined previously - in which case no child elements or other
+ * attributes are allowed.</p>
+ */
 public class Component extends DataType {
     private Resource resource;
     private org.cyclonedx.model.Component.Type type = 
org.cyclonedx.model.Component.Type.LIBRARY;
@@ -60,6 +71,13 @@ public class Component extends DataType {
     private String mimeType;
     private Union sbomLink;
 
+    /**
+     * Sets the resource the component is about.
+     *
+     * <p>At most one resource can be set. Without a nested resource
+     * the component will not have any "hashes" when written to the
+     * SBOM.</p>
+     */
     public void add(Resource resource) {
         checkChildrenAllowed();
         if (this.resource != null) {
@@ -68,62 +86,93 @@ public class Component extends DataType {
         this.resource = resource;
     }
 
+    /**
+     * Sets the type of the component.
+     *
+     * <p>Defaults to "library".</p>
+     */
     public void setType(ComponentType type) {
         checkAttributesAllowed();
         this.type = type.getType();
     }
 
+    /**
+     * Sets the name of the component.
+     */
     public void setName(String name) {
         checkAttributesAllowed();
         this.name = name;
     }
 
-    public String getName() {
-        if (isReference()) {
-            return getRef().getName();
-        }
-        dieOnCircularReference();
-        return name;
-    }
-
+    /**
+     * Sets the group of the component.
+     */
     public void setGroup(String group) {
         checkAttributesAllowed();
         this.group = group;
     }
 
-    public String getGroup() {
-        if (isReference()) {
-            return getRef().getGroup();
-        }
-        dieOnCircularReference();
-        return group;
-    }
-
+    /**
+     * Sets the version of the component.
+     */
     public void setVersion(String version) {
         checkAttributesAllowed();
         this.version = version;
     }
 
+    /**
+     * Sets the Package-URL (purl) of the component.
+     */
+    public void setPurl(String purl) {
+        checkAttributesAllowed();
+        this.purl = purl;
+    }
+
+    /**
+     * Sets the bom-ref of the component.
+     */
+    public void setBomRef(String bomRef) {
+        checkAttributesAllowed();
+        this.bomRef = bomRef;
+    }
+
+    /**
+     * Sets the decription of the component.
+     */
     public void setDescription(String description) {
         checkAttributesAllowed();
         this.description = description;
     }
 
+    /**
+     * Sets the publisher of the component.
+     */
     public void setPublisher(String publisher) {
         checkAttributesAllowed();
         this.publisher = publisher;
     }
 
+    /**
+     * Sets the copyright of the component.
+     */
     public void setCopyright(String copyright) {
         checkAttributesAllowed();
         this.copyright = copyright;
     }
 
+    /**
+     * Sets the mime-type of the component.
+     */
     public void setMimeType(String mimeType) {
         checkAttributesAllowed();
         this.mimeType = mimeType;
     }
 
+    /**
+     * Sets the manufacturer of the component.
+     *
+     * <p>At most one manufacturer can be set.</p>
+     */
     public void addManufacturer(Organization manufacturer) {
         checkChildrenAllowed();
         if (this.manufacturer != null) {
@@ -132,6 +181,11 @@ public class Component extends DataType {
         this.manufacturer = manufacturer;
     }
 
+    /**
+     * Sets the supplier of the component.
+     *
+     * <p>At most one supplier can be set.</p>
+     */
     public void addSupplier(Organization supplier) {
         checkChildrenAllowed();
         if (this.supplier != null) {
@@ -140,36 +194,152 @@ public class Component extends DataType {
         this.supplier = supplier;
     }
 
+    /**
+     * Adds an author to the component.
+     */
     public void addAuthor(OrganizationalContact author) {
         checkChildrenAllowed();
         authors.add(author);
     }
 
+    /**
+     * Adds a tag to the component.
+     */
     public void addTag(Tag tag) {
         checkChildrenAllowed();
         tags.add(tag);
     }
 
+    /**
+     * Adds a property to the component.
+     */
     public void addProperty(Property property) {
         checkChildrenAllowed();
         properties.add(property);
     }
 
+    /**
+     * If set to {@code true} the manufacturer will also be used to
+     * provide the supplier information.
+     */
     public void setManufacturerIsSupplier(boolean manufacturerIsSupplier) {
         checkAttributesAllowed();
         this.manufacturerIsSupplier = manufacturerIsSupplier;
     }
 
+    /**
+     * Adds a license to this component.
+     */
     public void addConfiguredLicense(License l) {
         checkChildrenAllowed();
         licenses.add(l.toCycloneDxLicense());
     }
 
-    public void setPurl(String purl) {
+    /**
+     * Adds an external reference to the component.
+     */
+    public void addConfiguredExternalReference(ExternalReference ref) {
+        checkChildrenAllowed();
+        externalReferences.add(ref.toCycloneDxExternalReference());
+    }
+
+    /**
+     * Adds a set of external references to the component.
+     */
+    public void addConfiguredExternalReferenceSet(ExternalReferenceSet set) {
+        checkChildrenAllowed();
+        externalReferences.addAll(set.getExternalReferences());
+    }
+
+    /**
+     * Sets the scope of this component.
+     *
+     * <p>Must not be set for the main component of the SBOM.</p>
+     */
+    public void setScope(ComponentScope scope) {
         checkAttributesAllowed();
-        this.purl = purl;
+        this.scope = scope.getScope();
+    }
+
+    /**
+     * Sets whether the component is external.
+     *
+     * <p>The CycloneDX Specification says: An external component is
+     * one that is not part of an assembly, but is expected to be
+     * provided by the environment, regardless of the component's
+     * scope.</p>
+     *
+     * <p>Must not be set to {@code true} for the main component of
+     * the SBOM.</p>
+     *
+     * <p>Right now this attribute has no effect until the CycloneDX
+     * core library supports the specification version 1.7.</p>
+     */
+    public void setIsExternal(boolean isExternal) {
+        checkAttributesAllowed();
+        this.isExternal = isExternal;
+    }
+
+    /**
+     * Adds a dependency to this component.
+     */
+    public void addDependency(Dependency d) {
+        checkChildrenAllowed();
+        dependencies.add(d);
     }
 
+    /**
+     * Sets whether the dependencies of this component are unknown.
+     *
+     * <p>This flag is needed to be able to tell dependencies with
+     * unknown dependencies from components without any
+     * dependencies.</p>
+     */
+    public void setUnknownDependencies(boolean unknownDependencies) {
+        checkAttributesAllowed();
+        this.unknownDependencies = unknownDependencies;
+    }
+
+    public void addComponent(Component c) {
+        checkChildrenAllowed();
+        nestedComponents.add(c);
+        setChecked(false);
+    }
+
+    public Union createSbomLink() {
+        checkChildrenAllowed();
+        return sbomLink == null ? (sbomLink = new Union()) : sbomLink;
+    }
+
+    /**
+     * Gets the name of the component.
+     */
+    public String getName() {
+        if (isReference()) {
+            return getRef().getName();
+        }
+        dieOnCircularReference();
+        return name;
+    }
+
+    /**
+     * Gets the group of the component.
+     */
+    public String getGroup() {
+        if (isReference()) {
+            return getRef().getGroup();
+        }
+        dieOnCircularReference();
+        return group;
+    }
+
+    /**
+     * Gets the Package-URL (purl) of the component.
+     *
+     * @return the value set with {@see #setPurl} or a Maven purl
+     * derived from name, group and version if all three or set - or
+     * {@code null otherwise}.
+     */
     public String getPurl() {
         if (isReference()) {
             return getRef().getPurl();
@@ -184,11 +354,11 @@ public class Component extends DataType {
         return null;
     }
 
-    public void setBomRef(String bomRef) {
-        checkAttributesAllowed();
-        this.bomRef = bomRef;
-    }
-
+    /**
+     * Gets the bom-ref of the component.
+     *
+     * @return the value set with {@see #setBomRef} or the result of {@see 
#getPurl}.
+     */
     public String getBomRef() {
         if (isReference()) {
             return getRef().getBomRef();
@@ -200,31 +370,9 @@ public class Component extends DataType {
         return bomRef;
     }
 
-    public void addConfiguredExternalReference(ExternalReference ref) {
-        checkChildrenAllowed();
-        externalReferences.add(ref.toCycloneDxExternalReference());
-    }
-
-    public void addConfiguredExternalReferenceSet(ExternalReferenceSet set) {
-        checkChildrenAllowed();
-        externalReferences.addAll(set.getExternalReferences());
-    }
-
-    public void setScope(ComponentScope scope) {
-        checkAttributesAllowed();
-        this.scope = scope.getScope();
-    }
-
-    public void setIsExternal(boolean isExternal) {
-        checkAttributesAllowed();
-        this.isExternal = isExternal;
-    }
-
-    public void addDependency(Dependency d) {
-        checkChildrenAllowed();
-        dependencies.add(d);
-    }
-
+    /**
+     * Gets the dependencies of the component.
+     */
     public Iterable<Dependency> getDependencies() {
         if (isReference()) {
             return getRef().getDependencies();
@@ -233,10 +381,16 @@ public class Component extends DataType {
         return dependencies;
     }
 
-    public void addComponent(Component c) {
-        checkChildrenAllowed();
-        nestedComponents.add(c);
-        setChecked(false);
+    /**
+     * @return the value set with {@link #setUnknownDependencies}
+     * or {@code false}.
+     */
+    public boolean areDependenciesUnknown() {
+        if (isReference()) {
+            return getRef().areDependenciesUnknown();
+        }
+        dieOnCircularReference();
+        return unknownDependencies;
     }
 
     public List<Component> getNestedComponents() {
@@ -253,16 +407,6 @@ public class Component extends DataType {
         return result;
     }
 
-    public void setUnknownDependencies(boolean unknownDependencies) {
-        checkAttributesAllowed();
-        this.unknownDependencies = unknownDependencies;
-    }
-
-    public Union createSbomLink() {
-        checkChildrenAllowed();
-        return sbomLink == null ? (sbomLink = new Union()) : sbomLink;
-    }
-
     public boolean hasSbomLink() {
         if (isReference()) {
             return getRef().hasSbomLink();
@@ -271,14 +415,6 @@ public class Component extends DataType {
         return sbomLink != null;
     }
 
-    public boolean areDependenciesUnknown() {
-        if (isReference()) {
-            return getRef().areDependenciesUnknown();
-        }
-        dieOnCircularReference();
-        return unknownDependencies;
-    }
-
     public Collection<Component> resolve() throws IOException {
         if (isReference()) {
             return getRef().resolve();
@@ -354,7 +490,7 @@ public class Component extends DataType {
         return Collections.emptyList();
     }
 
-    public org.cyclonedx.model.Component toMainCycloneDxComponent(Version 
bomVersion)
+    org.cyclonedx.model.Component toMainCycloneDxComponent(Version bomVersion)
         throws IOException {
         if (isReference()) {
             return getRef().toMainCycloneDxComponent(bomVersion);
@@ -362,10 +498,13 @@ public class Component extends DataType {
         if (isExternal) {
             throw new BuildException("isExternal can not be true for the main 
bom component");
         }
+        if (scope != null) {
+            throw new BuildException("scope must not be set for the main bom 
component");
+        }
         return toCycloneDxComponent(bomVersion);
     }
 
-    public org.cyclonedx.model.Component 
toAdditionalCycloneDxComponent(Version bomVersion)
+    org.cyclonedx.model.Component toAdditionalCycloneDxComponent(Version 
bomVersion)
         throws IOException {
         if (isReference()) {
             return getRef().toAdditionalCycloneDxComponent(bomVersion);
@@ -377,7 +516,7 @@ public class Component extends DataType {
         return component;
     }
 
-    public static Component from(org.cyclonedx.model.Component real) {
+    private static Component from(org.cyclonedx.model.Component real) {
         Component c = new Component();
         c.fillFrom(real);
         return c;
@@ -548,15 +687,24 @@ public class Component extends DataType {
         component.setHashes(BomUtils.calculateHashes(file, bomVersion));
     }
 
-    public static class Dependency extends ProjectComponent {
+    /**
+     * Represents a dependency of a component.
+     */
+    public static class Dependency {
         private String bomRef;
-        private String componentRef;
+        private Reference componentRef;
 
+        /**
+         * Identifies the dependency by its bom-ref.
+         */
         public void setBomRef(String bomRef) {
             this.bomRef = bomRef;
         }
 
-        public void setComponentRef(String componentRef) {
+        /**
+         * Identifies the dependency by its Ant {@code id} attribute.
+         */
+        public void setComponentRef(Reference componentRef) {
             this.componentRef = componentRef;
         }
 
@@ -571,10 +719,7 @@ public class Component extends DataType {
                 return bomRef;
             }
 
-            Object component = getProject().getReference(componentRef);
-            if (component == null) {
-                throw new BuildException("componentRef '" + componentRef + "' 
is unknown");
-            }
+            Object component = componentRef.getReferencedObject();
             if (component instanceof Component) {
                 String b = ((Component) component).getBomRef();
                 if (b == null) {
@@ -585,21 +730,27 @@ public class Component extends DataType {
             throw new BuildException("componentRef '" + componentRef + "' 
doesn't refer to a component");
         }
 
-        public static Dependency from(org.cyclonedx.model.Dependency 
dependency) {
+        static Dependency from(org.cyclonedx.model.Dependency dependency) {
             Dependency d = new Dependency();
             d.setBomRef(dependency.getRef());
             return d;
         }
     }
 
+    /**
+     * Represents a tag.
+     */
     public static class Tag {
         private String tag;
 
+        /**
+         * Sets the tag value.
+         */
         public void addText(String text) {
             tag = text;
         }
 
-        public String getTag() {
+        String getTag() {
             return tag;
         }
     }
diff --git a/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java 
b/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
index c0bc776..7d41bee 100644
--- a/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
+++ b/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
@@ -205,7 +205,7 @@ public class ComponentBomTask extends Task {
             }
         }
 
-        if (component.getBomRef() != null) {
+        if (component.getBomRef() != null && 
!component.areDependenciesUnknown()) {
             Dependency dep = new Dependency(component.getBomRef());
             for (Component.Dependency d : component.getDependencies()) {
                 String br = d.getBomRef();
diff --git a/src/main/org/apache/ant/cyclonedx/Organization.java 
b/src/main/org/apache/ant/cyclonedx/Organization.java
index 2ab4e4e..962ad80 100644
--- a/src/main/org/apache/ant/cyclonedx/Organization.java
+++ b/src/main/org/apache/ant/cyclonedx/Organization.java
@@ -9,8 +9,8 @@ import org.apache.tools.ant.types.resources.URLResource;
 import org.cyclonedx.model.OrganizationalEntity;
 
 /**
- * Organization appears as "manufacturer", "publisher" or "supplier"
- * of components or the SBOM itself.
+ * Organization appears as "manufacturer" or "supplier" of components
+ * or the SBOM itself.
  *
  * <p>The CycloneDX specification supports more information for an
  * organization than this type currently exposes.</p>
diff --git a/src/tests/antunit/component-test.xml 
b/src/tests/antunit/component-test.xml
new file mode 100644
index 0000000..5835cd2
--- /dev/null
+++ b/src/tests/antunit/component-test.xml
@@ -0,0 +1,273 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<project name="component-test" default="antunit">
+
+  <import file="shared.xml" />
+
+  <target name="testComponentNameIsRequired">
+    <au:expectfailure expectedMessage="component name is required"
+        xmlns:au="antlib:org.apache.ant.antunit">
+      <cdx:componentbom outputdirectory="${output}" format="xml"
+                        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+        <component/>
+      </cdx:componentbom>
+    </au:expectfailure>
+  </target>
+
+  <target name="testComponentsDetectCircularReferences">
+    <au:expectfailure expectedMessage="This data type contains a circular 
reference"
+        xmlns:au="antlib:org.apache.ant.antunit">
+      <cdx:componentbom outputdirectory="${output}" format="xml"
+                        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+        <component name="level1" id="top">
+          <component name="level2">
+            <component refid="top"/>
+          </component>
+        </component>
+      </cdx:componentbom>
+    </au:expectfailure>
+  </target>
+
+  <target name="testMinimalComponentData">
+    <cdx:componentbom outputdirectory="${output}" format="xml"
+                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="testname"/>
+    </cdx:componentbom>
+    <xmlproperty file="${output}/bom.xml"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.name"
+        value="testname"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component(type)"
+        value="library"/>
+  </target>
+
+  <target name="testComponentTypeCanBeSet">
+    <cdx:componentbom outputdirectory="${output}" format="xml"
+                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="testname" type="APPLICATION"/>
+    </cdx:componentbom>
+    <xmlproperty file="${output}/bom.xml"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component(type)"
+        value="application"/>
+  </target>
+
+  <target name="testComponentTypeCanBeSetInLowerCase">
+    <cdx:componentbom outputdirectory="${output}" format="xml"
+                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="testname" type="application"/>
+    </cdx:componentbom>
+    <xmlproperty file="${output}/bom.xml"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component(type)"
+        value="application"/>
+  </target>
+
+  <target name="testCalculatesPurlAndBomRef">
+    <cdx:componentbom outputdirectory="${output}" format="xml"
+                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="testname" group="org.example" version="1.0"/>
+    </cdx:componentbom>
+    <xmlproperty file="${output}/bom.xml"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.purl"
+        value="pkg:maven/org.example/[email protected]?type=jar"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component(bom-ref)"
+        value="pkg:maven/org.example/[email protected]?type=jar"/>
+  </target>
+
+  <target name="testPurlAndBomRefCanBeSet">
+    <cdx:componentbom outputdirectory="${output}" format="xml"
+                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="testname" group="org.example" version="1.0"
+                 purl="foo" bomRef="bar"/>
+    </cdx:componentbom>
+    <xmlproperty file="${output}/bom.xml"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.purl"
+        value="foo"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component(bom-ref)"
+        value="bar"/>
+  </target>
+
+  <target name="testScopeIsProhibitedForMainComponent">
+    <au:expectfailure expectedMessage="scope must not be set for the main bom 
component"
+        xmlns:au="antlib:org.apache.ant.antunit">
+      <cdx:componentbom outputdirectory="${output}" format="xml"
+                        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+        <component name="foo" scope="optional"/>
+      </cdx:componentbom>
+    </au:expectfailure>
+  </target>
+
+  <target name="testIsExternalTrueIsProhibitedForMainComponent">
+    <au:expectfailure expectedMessage="isExternal can not be true for the main 
bom component"
+        xmlns:au="antlib:org.apache.ant.antunit">
+      <cdx:componentbom outputdirectory="${output}" format="xml"
+                        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+        <component name="foo" isExternal="true"/>
+      </cdx:componentbom>
+    </au:expectfailure>
+  </target>
+
+  <target name="testIsExternalFalseIsPermittedForMainComponent">
+    <cdx:componentbom outputdirectory="${output}" format="xml"
+                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="foo" isExternal="false"/>
+    </cdx:componentbom>
+  </target>
+
+  <target name="testScopeCanBeSetForAdditionalComponent">
+    <cdx:componentbom outputdirectory="${output}" format="xml"
+                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="foo"/>
+      <additionalComponent name="bar" scope="optional"/>
+    </cdx:componentbom>
+    <xmlproperty file="${output}/bom.xml"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.components.component.scope"
+        value="optional"/>
+  </target>
+
+  <target name="testScopeCanBeSetInUpperCaseForAdditionalComponent">
+    <cdx:componentbom outputdirectory="${output}" format="xml"
+                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="foo"/>
+      <additionalComponent name="bar" scope="OPTIONAL"/>
+    </cdx:componentbom>
+    <xmlproperty file="${output}/bom.xml"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.components.component.scope"
+        value="optional"/>
+  </target>
+
+  <target name="testIsExternalCanBeSetForAdditionalComponent">
+    <cdx:componentbom outputdirectory="${output}" format="xml"
+                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="foo"/>
+      <additionalComponent name="bar" isExternal="true"/>
+    </cdx:componentbom>
+  </target>
+
+  <target name="testManufacturerIsNotCopiedToSupplierByDefault">
+    <cdx:componentbom outputdirectory="${output}" format="xml"
+                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="foo">
+        <manufacturer name="Example">
+          <url url="https://example.com/"/>
+        </manufacturer>
+      </component>
+    </cdx:componentbom>
+    <xmlproperty file="${output}/bom.xml"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.manufacturer.name"
+        value="Example"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.manufacturer.url"
+        value="https://example.com/"/>
+    <au:fail message="expected bom.metadata.components.component.supplier.name 
to not be set"
+        xmlns:au="antlib:org.apache.ant.antunit">
+      <not>
+        <isset property="bom.metadata.component.supplier.name"/>
+      </not>
+    </au:fail>
+  </target>
+
+  <target name="testManufacturerIsCopiedToSupplierWhenRequested">
+    <cdx:componentbom outputdirectory="${output}" format="xml"
+                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="foo" manufacturerIsSupplier="true">
+        <manufacturer name="Example">
+          <url url="https://example.com/"/>
+        </manufacturer>
+      </component>
+    </cdx:componentbom>
+    <xmlproperty file="${output}/bom.xml"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.manufacturer.name"
+        value="Example"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.manufacturer.url"
+        value="https://example.com/"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.supplier.name"
+        value="Example"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.supplier.url"
+        value="https://example.com/"/>
+  </target>
+
+  <target name="testManufacturerIsSupplierMustNotBeTrueWhenSupplierIsPresent">
+    <au:expectfailure expectedMessage="component with supplier can't use 
manufacturer as supplier"
+        xmlns:au="antlib:org.apache.ant.antunit">
+      <cdx:componentbom outputdirectory="${output}" format="xml"
+                        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+        <component name="foo" manufacturerIsSupplier="true">
+          <manufacturer name="Example">
+            <url url="https://example.com/"/>
+          </manufacturer>
+          <supplier name="Example 2">
+            <url url="https://example.org/"/>
+          </supplier>
+        </component>
+      </cdx:componentbom>
+    </au:expectfailure>
+  </target>
+
+  <target name="testDependenciesAreEmptyByDefault">
+    <cdx:componentbom outputdirectory="${output}" format="json"
+                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="foo" bomRef="foo"/>
+    </cdx:componentbom>
+    <au:assertResourceContains
+        xmlns:au="antlib:org.apache.ant.antunit"
+        resource="${output}/bom.json"
+        value='"dependsOn" : [ ]'/>
+  </target>
+
+  <target name="testDependenciesAreNotPresentIfUnknown">
+    <cdx:componentbom outputdirectory="${output}" format="json"
+                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="foo" bomRef="foo" unknownDependencies="true"/>
+    </cdx:componentbom>
+    <copy todir="/tmp" file="${output}/bom.json"/>
+    <au:assertResourceContains
+        xmlns:au="antlib:org.apache.ant.antunit"
+        resource="${output}/bom.json"
+        value='"dependencies" : [ ]'/>
+  </target>
+</project>
diff --git a/src/tests/antunit/componentbom-test.xml 
b/src/tests/antunit/componentbom-test.xml
index 431c578..bcc804e 100644
--- a/src/tests/antunit/componentbom-test.xml
+++ b/src/tests/antunit/componentbom-test.xml
@@ -239,30 +239,6 @@
     </au:expectfailure>
   </target>
 
-  <target name="testComponentNameIsRequired">
-    <au:expectfailure expectedMessage="component name is required"
-        xmlns:au="antlib:org.apache.ant.antunit">
-      <cdx:componentbom outputdirectory="${output}" format="xml"
-                        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
-        <component/>
-      </cdx:componentbom>
-    </au:expectfailure>
-  </target>
-
-  <target name="testComponentsDetectCircularReferences">
-    <au:expectfailure expectedMessage="This data type contains a circular 
reference"
-        xmlns:au="antlib:org.apache.ant.antunit">
-      <cdx:componentbom outputdirectory="${output}" format="xml"
-                        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
-        <component name="level1" id="top">
-          <component name="level2">
-            <component refid="top"/>
-          </component>
-        </component>
-      </cdx:componentbom>
-    </au:expectfailure>
-  </target>
-
   <target name="testSpecVersionAsVersionString">
     <cdx:componentbom
         outputdirectory="${output}"
@@ -291,79 +267,6 @@
         value='"specVersion" : "1.6"'/>
   </target>
 
-  <target name="testMinimalComponentData">
-    <cdx:componentbom outputdirectory="${output}" format="xml"
-                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
-      <component name="testname"/>
-    </cdx:componentbom>
-    <xmlproperty file="${output}/bom.xml"/>
-    <au:assertPropertyEquals
-        xmlns:au="antlib:org.apache.ant.antunit"
-        name="bom.metadata.component.name"
-        value="testname"/>
-    <au:assertPropertyEquals
-        xmlns:au="antlib:org.apache.ant.antunit"
-        name="bom.metadata.component(type)"
-        value="library"/>
-  </target>
-
-  <target name="testComponentTypeCanBeSet">
-    <cdx:componentbom outputdirectory="${output}" format="xml"
-                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
-      <component name="testname" type="APPLICATION"/>
-    </cdx:componentbom>
-    <xmlproperty file="${output}/bom.xml"/>
-    <au:assertPropertyEquals
-        xmlns:au="antlib:org.apache.ant.antunit"
-        name="bom.metadata.component(type)"
-        value="application"/>
-  </target>
-
-  <target name="testComponentTypeCanBeSetInLowerCase">
-    <cdx:componentbom outputdirectory="${output}" format="xml"
-                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
-      <component name="testname" type="application"/>
-    </cdx:componentbom>
-    <xmlproperty file="${output}/bom.xml"/>
-    <au:assertPropertyEquals
-        xmlns:au="antlib:org.apache.ant.antunit"
-        name="bom.metadata.component(type)"
-        value="application"/>
-  </target>
-
-  <target name="testCalculatesPurlAndBomRef">
-    <cdx:componentbom outputdirectory="${output}" format="xml"
-                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
-      <component name="testname" group="org.example" version="1.0"/>
-    </cdx:componentbom>
-    <xmlproperty file="${output}/bom.xml"/>
-    <au:assertPropertyEquals
-        xmlns:au="antlib:org.apache.ant.antunit"
-        name="bom.metadata.component.purl"
-        value="pkg:maven/org.example/[email protected]?type=jar"/>
-    <au:assertPropertyEquals
-        xmlns:au="antlib:org.apache.ant.antunit"
-        name="bom.metadata.component(bom-ref)"
-        value="pkg:maven/org.example/[email protected]?type=jar"/>
-  </target>
-
-  <target name="testPurlAndBomRefCanBeSet">
-    <cdx:componentbom outputdirectory="${output}" format="xml"
-                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
-      <component name="testname" group="org.example" version="1.0"
-                 purl="foo" bomRef="bar"/>
-    </cdx:componentbom>
-    <xmlproperty file="${output}/bom.xml"/>
-    <au:assertPropertyEquals
-        xmlns:au="antlib:org.apache.ant.antunit"
-        name="bom.metadata.component.purl"
-        value="foo"/>
-    <au:assertPropertyEquals
-        xmlns:au="antlib:org.apache.ant.antunit"
-        name="bom.metadata.component(bom-ref)"
-        value="bar"/>
-  </target>
-
   <target name="testMaximalComponentData">
     <checksum property="ant.file.sha256" file="${ant.file}" 
algorithm="SHA-256"/>
     <cdx:componentbom outputdirectory="${output}" format="xml"


Reply via email to