• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4#
5# Copyright (c) 2025 Northeastern University
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19from copy import deepcopy
20from typing import List, Optional, Literal, Union, Tuple
21
22from ohos.sbom.sbom.builder.base_builder import ConfigurableBuilder
23from ohos.sbom.sbom.metadata.sbom_meta_data import Package, Hash
24
25
26class PackageBuilder(ConfigurableBuilder):
27    """
28    Builder class for creating Package metadata in SBOM documents.
29
30    Implements fluent interface pattern for easy configuration of package properties.
31    Uses 'package' configuration from FieldConfigManager by default.
32    """
33
34    CONFIG_NAME = "package"
35
36    def __init__(self):
37        """Initialize a new PackageBuilder with all fields set to None/empty."""
38        super().__init__()
39        self._type: Optional[str] = None
40        self._supplier: Optional[str] = None
41        self._group: Optional[str] = None
42        self._name: Optional[str] = None
43        self._version: Optional[str] = None
44        self._purl: Optional[str] = None
45        self._license_concluded: Optional[str] = None
46        self._license_declared: Optional[str] = None
47        self._com_copyright: Optional[str] = None
48        self._bom_ref: Optional[str] = None
49        self._download_location: Optional[str] = None
50        self._hashes: List[Hash] = []
51        self._comp_platform: Optional[str] = None
52
53    @property
54    def bom_ref(self) -> str:
55        """
56        Get the current BOM reference identifier.
57
58        Returns:
59            str: The BOM reference string or None if not set
60        """
61        return self._bom_ref
62
63    def with_type(self, type_: Literal["library", "application", "framework"]) -> 'PackageBuilder':
64        """
65        Set the package type.
66
67        Args:
68            type_: Package type, one of:
69                  - "library": Software library/dependency
70                  - "application": Executable application
71                  - "framework": Development framework
72
73        Returns:
74            PackageBuilder: self for method chaining
75        """
76        self._type = type_
77        return self
78
79    def with_supplier(self, supplier: str) -> 'PackageBuilder':
80        """
81        Set the package supplier information.
82
83        Args:
84            supplier: Supplier identification string in SPDX format
85
86        Returns:
87            PackageBuilder: self for method chaining
88        """
89        self._supplier = supplier
90        return self
91
92    def with_group(self, group: str) -> 'PackageBuilder':
93        """
94        Set the package group/namespace.
95
96        Args:
97            group: Group identifier
98
99        Returns:
100            PackageBuilder: self for method chaining
101        """
102        self._group = group
103        return self
104
105    def with_name(self, name: str) -> 'PackageBuilder':
106        """
107        Set the package name.
108
109        Args:
110            name: Package name
111
112        Returns:
113            PackageBuilder: self for method chaining
114        """
115        self._name = name
116        return self
117
118    def with_version(self, version: str) -> 'PackageBuilder':
119        """
120        Set the package version.
121
122        Args:
123            version: Version string
124
125        Returns:
126            PackageBuilder: self for method chaining
127        """
128        self._version = version
129        return self
130
131    def with_purl(self, purl: str) -> 'PackageBuilder':
132        """
133        Set the package URL (purl).
134
135        Args:
136            purl: Package URL identifier
137
138        Returns:
139            PackageBuilder: self for method chaining
140
141        See:
142            https://github.com/package-url/purl-spec
143        """
144        self._purl = purl
145        return self
146
147    def with_license_concluded(self, license_concluded: str) -> 'PackageBuilder':
148        """
149        Set the concluded license for the package.
150
151        Args:
152            license_concluded: SPDX license identifier
153
154        Returns:
155            PackageBuilder: self for method chaining
156        """
157        self._license_concluded = license_concluded
158        return self
159
160    def with_license_declared(self, license_declared: str) -> 'PackageBuilder':
161        """
162        Set the declared license for the package.
163
164        Args:
165            license_declared: SPDX license identifier
166
167        Returns:
168            PackageBuilder: self for method chaining
169        """
170        self._license_declared = license_declared
171        return self
172
173    def with_com_copyright(self, com_copyright: str) -> 'PackageBuilder':
174        """
175        Set the copyright notice for the package.
176
177        Args:
178            com_copyright: Copyright declaration text
179
180        Returns:
181            PackageBuilder: self for method chaining
182        """
183        self._com_copyright = com_copyright
184        return self
185
186    def with_bom_ref(self, bom_ref: str) -> 'PackageBuilder':
187        """
188        Set the BOM reference identifier for the package.
189
190        Args:
191            bom_ref: Unique reference string
192
193        Returns:
194            PackageBuilder: self for method chaining
195        """
196        self._bom_ref = bom_ref
197        return self
198
199    def with_download_location(self, location: str) -> 'PackageBuilder':
200        """
201        Set the package download location.
202
203        Args:
204            location: Download URL
205
206        Returns:
207            PackageBuilder: self for method chaining
208        """
209        self._download_location = location
210        return self
211
212    def with_hash(self, alg: str, content: str) -> 'PackageBuilder':
213        """
214        Add a single cryptographic hash for the package.
215
216        Args:
217            alg: Hash algorithm
218            content: Hexadecimal hash value
219
220        Returns:
221            PackageBuilder: self for method chaining
222        """
223        self._hashes.append(Hash(alg=alg, content=content))
224        return self
225
226    def with_hashes(self, hashes: List[Union[Tuple[str, str], 'Hash']]) -> 'PackageBuilder':
227        """
228        Add multiple cryptographic hashes for the package.
229
230        Args:
231            hashes: List of hashes where each can be:
232                   - Tuple of (algorithm, hash_value)
233                   - Hash object
234
235        Returns:
236            PackageBuilder: self for method chaining
237        """
238        self._hashes = [
239            h if isinstance(h, Hash) else Hash(alg=h[0], content=h[1])
240            for h in hashes
241        ]
242        return self
243
244    def with_comp_platform(self, platform: str) -> 'PackageBuilder':
245        """
246        Set the target platform for the package.
247
248        Args:
249            platform: Platform identifier
250
251        Returns:
252            PackageBuilder: self for method chaining
253        """
254        self._comp_platform = platform
255        return self
256
257    def _build_instance(self) -> Package:
258        """
259        Construct the Package instance with current configuration.
260
261        Returns:
262            Package: A new Package instance with all configured values
263
264        Note:
265            Uses deepcopy for all collection-type and string fields to prevent reference sharing
266        """
267        return Package(
268            type=self._type,
269            supplier=self._supplier,
270            group=self._group,
271            name=self._name,
272            version=self._version,
273            purl=self._purl,
274            license_concluded=self._license_concluded,
275            license_declared=self._license_declared,
276            bom_ref=self._bom_ref,
277            comp_platform=self._comp_platform,
278            com_copyright=deepcopy(self._com_copyright),
279            download_location=deepcopy(self._download_location),
280            hashes=deepcopy(self._hashes),
281        )
282