Files
gof3/tree/generic/unify.go
2024-03-07 16:36:47 +08:00

352 lines
11 KiB
Go

// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"fmt"
"strings"
"lab.forgefriends.org/friendlyforgeformat/gof3/f3"
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
type (
UnifyUpsertFunc func(ctx context.Context, origin NodeInterface, originParent Path, destination NodeInterface, destinationParent Path)
UnifyDeleteFunc func(ctx context.Context, destination NodeInterface, destinationParent Path)
)
type UnifyOptions struct {
destinationTree TreeInterface
upsert UnifyUpsertFunc
delete UnifyDeleteFunc
noremap bool
}
func NewUnifyOptions(destinationTree TreeInterface) *UnifyOptions {
return &UnifyOptions{
destinationTree: destinationTree,
}
}
func (o *UnifyOptions) SetUpsert(upsert UnifyUpsertFunc) *UnifyOptions {
o.upsert = upsert
return o
}
func (o *UnifyOptions) SetDelete(delete UnifyDeleteFunc) *UnifyOptions {
o.delete = delete
return o
}
func (o *UnifyOptions) SetNoRemap(noremap bool) *UnifyOptions {
o.noremap = noremap
return o
}
func TreeUnifyPath(ctx context.Context, origin TreeInterface, path Path, destination TreeInterface, options *UnifyOptions) bool {
if path.Empty() || string(path.First().GetID()) == "." {
return true
}
originRoot := origin.GetRoot()
destinationRoot := destination.GetRoot()
if destinationRoot == nil {
destinationRoot = destination.Factory(ctx, originRoot.GetKind())
destination.SetRoot(destinationRoot)
}
path = path.RemoveFirst()
if path.Empty() {
return true
}
originNode := originRoot.GetChild(path.First().GetID()).GetSelf()
return NodeUnifyPath(ctx, originNode, NewPath(originRoot), path.RemoveFirst(), NewPath(destinationRoot), options)
}
func TreeUnify(ctx context.Context, origin, destination TreeInterface, options *UnifyOptions) {
origin.Trace("")
originRoot := origin.GetRoot()
if originRoot == nil {
destination.SetRoot(nil)
return
}
destinationRoot := destination.GetRoot()
if destinationRoot == nil {
destinationRoot = destination.Factory(ctx, originRoot.GetKind())
destination.SetRoot(destinationRoot)
NodeCopy(ctx, originRoot, destinationRoot, originRoot.GetID(), options)
}
NodeUnify(ctx, originRoot, NewPath(), destinationRoot, NewPath(), options)
}
func RemapReferences(ctx context.Context, node NodeInterface, f f3.Interface) {
for _, reference := range f.GetReferences() {
toPath := NewPath()
collectTo := func(ctx context.Context, parent, path Path, node NodeInterface) {
element := NewNode()
element.SetID(node.GetMappedID())
toPath = toPath.Append(element)
}
from := reference.Get()
isRelative := !strings.HasPrefix(from, "/")
if isRelative && !strings.HasPrefix(from, "..") {
panic(fmt.Errorf("relative references that do not start with .. are not supported %s", from))
}
current := node.GetCurrentPath().String()
fromPath := PathAbsolute(current, from)
node.Trace("collectTo %s", from)
node.GetTree().Apply(ctx, fromPath, NewApplyOptions(collectTo).SetWhere(ApplyEachNode))
to := toPath.String()
if isRelative {
currentMapped := node.GetParent().GetCurrentPath().PathMappedString().Join()
// because the mapped ID of the current node has not been allocated yet
// and it does not matter as long as it is replaced with ..
// it will not work at all if a relative reference does not start with ..
currentMapped += "/PLACEHODLER"
to = PathStringRelative(currentMapped, to)
}
node.Trace("convert reference %s => %s", reference.Get(), to)
reference.Set(to)
}
}
func NodeCopy(ctx context.Context, origin, destination NodeInterface, destinationID NodeID, options *UnifyOptions) {
f := origin.GetSelf().ToFormat()
if options.noremap {
origin.Trace("noremap")
} else {
RemapReferences(ctx, origin, f)
}
f.SetID(string(destinationID))
destination.GetSelf().FromFormat(f)
destination.Upsert(ctx)
}
func NodeUnify(ctx context.Context, origin NodeInterface, originPath Path, destination NodeInterface, destinationPath Path, options *UnifyOptions) {
origin.Trace("origin '%s' | destination '%s'", origin.GetCurrentPath(), destination.GetCurrentPath())
util.MaybeTerminate(ctx)
originPath = originPath.Append(origin)
destinationPath = destinationPath.Append(destination)
originChildren := origin.GetSelf().GetChildren()
existing := make(map[NodeID]any, len(originChildren))
for _, originChild := range originChildren {
destinationID := GetMappedID(ctx, originChild, destination, options)
destinationChild := destination.GetChild(destinationID)
createDestinationChild := destinationChild == NilNode
if createDestinationChild {
destinationChild = options.destinationTree.Factory(ctx, originChild.GetKind())
destinationChild.SetParent(destination)
}
NodeCopy(ctx, originChild, destinationChild, destinationID, options)
if options.upsert != nil {
options.upsert(ctx, originChild.GetSelf(), originPath, destinationChild.GetSelf(), destinationPath)
}
if createDestinationChild {
destination.SetChild(destinationChild)
}
SetMappedID(ctx, originChild, destinationChild, options)
existing[destinationChild.GetID()] = true
NodeUnify(ctx, originChild, originPath, destinationChild, destinationPath, options)
}
destinationChildren := destination.GetSelf().GetChildren()
for _, destinationChild := range destinationChildren {
destinationID := destinationChild.GetID()
if _, ok := existing[destinationID]; !ok {
destinationChild.GetSelf().Delete(ctx)
if options.delete != nil {
options.delete(ctx, destinationChild.GetSelf(), destinationPath)
}
destination.DeleteChild(destinationID)
}
}
}
func SetMappedID(ctx context.Context, origin, destination NodeInterface, options *UnifyOptions) {
if options.noremap {
return
}
origin.SetMappedID(destination.GetID())
}
func GetMappedID(ctx context.Context, origin, destinationParent NodeInterface, options *UnifyOptions) NodeID {
if options.noremap {
return origin.GetID()
}
if id := origin.GetMappedID(); id != NilID {
return id
}
return destinationParent.LookupMappedID(origin.GetID())
}
func NodeUnifyOne(ctx context.Context, origin NodeInterface, originPath, path, destinationPath Path, options *UnifyOptions) NodeInterface {
destinationParent := destinationPath.Last()
destinationID := GetMappedID(ctx, origin, destinationParent, options)
origin.Trace("'%s' '%s' '%s' %v", originPath, path, destinationPath, destinationID)
destination := destinationParent.GetChild(destinationID)
createDestination := destination == NilNode
if createDestination {
destination = options.destinationTree.Factory(ctx, origin.GetKind())
destination.SetParent(destinationParent)
}
NodeCopy(ctx, origin, destination, destinationID, options)
if options.upsert != nil {
options.upsert(ctx, origin.GetSelf(), originPath, destination.GetSelf(), destinationPath)
}
if createDestination {
destinationParent.SetChild(destination)
}
origin.SetMappedID(destination.GetID())
return destination
}
func NodeUnifyPath(ctx context.Context, origin NodeInterface, originPath, path, destinationPath Path, options *UnifyOptions) bool {
origin.Trace("origin '%s' '%s' | destination '%s' | path '%s'", originPath.String(), origin.GetID(), destinationPath.String(), path.String())
util.MaybeTerminate(ctx)
if path.Empty() {
NodeUnifyOne(ctx, origin, originPath, path, destinationPath, options)
return true
}
id := string(path.First().GetID())
if id == "." {
NodeUnifyOne(ctx, origin, originPath, path, destinationPath, options)
return true
}
if id == ".." {
parent, originPath := originPath.Pop()
_, destinationPath := destinationPath.Pop()
return NodeUnifyPath(ctx, parent, originPath, path.RemoveFirst(), destinationPath, options)
}
destination := NodeUnifyOne(ctx, origin, originPath, path, destinationPath, options)
originPath = originPath.Append(origin.GetSelf())
destinationPath = destinationPath.Append(destination.GetSelf())
child := origin.GetSelf().GetChild(path.First().GetID())
if child == NilNode {
panic(NewError[ErrorNodeNotFound]("%s has no child with id %s", originPath.String(), path.First().GetID()))
}
return NodeUnifyPath(ctx, child, originPath, path.RemoveFirst(), destinationPath, options)
}
type ParallelApplyFunc func(ctx context.Context, origin, destination NodeInterface)
type ParallelApplyOptions struct {
fun ParallelApplyFunc
where ApplyWhere
noremap bool
}
func NewParallelApplyOptions(fun ParallelApplyFunc) *ParallelApplyOptions {
return &ParallelApplyOptions{
fun: fun,
}
}
func (o *ParallelApplyOptions) SetWhere(where ApplyWhere) *ParallelApplyOptions {
o.where = where
return o
}
func (o *ParallelApplyOptions) SetNoRemap(noremap bool) *ParallelApplyOptions {
o.noremap = noremap
return o
}
func TreePathRemap(ctx context.Context, origin TreeInterface, path Path, destination TreeInterface) Path {
remappedPath := NewPath()
remap := func(ctx context.Context, origin, destination NodeInterface) {
remappedPath = destination.GetCurrentPath()
}
TreeParallelApply(ctx, origin, path, destination, NewParallelApplyOptions(remap))
return remappedPath
}
func TreeParallelApply(ctx context.Context, origin TreeInterface, path Path, destination TreeInterface, options *ParallelApplyOptions) bool {
if path.Empty() {
return true
}
return NodeParallelApply(ctx, origin.GetRoot(), path.RemoveFirst(), destination.GetRoot(), options)
}
func NodeParallelApply(ctx context.Context, origin NodeInterface, path Path, destination NodeInterface, options *ParallelApplyOptions) bool {
origin.Trace("origin '%s' | destination '%s' | path '%s'", origin.GetCurrentPath(), destination.GetCurrentPath(), path.String())
util.MaybeTerminate(ctx)
if path.Empty() {
options.fun(ctx, origin, destination)
return true
}
id := string(path.First().GetID())
if id == "." {
return NodeParallelApply(ctx, origin, path.RemoveFirst(), destination, options)
}
if id == ".." {
return NodeParallelApply(ctx, origin.GetParent(), path.RemoveFirst(), destination.GetParent(), options)
}
if options.where == ApplyEachNode {
options.fun(ctx, origin, destination)
}
originChild := origin.GetSelf().GetChild(path.First().GetID())
if originChild == NilNode {
origin.Trace("no child %s", path.First().GetID())
return false
}
var mappedID NodeID
if options.noremap {
mappedID = originChild.GetID()
} else {
mappedID = originChild.GetMappedID()
if mappedID == NilID {
origin.Trace("%s no mapped", originChild.GetID())
return false
}
}
destinationChild := destination.GetChild(mappedID)
if destinationChild == NilNode {
panic(NewError[ErrorNodeNotFound]("%s has no child with id %s", destination.String(), originChild.GetMappedID()))
}
return NodeParallelApply(ctx, originChild, path.RemoveFirst(), destinationChild, options)
}