414 lines
9.6 KiB
Go
414 lines
9.6 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"
|
|
"sort"
|
|
|
|
"code.forgejo.org/f3/gof3/v3/f3"
|
|
"code.forgejo.org/f3/gof3/v3/id"
|
|
"code.forgejo.org/f3/gof3/v3/kind"
|
|
"code.forgejo.org/f3/gof3/v3/logger"
|
|
"code.forgejo.org/f3/gof3/v3/path"
|
|
"code.forgejo.org/f3/gof3/v3/util"
|
|
)
|
|
|
|
type ChildrenSlice []NodeInterface
|
|
|
|
func NewChildrenSlice(len int) ChildrenSlice {
|
|
return make([]NodeInterface, 0, len)
|
|
}
|
|
|
|
func (o ChildrenSlice) Len() int {
|
|
return len(o)
|
|
}
|
|
|
|
func (o ChildrenSlice) Swap(i, j int) {
|
|
o[i], o[j] = o[j], o[i]
|
|
}
|
|
|
|
func (o ChildrenSlice) Less(i, j int) bool {
|
|
return o[i].GetID().String() < o[j].GetID().String()
|
|
}
|
|
|
|
type NodeChildren map[id.NodeID]NodeInterface
|
|
|
|
func NewNodeChildren() NodeChildren {
|
|
return make(NodeChildren)
|
|
}
|
|
|
|
var (
|
|
NilNode = &Node{isNil: true}
|
|
NilParent = NilNode
|
|
)
|
|
|
|
type Node struct {
|
|
logger.Logger
|
|
|
|
tree TreeInterface
|
|
isNil bool
|
|
sync bool
|
|
kind kind.Kind
|
|
id id.NodeID
|
|
|
|
driver NodeDriverInterface
|
|
self NodeInterface
|
|
parent NodeInterface
|
|
|
|
children NodeChildren
|
|
}
|
|
|
|
func NewElementNode() path.PathElement {
|
|
return NewNode().(path.PathElement)
|
|
}
|
|
|
|
func NewNode() NodeInterface {
|
|
node := &Node{}
|
|
return node.Init(node)
|
|
}
|
|
|
|
func NewNodeFromID[T any](i T) NodeInterface {
|
|
node := NewNode()
|
|
node.SetID(id.NewNodeID(i))
|
|
return node
|
|
}
|
|
|
|
func (o *Node) Init(self NodeInterface) NodeInterface {
|
|
o.SetTree(nil)
|
|
o.SetIsNil(true)
|
|
o.SetKind(kind.KindNil)
|
|
o.SetID(id.NilID)
|
|
o.SetSelf(self)
|
|
o.SetParent(NilNode)
|
|
o.children = NewNodeChildren()
|
|
|
|
return self
|
|
}
|
|
|
|
func (o *Node) GetIsNil() bool { return o == nil || o.isNil }
|
|
func (o *Node) SetIsNil(isNil bool) { o.isNil = isNil }
|
|
|
|
func (o *Node) GetIsSync() bool { return o.sync }
|
|
func (o *Node) SetIsSync(sync bool) { o.sync = sync }
|
|
|
|
func (o *Node) GetSelf() NodeInterface { return o.self }
|
|
func (o *Node) SetSelf(self NodeInterface) { o.self = self }
|
|
|
|
func (o *Node) GetParent() NodeInterface { return o.parent }
|
|
func (o *Node) SetParent(parent NodeInterface) { o.parent = parent }
|
|
|
|
func (o *Node) GetKind() kind.Kind { return o.kind }
|
|
func (o *Node) SetKind(kind kind.Kind) { o.kind = kind }
|
|
|
|
func (o *Node) GetID() id.NodeID {
|
|
if o.id == nil {
|
|
return id.NilID
|
|
}
|
|
return o.id
|
|
}
|
|
func (o *Node) SetID(id id.NodeID) { o.id = id }
|
|
|
|
func (o *Node) GetMappedID() id.NodeID {
|
|
mappedID := o.GetDriver().GetMappedID()
|
|
if mappedID == nil {
|
|
mappedID = id.NilID
|
|
}
|
|
return mappedID
|
|
}
|
|
|
|
func (o *Node) SetMappedID(mapped id.NodeID) {
|
|
o.GetDriver().SetMappedID(mapped)
|
|
}
|
|
|
|
func (o *Node) GetTree() TreeInterface { return o.tree }
|
|
func (o *Node) SetTree(tree TreeInterface) {
|
|
o.tree = tree
|
|
if tree != nil {
|
|
o.SetLogger(tree.GetLogger())
|
|
}
|
|
}
|
|
|
|
func (o *Node) GetDriver() NodeDriverInterface { return o.driver }
|
|
func (o *Node) SetDriver(driver NodeDriverInterface) {
|
|
driver.SetNode(o)
|
|
o.driver = driver
|
|
}
|
|
|
|
func (o *Node) GetNodeChildren() NodeChildren {
|
|
return o.children
|
|
}
|
|
|
|
func (o *Node) GetChildren() ChildrenSlice {
|
|
children := NewChildrenSlice(len(o.children))
|
|
for _, child := range o.children {
|
|
children = append(children, child)
|
|
}
|
|
sort.Sort(children)
|
|
return children
|
|
}
|
|
|
|
func (o *Node) SetChildren(children NodeChildren) { o.children = children }
|
|
|
|
func (o *Node) String() string {
|
|
driver := o.GetDriver().String()
|
|
if driver != "" {
|
|
driver = "=" + driver
|
|
}
|
|
return o.GetID().String() + driver
|
|
}
|
|
|
|
func (o *Node) Equals(ctx context.Context, other NodeInterface) bool {
|
|
return o.GetKind() == other.GetKind() && o.GetID() == other.GetID()
|
|
}
|
|
|
|
func (o *Node) GetCurrentPath() path.Path {
|
|
var p path.Path
|
|
if o.GetIsNil() {
|
|
p = path.NewPath()
|
|
} else {
|
|
p = o.GetParent().GetCurrentPath().Append(o)
|
|
}
|
|
return p
|
|
}
|
|
|
|
func (o *Node) ListPage(ctx context.Context, page int) ChildrenSlice {
|
|
return o.GetDriver().ListPage(ctx, page)
|
|
}
|
|
|
|
func (o *Node) List(ctx context.Context) ChildrenSlice {
|
|
o.Trace("%s", o.GetKind())
|
|
children := NewNodeChildren()
|
|
|
|
self := o.GetSelf()
|
|
for page := 1; ; page++ {
|
|
util.MaybeTerminate(ctx)
|
|
childrenPage := self.ListPage(ctx, page)
|
|
for _, child := range childrenPage {
|
|
children[child.GetID()] = child
|
|
}
|
|
|
|
if len(childrenPage) < o.GetTree().GetPageSize() {
|
|
break
|
|
}
|
|
}
|
|
|
|
o.children = children
|
|
return o.GetChildren()
|
|
}
|
|
|
|
func (o *Node) GetChild(id id.NodeID) NodeInterface {
|
|
child, ok := o.children[id]
|
|
if !ok {
|
|
return NilNode
|
|
}
|
|
return child
|
|
}
|
|
|
|
func (o *Node) SetChild(child NodeInterface) NodeInterface {
|
|
o.children[child.GetID()] = child
|
|
return child
|
|
}
|
|
|
|
func (o *Node) DeleteChild(id id.NodeID) NodeInterface {
|
|
if child, ok := o.children[id]; ok {
|
|
delete(o.children, id)
|
|
return child
|
|
}
|
|
return NilNode
|
|
}
|
|
|
|
func (o *Node) CreateChild(ctx context.Context) NodeInterface {
|
|
tree := o.GetTree()
|
|
child := tree.Factory(ctx, tree.GetChildrenKind(o.GetKind()))
|
|
child.SetParent(o)
|
|
return child
|
|
}
|
|
|
|
func (o *Node) GetIDFromName(ctx context.Context, name string) id.NodeID {
|
|
return o.GetDriver().GetIDFromName(ctx, name)
|
|
}
|
|
|
|
func (o *Node) Get(ctx context.Context) NodeInterface {
|
|
if o.GetDriver().Get(ctx) {
|
|
o.SetIsSync(true)
|
|
}
|
|
return o
|
|
}
|
|
|
|
func (o *Node) Upsert(ctx context.Context) NodeInterface {
|
|
if o.GetID() != id.NilID {
|
|
o.GetDriver().Patch(ctx)
|
|
} else {
|
|
o.SetID(o.GetDriver().Put(ctx))
|
|
}
|
|
if o.GetParent() != NilNode {
|
|
o.GetParent().SetChild(o)
|
|
}
|
|
return o
|
|
}
|
|
|
|
func (o *Node) Delete(ctx context.Context) NodeInterface {
|
|
o.GetDriver().Delete(ctx)
|
|
return o.GetParent().DeleteChild(o.GetID())
|
|
}
|
|
|
|
func (o *Node) NewFormat() f3.Interface {
|
|
return o.GetDriver().NewFormat()
|
|
}
|
|
|
|
func (o *Node) FromFormat(f f3.Interface) NodeInterface {
|
|
o.SetID(id.NewNodeID(f.GetID()))
|
|
o.GetDriver().FromFormat(f)
|
|
return o
|
|
}
|
|
|
|
func (o *Node) ToFormat() f3.Interface {
|
|
if o == nil || o.GetDriver() == nil {
|
|
return nil
|
|
}
|
|
return o.GetDriver().ToFormat()
|
|
}
|
|
|
|
func (o *Node) LookupMappedID(id id.NodeID) id.NodeID { return o.GetDriver().LookupMappedID(id) }
|
|
|
|
func (o *Node) ApplyAndGet(ctx context.Context, p path.Path, options *ApplyOptions) bool {
|
|
applyWrapInGet := func(options *ApplyOptions) ApplyFunc {
|
|
return func(ctx context.Context, parent, p path.Path, node NodeInterface) {
|
|
if options.fun != nil && (p.Empty() || options.where == ApplyEachNode) {
|
|
options.fun(ctx, parent, p, node)
|
|
}
|
|
if p.Empty() {
|
|
return
|
|
}
|
|
|
|
childID := p.First().(NodeInterface).GetID()
|
|
// is it a known child?
|
|
child := node.GetChild(childID)
|
|
// if not, maybe it is a known child referenced by name
|
|
if child.GetIsNil() {
|
|
childIDFromName := node.GetIDFromName(ctx, childID.String())
|
|
if childIDFromName != id.NilID {
|
|
childID = childIDFromName
|
|
child = node.GetChild(childID)
|
|
p.First().(NodeInterface).SetID(childID)
|
|
}
|
|
}
|
|
// not a known child by ID or by Name: make one
|
|
if child.GetIsNil() {
|
|
child = node.CreateChild(ctx)
|
|
child.SetID(childID)
|
|
if child.Get(ctx).GetIsSync() {
|
|
// only set the child if the driver is able to get it, otherwise
|
|
// it means that although the ID is known the child itself cannot be
|
|
// obtained and is assumed to be not found
|
|
node.SetChild(child)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return o.Apply(ctx, path.NewPath(), p, NewApplyOptions(applyWrapInGet(options)).SetWhere(ApplyEachNode))
|
|
}
|
|
|
|
func (o *Node) WalkAndGet(ctx context.Context, parent path.Path, options *WalkOptions) {
|
|
walkWrapInGet := func(fun WalkFunc) WalkFunc {
|
|
return func(ctx context.Context, p path.Path, node NodeInterface) {
|
|
node.Get(ctx)
|
|
node.List(ctx)
|
|
if fun != nil {
|
|
fun(ctx, p, node)
|
|
}
|
|
}
|
|
}
|
|
|
|
o.Walk(ctx, parent, NewWalkOptions(walkWrapInGet(options.fun)))
|
|
}
|
|
|
|
func (o *Node) Walk(ctx context.Context, parent path.Path, options *WalkOptions) {
|
|
util.MaybeTerminate(ctx)
|
|
options.fun(ctx, parent, o.GetSelf())
|
|
parent = parent.Append(o.GetSelf().(path.PathElement))
|
|
for _, child := range o.GetSelf().GetChildren() {
|
|
child.Walk(ctx, parent, options)
|
|
}
|
|
}
|
|
|
|
func (o *Node) FindAndGet(ctx context.Context, p path.Path) NodeInterface {
|
|
var r NodeInterface
|
|
r = NilNode
|
|
set := func(ctx context.Context, parent, p path.Path, node NodeInterface) {
|
|
r = node
|
|
}
|
|
o.ApplyAndGet(ctx, p, NewApplyOptions(set))
|
|
return r
|
|
}
|
|
|
|
func (o *Node) MustFind(p path.Path) NodeInterface {
|
|
found := o.Find(p)
|
|
if found.GetIsNil() {
|
|
panic(fmt.Errorf("%s not found", p))
|
|
}
|
|
return found
|
|
}
|
|
|
|
func (o *Node) Find(p path.Path) NodeInterface {
|
|
if p.Empty() {
|
|
return o
|
|
}
|
|
|
|
child := o.GetChild(p.First().(NodeInterface).GetID())
|
|
if child.GetIsNil() {
|
|
o.Info("'%s' not found", p.String())
|
|
return NilNode
|
|
}
|
|
|
|
return child.Find(p.RemoveFirst())
|
|
}
|
|
|
|
func (o *Node) Apply(ctx context.Context, parentPath, p path.Path, options *ApplyOptions) bool {
|
|
o.Trace("parent '%s', node '%s', path '%s'", parentPath.ReadableString(), o.String(), p.ReadableString())
|
|
|
|
util.MaybeTerminate(ctx)
|
|
|
|
if p.Empty() {
|
|
options.fun(ctx, parentPath, p, o.GetSelf())
|
|
return true
|
|
}
|
|
|
|
i := p.First().(NodeInterface).GetID().String()
|
|
|
|
if i == "." {
|
|
return o.Apply(ctx, parentPath, p.RemoveFirst(), options)
|
|
}
|
|
|
|
if i == ".." {
|
|
parent, parentPath := parentPath.Pop()
|
|
return parent.(NodeInterface).Apply(ctx, parentPath, p.RemoveFirst(), options)
|
|
}
|
|
|
|
if options.where == ApplyEachNode {
|
|
options.fun(ctx, parentPath, p, o.GetSelf())
|
|
}
|
|
|
|
child := o.GetChild(p.First().(NodeInterface).GetID())
|
|
if child.GetIsNil() && options.search == ApplySearchByName {
|
|
if childID := o.GetIDFromName(ctx, p.First().(NodeInterface).GetID().String()); childID != id.NilID {
|
|
child = o.GetChild(childID)
|
|
}
|
|
}
|
|
if child.GetIsNil() {
|
|
return false
|
|
}
|
|
parentPath = parentPath.Append(o.GetSelf().(path.PathElement))
|
|
return child.Apply(ctx, parentPath, p.RemoveFirst(), options)
|
|
}
|
|
|
|
type ErrorNodeNotFound error
|
|
|
|
func NewError[T error](message string, args ...any) T {
|
|
e := fmt.Errorf(message, args...)
|
|
return e.(T)
|
|
}
|