159 lines
4.2 KiB
Go
159 lines
4.2 KiB
Go
package engine
|
|
|
|
import (
|
|
"slices"
|
|
"sort"
|
|
)
|
|
|
|
type historySelection struct {
|
|
path Path
|
|
phase historyPhase
|
|
payload historyPayloadRef
|
|
reportedPath Path
|
|
role historyRole
|
|
before bool
|
|
after bool
|
|
attribution historyAttribution
|
|
hasAttribution bool
|
|
previous []Origin
|
|
}
|
|
|
|
func (model *historyModel) selectHistory(path Path) []historySelection {
|
|
if model == nil || !path.valid() || !model.hasObservedShape(path) {
|
|
return nil
|
|
}
|
|
|
|
candidateSet := make(map[int]struct{})
|
|
for _, observationID := range model.observationsByPointer[path.String()] {
|
|
candidateSet[observationID] = struct{}{}
|
|
}
|
|
model.reportedPathIndex.visitAncestors(path.String(), func(observationID int) {
|
|
candidateSet[observationID] = struct{}{}
|
|
})
|
|
candidates := make([]int, 0, len(candidateSet))
|
|
for observationID := range candidateSet {
|
|
candidates = append(candidates, observationID)
|
|
}
|
|
slices.Sort(candidates)
|
|
|
|
selections := make([]historySelection, 0, len(candidates))
|
|
selectionByPayload := make(map[historyPayloadRef]int)
|
|
for _, observationID := range candidates {
|
|
observation := model.observations[observationID]
|
|
if observation.role == historyRoleInventory || !matchesPathShape(path, observation.path) {
|
|
continue
|
|
}
|
|
selectionIndex, found := selectionByPayload[observation.payload]
|
|
if !found {
|
|
selectionIndex = len(selections)
|
|
selectionByPayload[observation.payload] = selectionIndex
|
|
selection := historySelection{
|
|
path: path.clone(),
|
|
phase: observation.phase,
|
|
payload: observation.payload,
|
|
reportedPath: observation.reportedPath.clone(),
|
|
role: observation.role,
|
|
}
|
|
if attribution, ok := model.resolveAttribution(observation.attributionRef); ok {
|
|
selection.attribution = attribution
|
|
selection.hasAttribution = true
|
|
}
|
|
selections = append(selections, selection)
|
|
}
|
|
selection := &selections[selectionIndex]
|
|
selection.before = selection.before || observation.beforePresent
|
|
selection.after = selection.after || observation.afterPresent
|
|
}
|
|
if len(selections) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var previous *Origin
|
|
for index := range selections {
|
|
selection := &selections[index]
|
|
if selection.hasAttribution && metadataHistoryRole(selection.role) {
|
|
if previous != nil {
|
|
selection.previous = []Origin{cloneOrigin(*previous)}
|
|
}
|
|
origin := cloneOrigin(selection.attribution.origin)
|
|
previous = &origin
|
|
}
|
|
}
|
|
return selections
|
|
}
|
|
|
|
func (model *historyModel) hasObservedShape(path Path) bool {
|
|
if model == nil || !path.valid() {
|
|
return false
|
|
}
|
|
for _, candidate := range model.shapesByPointer[path.String()] {
|
|
if matchesPathShape(path, candidate) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (model *historyModel) concreteShapes(pointer string) []Path {
|
|
if model == nil {
|
|
return nil
|
|
}
|
|
shapes := model.shapesByPointer[pointer]
|
|
cloned := make([]Path, len(shapes))
|
|
for index, path := range shapes {
|
|
cloned[index] = path.clone()
|
|
}
|
|
return cloned
|
|
}
|
|
|
|
func (model *historyModel) observedPaths() []Path {
|
|
if model == nil {
|
|
return nil
|
|
}
|
|
paths := make([]Path, len(model.shapeOrder))
|
|
for index, path := range model.shapeOrder {
|
|
paths[index] = path.clone()
|
|
}
|
|
return paths
|
|
}
|
|
|
|
func (model *historyModel) historicalPaths() []Path {
|
|
if model == nil {
|
|
return nil
|
|
}
|
|
pointers := make([]string, 0, len(model.shapesByPointer))
|
|
for pointer := range model.shapesByPointer {
|
|
pointers = append(pointers, pointer)
|
|
}
|
|
sort.Strings(pointers)
|
|
paths := make([]Path, 0, len(pointers))
|
|
for _, pointer := range pointers {
|
|
path, err := ParsePath(pointer)
|
|
if err == nil {
|
|
paths = append(paths, path)
|
|
}
|
|
}
|
|
return paths
|
|
}
|
|
|
|
func (model *historyModel) resolveAttribution(ref historyAttributionRef) (historyAttribution, bool) {
|
|
if model == nil || ref < 0 || int(ref) >= len(model.attributions) {
|
|
return historyAttribution{}, false
|
|
}
|
|
attribution := model.attributions[ref]
|
|
return historyAttribution{
|
|
origin: cloneOrigin(attribution.origin),
|
|
reasons: append([]string(nil), attribution.reasons...),
|
|
dependencies: clonePaths(attribution.dependencies),
|
|
}, true
|
|
}
|
|
|
|
func metadataHistoryRole(role historyRole) bool {
|
|
switch role {
|
|
case historyRoleDefinition, historyRoleCompositeDefinition, historyRoleOperation, historyRoleControl:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|