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