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

commit caad2b677afb55b3600188106224295e52525028
Author: Stefan Bodewig <[email protected]>
AuthorDate: Thu Jun 4 14:55:51 2026 +0200

    introduce propertyset
---
 changes.xml                                        |   8 ++
 docs/component.html                                |   9 +-
 docs/index.html                                    |   1 +
 docs/propertyset.html                              |  87 +++++++++++++++
 src/main/org/apache/ant/cyclonedx/Component.java   |  16 ++-
 src/main/org/apache/ant/cyclonedx/PropertySet.java |  59 ++++++++++
 src/main/org/apache/ant/cyclonedx/antlib.xml       |   2 +
 src/tests/antunit/properties-test.xml              | 123 +++++++++++++++++++++
 8 files changed, 303 insertions(+), 2 deletions(-)

diff --git a/changes.xml b/changes.xml
index 4baa608..9c19b08 100644
--- a/changes.xml
+++ b/changes.xml
@@ -38,6 +38,14 @@
   </properties>
 
   <release version="0.2" date="unreleased">
+    <action type="fix" breaks-bwc="true">
+      The name attribute of properties is required by the spec and
+      this is now enforced.
+    </action>
+    <action type="add">
+      A new type "propertyset" can be used to group properties and
+      reuse common sets of properties for multiple components.
+    </action>
   </release>
 
   <release version="0.1" date="2026-06-03" description="initial release">
diff --git a/docs/component.html b/docs/component.html
index 1116d02..5dd1c65 100644
--- a/docs/component.html
+++ b/docs/component.html
@@ -263,7 +263,7 @@ <h5>Attributes</h5>
       <tr>
         <td>name</td>
         <td>The name of the property.</td>
-        <td>No</td>
+        <td>Yes</td>
       </tr>
       <tr>
         <td>value</td>
@@ -272,6 +272,13 @@ <h5>Attributes</h5>
       </tr>
     </table>
 
+    <h4>propertySet</h4>
+
+    <p><em>since CycloneDX Antlib 0.2</em></p>
+
+    <p>A nested <a href="propertyset.html">propertyset</a> specifies
+      properties for the component.</p>
+
     <h4>license</h4>
 
     <p>A nested <a href="license.html">license</a> specifies the
diff --git a/docs/index.html b/docs/index.html
index 375d83b..978342f 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -72,6 +72,7 @@ <h2>Tasks and Types provided by this Ant Library</h2>
       <li><a href="externalreferenceset.html">externalreferenceset</a></li>
       <li><a href="license.html">license</a></li>
       <li><a href="organization.html">organization</a></li>
+      <li><a href="propertyset.html">propertyset</a></li>
     </ul>
 
     <h2>Requirements and Dependencies of this Ant Library</h2>
