/
runtime.go
444 lines (372 loc) · 12.3 KB
/
runtime.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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
package runtime
import (
"fmt"
"reflect"
"strings"
"github.com/aws/jsii-runtime-go/internal/api"
"github.com/aws/jsii-runtime-go/internal/kernel"
)
// FQN represents a fully-qualified type name in the jsii type system.
type FQN api.FQN
// Member is a runtime descriptor for a class or interface member
type Member interface {
toOverride() api.Override
}
// MemberMethod is a runtime descriptor for a class method (implementation of Member)
type MemberMethod api.MethodOverride
func (m MemberMethod) toOverride() api.Override {
return api.MethodOverride(m)
}
// MemberProperty is a runtime descriptor for a class or interface property (implementation of Member)
type MemberProperty api.PropertyOverride
func (m MemberProperty) toOverride() api.Override {
return api.PropertyOverride(m)
}
// Load ensures a npm package is loaded in the jsii kernel.
func Load(name string, version string, tarball []byte) {
c := kernel.GetClient()
_, err := c.Load(kernel.LoadProps{
Name: name,
Version: version,
}, tarball)
if err != nil {
panic(err)
}
}
// RegisterClass associates a class fully qualified name to the specified class
// interface, member list, and proxy maker function. Panics if class is not a go
// interface, or if the provided fqn was already used to register a different type.
func RegisterClass(fqn FQN, class reflect.Type, members []Member, maker func() interface{}) {
client := kernel.GetClient()
overrides := make([]api.Override, len(members))
for i, m := range members {
overrides[i] = m.toOverride()
}
if err := client.Types().RegisterClass(api.FQN(fqn), class, overrides, maker); err != nil {
panic(err)
}
}
// RegisterEnum associates an enum's fully qualified name to the specified enum
// type, and members. Panics if enum is not a reflect.String type, any value in
// the provided members map is of a type other than enum, or if the provided
// fqn was already used to register a different type.
func RegisterEnum(fqn FQN, enum reflect.Type, members map[string]interface{}) {
client := kernel.GetClient()
if err := client.Types().RegisterEnum(api.FQN(fqn), enum, members); err != nil {
panic(err)
}
}
// RegisterInterface associates an interface's fully qualified name to the
// specified interface type, member list, and proxy maker function. Panics if iface is not
// an interface, or if the provided fqn was already used to register a different type.
func RegisterInterface(fqn FQN, iface reflect.Type, members []Member, maker func() interface{}) {
client := kernel.GetClient()
overrides := make([]api.Override, len(members))
for i, m := range members {
overrides[i] = m.toOverride()
}
if err := client.Types().RegisterInterface(api.FQN(fqn), iface, overrides, maker); err != nil {
panic(err)
}
}
// RegisterStruct associates a struct's fully qualified name to the specified
// struct type. Panics if strct is not a struct, or if the provided fqn was
// already used to register a different type.
func RegisterStruct(fqn FQN, strct reflect.Type) {
client := kernel.GetClient()
if err := client.Types().RegisterStruct(api.FQN(fqn), strct); err != nil {
panic(err)
}
}
// RegisterStructValidator adds a validator function to an already registered
// struct type. This is separate call largely to maintain backwards compatibility
// with existing code.
func RegisterStructValidator(strct reflect.Type, validator func(interface{}, func() string) error) {
client := kernel.GetClient()
if err := client.Types().RegisterStructValidator(strct, validator); err != nil {
panic(err)
}
}
// InitJsiiProxy initializes a jsii proxy instance at the provided pointer.
// Panics if the pointer cannot be initialized to a proxy instance (i.e: the
// element of it is not a registered jsii interface or class type).
func InitJsiiProxy(ptr interface{}) {
ptrVal := reflect.ValueOf(ptr).Elem()
if err := kernel.GetClient().Types().InitJsiiProxy(ptrVal, ptrVal.Type()); err != nil {
panic(err)
}
}
// IsAnonymousProxy tells whether the value v is an anonymous object proxy, or
// a pointer to one.
func IsAnonymousProxy(v interface{}) bool {
return kernel.GetClient().Types().IsAnonymousProxy(v)
}
// Create will construct a new JSII object within the kernel runtime. This is
// called by jsii object constructors.
func Create(fqn FQN, args []interface{}, inst interface{}) {
client := kernel.GetClient()
instVal := reflect.ValueOf(inst)
structVal := instVal.Elem()
instType := structVal.Type()
numField := instType.NumField()
for i := 0; i < numField; i++ {
field := instType.Field(i)
if !field.Anonymous {
continue
}
switch field.Type.Kind() {
case reflect.Interface:
fieldVal := structVal.Field(i)
if !fieldVal.IsNil() {
continue
}
if err := client.Types().InitJsiiProxy(fieldVal, fieldVal.Type()); err != nil {
panic(err)
}
case reflect.Struct:
fieldVal := structVal.Field(i)
if !fieldVal.IsZero() {
continue
}
if err := client.Types().InitJsiiProxy(fieldVal, fieldVal.Type()); err != nil {
panic(err)
}
}
}
// Find method overrides thru reflection
mOverrides := getMethodOverrides(inst, "jsiiProxy_")
// If overriding struct has no overriding methods, could happen if
// overriding methods are not defined with pointer receiver.
if len(mOverrides) == 0 && !strings.HasPrefix(instType.Name(), "jsiiProxy_") {
panic(fmt.Errorf("%v has no overriding methods. Overriding methods must be defined with a pointer receiver", instType.Name()))
}
var overrides []api.Override
registry := client.Types()
added := make(map[string]bool)
for _, name := range mOverrides {
// Use getter's name even if setter is overriden
if strings.HasPrefix(name, "Set") {
propName := name[3:]
if override, ok := registry.GetOverride(api.FQN(fqn), propName); ok {
if !added[propName] {
added[propName] = true
overrides = append(overrides, override)
}
continue
}
}
if override, ok := registry.GetOverride(api.FQN(fqn), name); ok {
if !added[name] {
added[name] = true
overrides = append(overrides, override)
}
}
}
interfaces, newOverrides := client.Types().DiscoverImplementation(instType)
overrides = append(overrides, newOverrides...)
res, err := client.Create(kernel.CreateProps{
FQN: api.FQN(fqn),
Arguments: convertArguments(args),
Interfaces: interfaces,
Overrides: overrides,
})
if err != nil {
panic(err)
}
if err = client.RegisterInstance(instVal, api.ObjectRef{InstanceID: res.InstanceID, Interfaces: interfaces}); err != nil {
panic(err)
}
}
// Invoke will call a method on a jsii class instance. The response will be
// decoded into the expected return type for the method being called.
func Invoke(obj interface{}, method string, args []interface{}, ret interface{}) {
client := kernel.GetClient()
// Find reference to class instance in client
ref, found := client.FindObjectRef(reflect.ValueOf(obj))
if !found {
panic("No Object Found")
}
res, err := client.Invoke(kernel.InvokeProps{
Method: method,
Arguments: convertArguments(args),
ObjRef: ref,
})
if err != nil {
panic(err)
}
client.CastAndSetToPtr(ret, res.Result)
}
// InvokeVoid will call a void method on a jsii class instance.
func InvokeVoid(obj interface{}, method string, args []interface{}) {
client := kernel.GetClient()
// Find reference to class instance in client
ref, found := client.FindObjectRef(reflect.ValueOf(obj))
if !found {
panic("No Object Found")
}
_, err := client.Invoke(kernel.InvokeProps{
Method: method,
Arguments: convertArguments(args),
ObjRef: ref,
})
if err != nil {
panic(err)
}
}
// StaticInvoke will call a static method on a given jsii class. The response
// will be decoded into the expected return type for the method being called.
func StaticInvoke(fqn FQN, method string, args []interface{}, ret interface{}) {
client := kernel.GetClient()
res, err := client.SInvoke(kernel.StaticInvokeProps{
FQN: api.FQN(fqn),
Method: method,
Arguments: convertArguments(args),
})
if err != nil {
panic(err)
}
client.CastAndSetToPtr(ret, res.Result)
}
// StaticInvokeVoid will call a static void method on a given jsii class.
func StaticInvokeVoid(fqn FQN, method string, args []interface{}) {
client := kernel.GetClient()
_, err := client.SInvoke(kernel.StaticInvokeProps{
FQN: api.FQN(fqn),
Method: method,
Arguments: convertArguments(args),
})
if err != nil {
panic(err)
}
}
// Get reads a property value on a given jsii class instance. The response
// should be decoded into the expected type of the property being read.
func Get(obj interface{}, property string, ret interface{}) {
client := kernel.GetClient()
// Find reference to class instance in client
ref, found := client.FindObjectRef(reflect.ValueOf(obj))
if !found {
panic(fmt.Errorf("no object reference found for %v", obj))
}
res, err := client.Get(kernel.GetProps{
Property: property,
ObjRef: ref,
})
if err != nil {
panic(err)
}
client.CastAndSetToPtr(ret, res.Value)
}
// StaticGet reads a static property value on a given jsii class. The response
// should be decoded into the expected type of the property being read.
func StaticGet(fqn FQN, property string, ret interface{}) {
client := kernel.GetClient()
res, err := client.SGet(kernel.StaticGetProps{
FQN: api.FQN(fqn),
Property: property,
})
if err != nil {
panic(err)
}
client.CastAndSetToPtr(ret, res.Value)
}
// Set writes a property on a given jsii class instance. The value should match
// the type of the property being written, or the jsii kernel will crash.
func Set(obj interface{}, property string, value interface{}) {
client := kernel.GetClient()
// Find reference to class instance in client
ref, found := client.FindObjectRef(reflect.ValueOf(obj))
if !found {
panic("No Object Found")
}
_, err := client.Set(kernel.SetProps{
Property: property,
Value: client.CastPtrToRef(reflect.ValueOf(value)),
ObjRef: ref,
})
if err != nil {
panic(err)
}
}
// StaticSet writes a static property on a given jsii class. The value should
// match the type of the property being written, or the jsii kernel will crash.
func StaticSet(fqn FQN, property string, value interface{}) {
client := kernel.GetClient()
_, err := client.SSet(kernel.StaticSetProps{
FQN: api.FQN(fqn),
Property: property,
Value: client.CastPtrToRef(reflect.ValueOf(value)),
})
if err != nil {
panic(err)
}
}
// convertArguments turns an argument struct and produces a list of values
// ready for inclusion in an invoke or create request.
func convertArguments(args []interface{}) []interface{} {
if len(args) == 0 {
return nil
}
result := make([]interface{}, len(args))
client := kernel.GetClient()
for i, arg := range args {
val := reflect.ValueOf(arg)
result[i] = client.CastPtrToRef(val)
}
return result
}
// Get ptr's methods names which override "base" struct methods.
// The "base" struct is identified by name prefix "basePrefix".
func getMethodOverrides(ptr interface{}, basePrefix string) (methods []string) {
// Methods override cache: [methodName]bool
mCache := make(map[string]bool)
getMethodOverridesRec(ptr, basePrefix, mCache)
// Return overriden methods names in embedding hierarchy
for m := range mCache {
methods = append(methods, m)
}
return
}
func getMethodOverridesRec(ptr interface{}, basePrefix string, cache map[string]bool) {
ptrType := reflect.TypeOf(ptr)
if ptrType.Kind() != reflect.Ptr {
return
}
structType := ptrType.Elem()
if structType.Kind() != reflect.Struct {
return
}
if strings.HasPrefix(structType.Name(), basePrefix) {
// Skip base class
return
}
ptrVal := reflect.ValueOf(ptr)
structVal := ptrVal.Elem()
// Add embedded/super overrides first
for i := 0; i < structType.NumField(); i++ {
field := structType.Field(i)
if !field.Anonymous {
continue
}
if field.Type.Kind() == reflect.Ptr ||
field.Type.Kind() == reflect.Interface {
p := structVal.Field(i)
if !p.IsNil() {
getMethodOverridesRec(p.Interface(), basePrefix, cache)
}
}
}
// Add overrides in current struct
// Current struct's value-type method-set
valMethods := make(map[string]bool)
for i := 0; i < structType.NumMethod(); i++ {
valMethods[structType.Method(i).Name] = true
}
// Compare current struct's pointer-type method-set to its value-type method-set
for i := 0; i < ptrType.NumMethod(); i++ {
mn := ptrType.Method(i).Name
if !valMethods[mn] {
cache[mn] = true
}
}
}