/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package markers import ( "fmt" "sync" ) // Registry keeps track of registered definitions, and allows for easy lookup. // It's thread-safe, and the zero-value can be safely used. type Registry struct { forPkg map[string]*Definition forType map[string]*Definition forField map[string]*Definition helpFor map[*Definition]*DefinitionHelp mu sync.RWMutex initOnce sync.Once } func (r *Registry) init() { r.initOnce.Do(func() { if r.forPkg == nil { r.forPkg = make(map[string]*Definition) } if r.forType == nil { r.forType = make(map[string]*Definition) } if r.forField == nil { r.forField = make(map[string]*Definition) } if r.helpFor == nil { r.helpFor = make(map[*Definition]*DefinitionHelp) } }) } // Define defines a new marker with the given name, target, and output type. // It's a shortcut around // r.Register(MakeDefinition(name, target, obj)) func (r *Registry) Define(name string, target TargetType, obj interface{}) error { def, err := MakeDefinition(name, target, obj) if err != nil { return err } return r.Register(def) } // Register registers the given marker definition with this registry for later lookup. func (r *Registry) Register(def *Definition) error { r.init() r.mu.Lock() defer r.mu.Unlock() switch def.Target { case DescribesPackage: r.forPkg[def.Name] = def case DescribesType: r.forType[def.Name] = def case DescribesField: r.forField[def.Name] = def default: return fmt.Errorf("unknown target type %v", def.Target) } return nil } // AddHelp stores the given help in the registry, marking it as associated with // the given definition. func (r *Registry) AddHelp(def *Definition, help *DefinitionHelp) { r.init() r.mu.Lock() defer r.mu.Unlock() r.helpFor[def] = help } // Lookup fetches the definition corresponding to the given name and target type. func (r *Registry) Lookup(name string, target TargetType) *Definition { r.init() r.mu.RLock() defer r.mu.RUnlock() switch target { case DescribesPackage: return tryAnonLookup(name, r.forPkg) case DescribesType: return tryAnonLookup(name, r.forType) case DescribesField: return tryAnonLookup(name, r.forField) default: return nil } } // HelpFor fetches the help for a given definition, if present. func (r *Registry) HelpFor(def *Definition) *DefinitionHelp { r.init() r.mu.RLock() defer r.mu.RUnlock() return r.helpFor[def] } // AllDefinitions returns all marker definitions known to this registry. func (r *Registry) AllDefinitions() []*Definition { res := make([]*Definition, 0, len(r.forPkg)+len(r.forType)+len(r.forField)) for _, def := range r.forPkg { res = append(res, def) } for _, def := range r.forType { res = append(res, def) } for _, def := range r.forField { res = append(res, def) } return res } // tryAnonLookup tries looking up the given marker as both an struct-based // marker and an anonymous marker, returning whichever format matches first, // preferring the longer (anonymous) name in case of conflicts. func tryAnonLookup(name string, defs map[string]*Definition) *Definition { // NB(directxman12): we look up anonymous names first to work with // legacy style marker definitions that have a namespaced approach // (e.g. deepcopy-gen, which uses `+k8s:deepcopy-gen=foo,bar` *and* // `+k8s.io:deepcopy-gen:interfaces=foo`). name, anonName, _ := splitMarker(name) if def, exists := defs[anonName]; exists { return def } return defs[name] }