• 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 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