# Copyright (C) 2026 Siemens
#
# SPDX-License-Identifier: MIT
from abc import abstractmethod
from collections.abc import Iterable
from dataclasses import dataclass
from pathlib import Path
from io import IOBase
from packageurl import PackageURL
from ..bomreader.bomreader import BomReader
from ..util.sbom_processor import SbomProcessor
from ..sbom import SBOMType
[docs]
class NoRootNodeError(RuntimeError):
def __init__(self):
super().__init__("SBOM does not contain any root node")
[docs]
@dataclass
class PackageRepr:
"""
Simple package representation that covers meta, source and binary packages.
"""
name: str
ref: str
version: str | None = None
maintainer: str | None = None
purl: str | None = None
def __str__(self):
NOA = "NoAssertion"
return f"{self.name}\t{self.version or NOA}\t{self.purl or NOA}\t{self.maintainer or NOA}"
def __hash__(self):
"""Trivial hash function based on the unique ref field."""
return hash(self.ref)
[docs]
class GraphWalker(SbomProcessor):
"""
Base class of graph walkers
"""
[docs]
@classmethod
def create(
cls,
filename: Path,
bomtype: SBOMType | None = None,
) -> "GraphWalker":
"""
Factory to create a GraphWalker for the given SBOM type (based on the filename extension).
"""
reader = BomReader.create(filename, bomtype)
return cls.from_document(reader.read(), reader.sbom_type())
[docs]
@classmethod
def from_stream(cls, stream: IOBase, bomtype: SBOMType) -> "GraphWalker":
"""
Factory to create a GraphWalker for the given SBOM type that takes the SBOM as stream.
"""
reader = BomReader.from_stream(stream, bomtype)
return cls.from_document(reader.read(), reader.sbom_type())
[docs]
@classmethod
def from_json(cls, json_obj: IOBase, bomtype: SBOMType) -> "GraphWalker":
"""
Factory to create a GraphWalker for the given SBOM type that takes a json object.
"""
reader = BomReader.from_json(json_obj, bomtype)
return cls.from_document(reader.read(), reader.sbom_type())
[docs]
@classmethod
def from_document(cls, document, sbom_type: SBOMType) -> "GraphWalker":
"""
Factory to create a GraphWalker from an SBOM document instance.
"""
if sbom_type == SBOMType.SPDX:
from .spdx import SpdxGraphWalker
return SpdxGraphWalker(document)
else:
from .cdx import CdxGraphWalker
return CdxGraphWalker(document)
[docs]
@abstractmethod
def shortest(self, source: PackageURL) -> list[PackageRepr]:
"""Return shortest path from source to root. Abstract method."""
raise NotImplementedError()
[docs]
@abstractmethod
def all_shortest(self, source: PackageURL) -> Iterable[list[PackageRepr]]:
"""Return all shortest path from source to root. Abstract method."""
raise NotImplementedError()
[docs]
@abstractmethod
def all_simple(self, source: PackageURL) -> Iterable[list[PackageRepr]]:
"""Return all non-cyclic path from source to root. Abstract method."""
raise NotImplementedError()