mediocre-go-lib/mcmp/component.go

219 lines
6.1 KiB
Go

package mcmp
import (
"context"
"fmt"
"strings"
"sync"
"github.com/mediocregopher/mediocre-go-lib/mctx"
)
type child struct {
*Component
name string
}
// Component describes a single component of a program, and holds onto
// key/values for that component for use in generic libraries which instantiate
// those components.
//
// When instantiating a component it's generally necessary to know where in the
// component hierarchy it lies, for purposes of creating configuration
// parameters and so-forth. To support this, Components are able to spawn of
// child Components, each with a blank key/value namespace. Each child is
// differentiated from the other by a name, and a Component is able to use its
// Path (the sequence of names of its ancestors) to differentiate itself from
// any other component in the hierarchy.
//
// A new Component, i.e. the root Component in the hierarchy, can be initialized
// by doing:
// new(Component).
//
// Method's on Component are thread-safe.
type Component struct {
l sync.RWMutex
path []string
parent *Component
children []child
kv map[interface{}]interface{}
ctx context.Context
}
// SetValue sets the given key to the given value on the Component, overwriting
// any previous value for that key.
func (c *Component) SetValue(key, value interface{}) {
c.l.Lock()
defer c.l.Unlock()
if c.kv == nil {
c.kv = make(map[interface{}]interface{}, 1)
}
c.kv[key] = value
}
func (c *Component) value(key interface{}) (interface{}, bool) {
c.l.RLock()
defer c.l.RUnlock()
if c.kv == nil {
return nil, false
}
value, ok := c.kv[key]
return value, ok
}
// Value returns the value which has been set for the given key.
func (c *Component) Value(key interface{}) interface{} {
value, _ := c.value(key)
return value
}
// InheritedValue returns the value which has been set for the given key. It first
// looks for the key on the receiver Component. If not found, it will look on
// its parent Component, and so on, until the key is found. If the key is not
// found on the root Component then false is returned.
func (c *Component) InheritedValue(key interface{}) (interface{}, bool) {
value, ok := c.value(key)
if ok {
return value, ok
} else if c.parent == nil {
return nil, false
}
return c.parent.InheritedValue(key)
}
// Values returns all key/value pairs which have been set via SetValue.
func (c *Component) Values() map[interface{}]interface{} {
c.l.RLock()
defer c.l.RUnlock()
out := make(map[interface{}]interface{}, len(c.kv))
for k, v := range c.kv {
out[k] = v
}
return out
}
// HasValue returns true if the given key has had a value set on it with
// SetValue.
func (c *Component) HasValue(key interface{}) bool {
c.l.RLock()
defer c.l.RUnlock()
_, ok := c.kv[key]
return ok
}
// Child returns a new child component of the method receiver. The child will
// have the given name, and its Path will be the receiver's path with the name
// appended. The child will not inherit any of the receiver's key/value pairs.
//
// If a child of the given name has already been created this method will panic.
func (c *Component) Child(name string) *Component {
c.l.Lock()
defer c.l.Unlock()
for _, child := range c.children {
if child.name == name {
panic(fmt.Sprintf("child with name %q already exists", name))
}
}
childComp := &Component{
path: append(c.path, name),
parent: c,
}
c.children = append(c.children, child{name: name, Component: childComp})
return childComp
}
// Children returns all Components created via the Child method on this
// Component, in the order they were created.
func (c *Component) Children() []*Component {
c.l.RLock()
defer c.l.RUnlock()
children := make([]*Component, len(c.children))
for i := range c.children {
children[i] = c.children[i].Component
}
return children
}
// Name returns the name this Component was created with (via the Child method),
// or false if this Component was not created via Child (and is therefore the
// root Component).
func (c *Component) Name() (string, bool) {
c.l.RLock()
defer c.l.RUnlock()
if len(c.path) == 0 {
return "", false
}
return c.path[len(c.path)-1], true
}
// Path returns the sequence of names which were passed into Child calls in
// order to create this Component. If the Component was not created via Child
// (and is therefore the root Component) this will return an empty slice.
//
// root := new(Component)
// child := root.Child("child")
// grandChild := child.Child("grandchild")
// fmt.Printf("%#v\n", root.Path()) // "[]string(nil)"
// fmt.Printf("%#v\n", child.Path()) // []string{"child"}
// fmt.Printf("%#v\n", grandChild.Path()) // []string{"child", "grandchild"}
//
func (c *Component) Path() []string {
c.l.RLock()
defer c.l.RUnlock()
return c.path
}
func (c *Component) pathStr() string {
path := make([]string, len(c.path))
copy(path, c.path)
for i := range path {
path[i] = strings.ReplaceAll(path[i], "/", `\/`)
}
return "/" + strings.Join(path, "/")
}
type annotateKey string
func (c *Component) getCtx() context.Context {
if c.ctx == nil {
c.ctx = mctx.Annotated(annotateKey("componentPath"), c.pathStr())
}
return c.ctx
}
// Annotate annotates the Component's internal Context in-place, such that they
// will be included in any future calls to the Context method.
func (c *Component) Annotate(kv ...interface{}) {
c.l.Lock()
defer c.l.Unlock()
c.ctx = mctx.Annotate(c.getCtx(), kv...)
}
// Context returns a Context which has been annotated with any annotations from
// Annotate calls to this Component, as well as some default annotations which
// are always included.
func (c *Component) Context() context.Context {
c.l.Lock()
defer c.l.Unlock()
return c.getCtx()
}
// BreadthFirstVisit visits this Component and all of its children, and their
// children, etc... in a breadth-first order. If the callback returns false then
// the function returns without visiting any more Components.
func BreadthFirstVisit(c *Component, callback func(*Component) bool) {
queue := []*Component{c}
for len(queue) > 0 {
if !callback(queue[0]) {
return
}
for _, child := range queue[0].Children() {
queue = append(queue, child)
}
queue = queue[1:]
}
}