diff --git a/docs/propertyset.html b/docs/propertyset.html
new file mode 100644
index 0000000..3e8a0a5
--- /dev/null
+++ b/docs/propertyset.html
@@ -0,0 +1,87 @@
+<!--
+   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 - propertyset</title>
+  </head>
+
+  <body>
+    <h2 id="propertyset">propertyset</h2>
+
+    <p><em>since CycloneDX Antlib 0.2</em></p>
+
+    <p>propertysets are collections of properties that can be
+      associated to a component or an SBOM. They 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>.</p>
+
+    <p>Apart from Ant's <code>id/refid</code> this element doesn't
+      support any attributes.</p>
+
+    <h3>Nested elements</h3>
+
+    <h4 id="property">property</h4>
+
+    <p>Represents a single property to be added to the BOM.</p>
+
+    <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>Yes</td>
+      </tr>
+      <tr>
+        <td>value</td>
+        <td>The value of the property.</td>
+        <td>No</td>
+      </tr>
+    </table>
+
+    <h3>Examples</h3>
+
+    <p>Below is a set of properties like those required by the BSI TR
+      for EU CRA compatible SBOM components.</p>
+
+    <pre>
+      &lt;cdx:propertyset
+        id="antlib-props"
+        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+        &lt;property
+            name="bsi:component:filename"
+            value="antlibs-cyclonedx.jar"/>
+        &lt;property
+            name="bsi:component:executable"
+            value="non-executable"/>
+        &lt;property
+            name="bsi:component:archive"
+            value="archive"/>
+        &lt;property
+            name="bsi:component:structured"
+            value="structured"/>
+      &lt;/cdx:propertyset>
+    </pre>
+
+  </body>
diff --git a/src/main/org/apache/ant/cyclonedx/Component.java 
b/src/main/org/apache/ant/cyclonedx/Component.java
index 6baae2e..0387135 100644
--- a/src/main/org/apache/ant/cyclonedx/Component.java
+++ b/src/main/org/apache/ant/cyclonedx/Component.java
@@ -248,11 +248,25 @@ public class Component extends DataType {
      *
      * @param property component property
      */
-    public void addProperty(Property property) {
+    public void addConfiguredProperty(Property property) {
         checkChildrenAllowed();
+        if (property.getName() == null) {
+            throw new BuildException("properties must have a name");
+        }
         properties.add(property);
     }
 
+    /**
+     * Adds a set of properties to the component.
+     *
+     * @param set set of properties of component
+     * @since CycloneDX Antlib 0.2
+     */
+    public void addConfiguredPropertySet(PropertySet set) {
+        checkChildrenAllowed();
+        properties.addAll(set.getProperties());
+    }
+
     /**
      * If set to {@code true} the supplier will also be used to
      * provide the manufacturer information.
diff --git a/src/main/org/apache/ant/cyclonedx/PropertySet.java 
b/src/main/org/apache/ant/cyclonedx/PropertySet.java
new file mode 100644
index 0000000..310afe1
--- /dev/null
+++ b/src/main/org/apache/ant/cyclonedx/PropertySet.java
@@ -0,0 +1,59 @@
+package org.apache.ant.cyclonedx;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.DataType;
+
+import org.cyclonedx.model.Property;
+
+/**
+ * A container for a collection of {@link Property}s.
+ *
+ * <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 are
+ * allowed.</p>
+ *
+ * @since CycloneDX Antlib 0.2
+ */
+public class PropertySet extends DataType {
+    private List<Property> properties = new ArrayList<>();
+
+    /**
+     * Adds a property to the set.
+     *
+     * @param property set property
+     */
+    public void addConfiguredProperty(Property property) {
+        checkChildrenAllowed();
+        if (property.getName() == null) {
+            throw new BuildException("properties must have a name");
+        }
+        properties.add(property);
+    }
+
+    /**
+     * Return the external references contained in this set.
+     *
+     * @return external references contained in this set
+     */
+    public Collection<Property> getProperties() {
+        if (isReference()) {
+            return getRef().getProperties();
+        }
+        dieOnCircularReference();
+        return properties;
+    }
+
+    /**
+     * Perform the check for circular references and return the
+     * referenced PropertySet.
+     * @return <code>PropertySet</code>.
+     */
+    protected PropertySet getRef() {
+        return getCheckedRef(PropertySet.class);
+    }
+}
diff --git a/src/main/org/apache/ant/cyclonedx/antlib.xml 
b/src/main/org/apache/ant/cyclonedx/antlib.xml
index 33c442c..2e545bc 100644
--- a/src/main/org/apache/ant/cyclonedx/antlib.xml
+++ b/src/main/org/apache/ant/cyclonedx/antlib.xml
@@ -28,4 +28,6 @@ under the License.
     classname="org.apache.ant.cyclonedx.License"/>
   <typedef name="externalreferenceset"
     classname="org.apache.ant.cyclonedx.ExternalReferenceSet"/>
+  <typedef name="propertyset"
+    classname="org.apache.ant.cyclonedx.PropertySet"/>
 </antlib>
diff --git a/src/tests/antunit/properties-test.xml 
b/src/tests/antunit/properties-test.xml
new file mode 100644
index 0000000..92083b8
--- /dev/null
+++ b/src/tests/antunit/properties-test.xml
@@ -0,0 +1,123 @@
+<?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="properties-test" default="antunit">
+
+  <import file="shared.xml" />
+
+  <target name="testPropertyRequiresName">
+    <au:expectfailure
+        expectedMessage="properties must have a name"
+        xmlns:au="antlib:org.apache.ant.antunit">
+      <cdx:componentbom
+          outputdirectory="${output}" format="xml"
+          xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+        <component name="testname">
+          <property value="foo"/>
+        </component>
+      </cdx:componentbom>
+    </au:expectfailure>
+    <au:expectfailure
+        expectedMessage="properties must have a name"
+        xmlns:au="antlib:org.apache.ant.antunit">
+      <cdx:propertyset
+          xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+        <property value="foo"/>
+      </cdx:propertyset>
+    </au:expectfailure>
+  </target>
+
+  <target name="testPropertyWorksAsDirectChildOfComponent">
+    <cdx:componentbom
+        outputdirectory="${output}" format="xml"
+        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="testname">
+        <property name="website" value="https://ant.apache.org/"/>
+      </component>
+    </cdx:componentbom>
+    <xmlproperty file="${output}/bom.xml"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.properties.property(name)"
+        value="website"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.properties.property"
+        value="https://ant.apache.org/"/>
+  </target>
+
+  <target name="testPropertyWorksNestedIntoAsSet">
+    <cdx:componentbom
+        outputdirectory="${output}" format="xml"
+        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="testname">
+        <propertyset>
+          <property name="website" value="https://ant.apache.org/"/>
+        </propertyset>
+      </component>
+    </cdx:componentbom>
+    <xmlproperty file="${output}/bom.xml"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.properties.property(name)"
+        value="website"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.properties.property"
+        value="https://ant.apache.org/"/>
+  </target>
+
+  <target name="testPropertyWorksViaReferenceToASet">
+    <cdx:propertyset id="test-set"
+        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <property name="website" value="https://ant.apache.org/"/>
+    </cdx:propertyset>
+    <cdx:componentbom
+        outputdirectory="${output}" format="xml"
+        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <component name="testname">
+        <propertyset refid="test-set"/>
+      </component>
+    </cdx:componentbom>
+    <xmlproperty file="${output}/bom.xml"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.properties.property(name)"
+        value="website"/>
+    <au:assertPropertyEquals
+        xmlns:au="antlib:org.apache.ant.antunit"
+        name="bom.metadata.component.properties.property"
+        value="https://ant.apache.org/"/>
+  </target>
+
+  <target
+      name="testPropertySetWithRefIdDoesntAllowNestedChildren">
+    <cdx:propertyset id="test-set"
+        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+      <property name="website" value="https://ant.apache.org/"/>
+    </cdx:propertyset>
+    <au:expectfailure
+        expectedMessage='You must not specify nested elements when using refid'
+        xmlns:au="antlib:org.apache.ant.antunit">
+      <cdx:propertyset id="test-set"
+          refid="test-set"
+          xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+        <property name="website" value="https://ant.apache.org/"/>
+      </cdx:propertyset>
+    </au:expectfailure>
+  </target>
+</project>

Reply via email to