diff --git a/ctx/ctx.go b/ctx/ctx.go new file mode 100644 index 0000000..b9bd058 --- /dev/null +++ b/ctx/ctx.go @@ -0,0 +1,105 @@ +package ctx + +import ( + "reflect" + "sync" +) + +func NewCtx(parent Ctx) Ctx { + c := &defaultCtx{ + parent: parent, + } + c.attributes = make(map[interface{}]interface{}) + return c +} + +type Ctx interface { + Parent() Ctx + SetAttribute(key interface{}, value interface{}) + GetAttribute(key interface{}) (value interface{}) + RemoveAttribute(key interface{}) + ContainsAttribute(key interface{}) (exist bool) +} + +type defaultCtx struct { + parent Ctx + attributes map[interface{}]interface{} + + mtx sync.RWMutex +} + +func (dc *defaultCtx) Parent() Ctx { + return dc.parent +} + +func (dc *defaultCtx) SetAttribute(key interface{}, value interface{}) { + dc.checkInitialized() + + if key == nil { + panic("nil key") + } + if !reflect.TypeOf(key).Comparable() { + panic("key is not comparable") + } + + dc.mtx.Lock() + defer dc.mtx.Unlock() + + dc.attributes[key] = value +} + +func (dc *defaultCtx) GetAttribute(key interface{}) (value interface{}) { + dc.checkInitialized() + + dc.mtx.RLock() + defer dc.mtx.RUnlock() + + if _, ok := dc.attributes[key]; ok { + return dc.attributes[key] + } + + if nil == dc.parent { + return nil + } + return dc.parent.GetAttribute(key) +} + +func (dc *defaultCtx) RemoveAttribute(key interface{}) { + dc.checkInitialized() + + dc.mtx.Lock() + defer dc.mtx.Unlock() + + if _, ok := dc.attributes[key]; ok { + delete(dc.attributes, key) + return + } + + if nil == dc.parent { + return + } + + dc.parent.RemoveAttribute(key) +} + +func (dc *defaultCtx) ContainsAttribute(key interface{}) (exist bool) { + dc.checkInitialized() + + dc.mtx.RLock() + defer dc.mtx.RUnlock() + + if _, ok := dc.attributes[key]; ok { + return true + } + + if nil == dc.parent { + return false + } + return dc.parent.ContainsAttribute(key) +} + +func (dc *defaultCtx) checkInitialized() { + if nil == dc.attributes { + panic("Attribute Manager: must be initialized") + } +}