-
Notifications
You must be signed in to change notification settings - Fork 242
/
objectstore.go
265 lines (232 loc) · 9.07 KB
/
objectstore.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
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 %s as %s, but it was already registered as %s", 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 %s is embedded in %s which has ID %s, but was already assigned %s", 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.CanConvert(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: %s 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
}