internal/objectstore/objectstore.go (147 lines of code) (raw):
package objectstore
import (
"fmt"
"reflect"
"github.com/aws/jsii-runtime-go/internal/api"
)
// stringSet is a set of strings, implemented as a map from string to an
// arbitrary 0-width value.
type stringSet map[string]struct{}
// ObjectStore tracks object instances for which an identifier has been
// associated. Object to instanceID association is tracked using the object
// memory address (aka pointer value) in order to not have issues with go's
// standard object equality rules (we need distinct - but possibly equal) object
// instances to be considered as separate entities for our purposes.
type ObjectStore struct {
// objectToID associates an object's memory address (pointer value) with an
// instanceID. This includes aliases (anonymous embedded values) of objects
// passed to the Register method.
objectToID map[uintptr]string
// idToObject associates an instanceID with the first reflect.Value instance
// that represents the top-level object that was registered with the
// instanceID first via the Register method.
idToObject map[string]reflect.Value
// idToObjects associates an instanceID with the reflect.Value instances that
// represent the top-level objects that were registered with the instanceID
// via the Register method.
idToObjects map[string]map[reflect.Value]struct{}
// idToInterfaces associates an instanceID with the set of interfaces that it
// is known to implement.
//
// Incorrect use of the UnsafeCast function may result in an instance's
// interface list containing interfaces that it does not actually implement.
idToInterfaces map[string]stringSet
}
// New initializes a new ObjectStore.
func New() *ObjectStore {
return &ObjectStore{
objectToID: make(map[uintptr]string),
idToObject: make(map[string]reflect.Value),
idToObjects: make(map[string]map[reflect.Value]struct{}),
idToInterfaces: make(map[string]stringSet),
}
}
// Register associates the provided value with the given instanceID. It also
// registers any anonymously embedded value (transitively) against the same
// instanceID, so that methods promoted from those resolve the correct
// instanceID, too.
//
// Returns an error if the provided value is not a pointer value; if the value
// or any of it's (transitively) anonymous embeds have already been registered
// against a different instanceID; of if the provided instanceID was already
// associated to a different value.
//
// The call is idempotent: calling Register again with the same value and
// instanceID does not result in an error.
func (o *ObjectStore) Register(value reflect.Value, objectRef api.ObjectRef) error {
var err error
if value, err = canonicalValue(value); err != nil {
return err
}
ptr := value.Pointer()
if existing, found := o.objectToID[ptr]; found {
if existing == objectRef.InstanceID {
o.mergeInterfaces(objectRef)
return nil
}
return fmt.Errorf("attempting to register %v as %v, but it was already registered as %v", value, objectRef.InstanceID, existing)
}
aliases := findAliases(value)
if existing, found := o.idToObjects[objectRef.InstanceID]; found {
if _, found := existing[value]; found {
o.mergeInterfaces(objectRef)
return nil
}
// Value already exists (e.g: a constructor made a callback with "this"
// passed as an argument). We make the current value(s) an alias of the new
// one.
for existing := range existing {
aliases = append(aliases, existing)
}
}
for _, alias := range aliases {
ptr := alias.Pointer()
if existing, found := o.objectToID[ptr]; found && existing != objectRef.InstanceID {
return fmt.Errorf("value %v is embedded in %v which has ID %v, but was already assigned %v", alias.String(), value.String(), objectRef.InstanceID, existing)
}
}
o.objectToID[ptr] = objectRef.InstanceID
// Only add to idToObject if this is the first time this InstanceID is registered
if _, found := o.idToObject[objectRef.InstanceID]; !found {
o.idToObject[objectRef.InstanceID] = value
}
if _, found := o.idToObjects[objectRef.InstanceID]; !found {
o.idToObjects[objectRef.InstanceID] = make(map[reflect.Value]struct{})
}
o.idToObjects[objectRef.InstanceID][value] = struct{}{}
for _, alias := range aliases {
o.objectToID[alias.Pointer()] = objectRef.InstanceID
}
o.mergeInterfaces(objectRef)
return nil
}
// mergeInterfaces adds all interfaces carried by the provided objectRef to the
// tracking set for the objectRef's InstanceID. Does nothing if no interfaces
// are designated on the objectRef.
func (o *ObjectStore) mergeInterfaces(objectRef api.ObjectRef) {
// If we don't have interfaces, we have nothing to do...
if objectRef.Interfaces == nil {
return
}
// Find or create the interface list for the relevant InstanceID
var interfaces stringSet
if list, found := o.idToInterfaces[objectRef.InstanceID]; found {
interfaces = list
} else {
interfaces = make(stringSet)
o.idToInterfaces[objectRef.InstanceID] = interfaces
}
// Add any missing interface to the list.
for _, iface := range objectRef.Interfaces {
interfaces[string(iface)] = struct{}{}
}
}
// InstanceID attempts to determine the instanceID associated with the provided
// value, if any. Returns the existing instanceID and a boolean informing
// whether an instanceID was already found or not.
//
// The InstanceID method is safe to call with values that are not track-able in
// an ObjectStore (i.e: non-pointer values, primitive values, etc...).
func (o *ObjectStore) InstanceID(value reflect.Value) (instanceID string, found bool) {
var err error
if value, err = canonicalValue(value); err == nil {
ptr := value.Pointer()
instanceID, found = o.objectToID[ptr]
}
return
}
// Interfaces returns the set of interfaces associated with the provided
// instanceID.
//
// It returns a nil slice in case the instancceID is invalid, or if it does not
// have any associated interfaces.
func (o *ObjectStore) Interfaces(instanceID string) []api.FQN {
if set, found := o.idToInterfaces[instanceID]; found {
interfaces := make([]api.FQN, 0, len(set))
for iface := range set {
interfaces = append(interfaces, api.FQN(iface))
}
return interfaces
} else {
return nil
}
}
// GetObject attempts to retrieve the object value associated with the given
// instanceID. Returns the existing value and a boolean informing whether a
// value was associated with this instanceID or not.
//
// The GetObject method is safe to call with an instanceID that was never
// registered with the ObjectStore.
func (o *ObjectStore) GetObject(instanceID string) (value reflect.Value, found bool) {
value, found = o.idToObject[instanceID]
return
}
// GetObjectAs attempts to retrieve the object value associated with the given
// instanceID, compatible with the given type. Returns the existing value and a
// boolean informing whether a value was associated with this instanceID and
// compatible with this type or not.
//
// The GetObjectAs method is safe to call with an instanceID that was never
// registered with the ObjectStore.
func (o *ObjectStore) GetObjectAs(instanceID string, typ reflect.Type) (value reflect.Value, found bool) {
found = false
if values, exists := o.idToObjects[instanceID]; exists {
for value = range values {
if value.Type().AssignableTo(typ) {
value = value.Convert(typ)
found = true
return
}
}
}
return
}
// canonicalValue ensures the same reference is always considered for object
// identity (especially in maps), so that we don't get surprised by pointer to
// struct versus struct value versus opaque interface value, etc...
func canonicalValue(value reflect.Value) (reflect.Value, error) {
if value.Kind() == reflect.Ptr && value.Elem().Kind() == reflect.Struct {
return value, nil
}
// If this is a pointer to something, de-references it.
result := reflect.ValueOf(reflect.Indirect(value).Interface())
if result.Kind() != reflect.Ptr {
return reflect.Value{}, fmt.Errorf("illegal argument: %v is not a pointer", result.String())
}
return result, nil
}
// findAliases traverses the provided object value to recursively identify all
// anonymous embedded values, which will then be registered against the same
// instanceID as the embedding value.
//
// This function assumes the provided value is either a reflect.Struct or a
// pointer to a reflect.Struct (possibly as a reflect.Interface). Calling with
// a nil value, or a value that is not ultimately a reflect.Struct may result
// in panic.
func findAliases(value reflect.Value) []reflect.Value {
var result []reflect.Value
// Indirect so we always work on the pointer referree
value = reflect.Indirect(value)
t := value.Type()
numField := t.NumField()
for i := 0; i < numField; i++ {
f := t.Field(i)
// Ignore non-anonymous fields (including padding)
if !f.Anonymous {
continue
}
fv := value.FieldByIndex(f.Index)
if fv.Kind() == reflect.Interface {
// If an interface, de-reference to get to the struct type.
fv = reflect.ValueOf(fv.Interface())
}
if fv.Kind() == reflect.Struct {
// If a struct, get the address of the member.
fv = fv.Addr()
}
result = append(result, fv)
// Recurse down to collect nested aliases
result = append(result, findAliases(fv)...)
}
return result
}