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 abc import ABC, abstractmethod 20from typing import Any, Dict, Optional 21from typing import List 22 23from ohos.sbom.sbom.config.field_config import FieldConfigManager 24from ohos.sbom.sbom.validation.validator import Validator 25 26 27class BaseBuilder(ABC): 28 """Abstract base class defining the core builder interface.""" 29 30 def build(self, validate: bool = True) -> Any: 31 """ 32 Execute the complete build process. 33 34 Args: 35 validate: Whether to perform validation before building 36 37 Returns: 38 Any: The constructed object 39 40 Process: 41 1. Fill default values 42 2. Validate fields (if enabled) 43 3. Construct and return the final instance 44 """ 45 self._fill_defaults() 46 if validate: 47 self._validate() 48 return self._build_instance() 49 50 @abstractmethod 51 def _fill_defaults(self) -> None: 52 """Populate default values for unset fields.""" 53 pass 54 55 @abstractmethod 56 def _validate(self) -> None: 57 """Validate that all required fields are properly set.""" 58 pass 59 60 @abstractmethod 61 def _build_instance(self): 62 """Construct and return the final instance.""" 63 pass 64 65 66class ConfigurableBuilder(BaseBuilder): 67 """ 68 Configurable builder that works with FieldConfigManager. 69 70 Implements default value filling and validation based on configuration. 71 Subclasses should define CONFIG_NAME to enable automatic configuration loading. 72 """ 73 74 CONFIG_NAME: Optional[str] = None 75 76 def __init__(self, config_mgr: Optional[FieldConfigManager] = None): 77 """ 78 Initialize the builder. 79 80 Args: 81 config_mgr: Optional FieldConfigManager instance 82 - If provided, uses the given configuration 83 - If None, attempts to load using CONFIG_NAME 84 - If both unavailable, builder operates without configuration 85 """ 86 self._config_mgr = config_mgr 87 if self._config_mgr is None and hasattr(self, 'CONFIG_NAME') and self.CONFIG_NAME: 88 self._config_mgr = FieldConfigManager.get_instance(self.CONFIG_NAME) 89 90 def build(self, validate: bool = True) -> Any: 91 """ 92 Build with configurable validation. 93 94 Args: 95 validate: Whether to perform validation (only if config manager available) 96 97 Returns: 98 Any: The constructed instance 99 """ 100 self._fill_defaults() 101 if validate and self._config_mgr is not None: 102 self._validate() 103 return self._build_instance() 104 105 @abstractmethod 106 def _build_instance(self): 107 """Subclasses must implement actual instance construction.""" 108 pass 109 110 def get_property_value(self, property_name: str) -> Any: 111 """ 112 Safely get an internal property value. 113 114 Args: 115 property_name: The property name (e.g. 'serial_number') 116 117 Returns: 118 Any: The property value or None if not set 119 """ 120 return getattr(self, f"_{property_name}", None) 121 122 def _fill_defaults(self) -> None: 123 """Fill default values for all unset fields.""" 124 if self._config_mgr is None: 125 return 126 127 for prop in self._config_mgr.all_properties(): 128 current_value = self.get_property_value(prop) 129 130 # Check if "unset" or empty (excluding False, 0, etc.) 131 if not Validator.has_value(current_value): 132 default_val = self._config_mgr.get_default(prop) 133 if default_val is not None: 134 setattr(self, f"_{prop}", default_val) 135 136 def _validate(self) -> None: 137 """Validate all required fields are properly set.""" 138 if self._config_mgr is None: 139 return 140 141 requirements = [] 142 for prop in self._config_mgr.all_properties(): 143 value = self.get_property_value(prop) 144 required = self._config_mgr.is_required(prop) 145 requirements.append((prop, value, lambda req=required: req)) 146 147 Validator.validate_fields(requirements) 148 149 150class CompositeBuilder(BaseBuilder): 151 """Builder that composes multiple child builders.""" 152 153 def __init__(self): 154 """Initialize with empty child builder list.""" 155 self._children: List[BaseBuilder] = [] 156 157 def add_child_builder(self, builder: BaseBuilder) -> None: 158 """ 159 Add a child builder to the composition. 160 161 Args: 162 builder: The child builder to add 163 """ 164 self._children.append(builder) 165 166 @abstractmethod 167 def _build_instance(self) -> Dict[str, Any]: 168 """ 169 Construct the composite instance. 170 171 Returns: 172 Dict[str, Any]: The composite result 173 """ 174 pass 175 176 def _fill_defaults(self): 177 """ 178 Perform hierarchical validation with clear error reporting. 179 180 Raises: 181 ValueError: If any child builder validation fails, with detailed messages 182 """ 183 for child in self._children: 184 child._fill_defaults() # pylint: disable=protected-access 185 186 def _validate(self) -> None: 187 """ 188 Perform hierarchical validation with clear error reporting. 189 190 Raises: 191 ValueError: If any child builder validation fails, with detailed messages 192 """ 193 errors = [] 194 195 for child in self._children: 196 try: 197 child._validate() # pylint: disable=protected-access 198 except ValueError as e: 199 child_name = child.__class__.__name__.replace("BuilderContext", "") 200 errors.append(f"{child_name}missing: {str(e)}") 201 202 if errors: 203 raise ValueError("\n".join(errors)) 204