Skip to content

Commit 0e1cbbf

Browse files
committed
Add reflector based implementation of value interface
1 parent 446b504 commit 0e1cbbf

File tree

10 files changed

+1190
-2
lines changed

10 files changed

+1190
-2
lines changed

typed/merge.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,7 @@ func (w *mergingWalker) visitMapItems(t *schema.Map, lhs, rhs value.Map) (errs V
328328
if rhs != nil {
329329
rhs.Iterate(func(key string, val value.Value) bool {
330330
if lhs != nil {
331-
if v, ok := lhs.Get(key); ok {
332-
v.Recycle()
331+
if lhs.Has(key) {
333332
return true
334333
}
335334
}

value/jsontagutil.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package value
18+
19+
import (
20+
"reflect"
21+
"strings"
22+
)
23+
24+
// TODO: This implements the same functionality as https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go#L236
25+
// but is based on the highly efficient approach from https://golang.org/src/encoding/json/encode.go
26+
27+
func lookupJsonTags(f reflect.StructField) (string, bool, bool) {
28+
var name string
29+
tag := f.Tag.Get("json")
30+
if tag == "-" {
31+
return f.Name, false, false
32+
}
33+
name, opts := parseTag(tag)
34+
if name == "" {
35+
name = f.Name
36+
}
37+
return name, opts.Contains("inline"), opts.Contains("omitempty")
38+
}
39+
40+
func isZero(v reflect.Value) bool {
41+
switch v.Kind() {
42+
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
43+
return v.Len() == 0
44+
case reflect.Bool:
45+
return !v.Bool()
46+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
47+
return v.Int() == 0
48+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
49+
return v.Uint() == 0
50+
case reflect.Float32, reflect.Float64:
51+
return v.Float() == 0
52+
case reflect.Interface, reflect.Ptr:
53+
return v.IsNil()
54+
}
55+
return false
56+
}
57+
58+
type tagOptions string
59+
60+
// parseTag splits a struct field's json tag into its name and
61+
// comma-separated options.
62+
func parseTag(tag string) (string, tagOptions) {
63+
if idx := strings.Index(tag, ","); idx != -1 {
64+
return tag[:idx], tagOptions(tag[idx+1:])
65+
}
66+
return tag, tagOptions("")
67+
}
68+
69+
// Contains reports whether a comma-separated list of options
70+
// contains a particular substr flag. substr must be surrounded by a
71+
// string boundary or commas.
72+
func (o tagOptions) Contains(optionName string) bool {
73+
if len(o) == 0 {
74+
return false
75+
}
76+
s := string(o)
77+
for s != "" {
78+
var next string
79+
i := strings.Index(s, ",")
80+
if i >= 0 {
81+
s, next = s[:i], s[i+1:]
82+
}
83+
if s == optionName {
84+
return true
85+
}
86+
s = next
87+
}
88+
return false
89+
}

value/listreflect.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package value
18+
19+
import "reflect"
20+
21+
type listReflect struct {
22+
Value reflect.Value
23+
}
24+
25+
func (r listReflect) Length() int {
26+
val := r.Value
27+
return val.Len()
28+
}
29+
30+
func (r listReflect) At(i int) Value {
31+
val := r.Value
32+
return mustWrapValueReflect(val.Index(i))
33+
}
34+
35+
func (r listReflect) Unstructured() interface{} {
36+
l := r.Length()
37+
result := make([]interface{}, l)
38+
for i := 0; i < l; i++ {
39+
result[i] = r.At(i).Unstructured()
40+
}
41+
return result
42+
}

value/map.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ type Map interface {
2727
Set(key string, val Value)
2828
// Get returns the value for the given key, if present, or (nil, false) otherwise.
2929
Get(key string) (Value, bool)
30+
// Has returns true if the key is present, or false otherwise.
31+
Has(key string) bool
3032
// Delete removes the key from the map.
3133
Delete(key string)
3234
// Equals compares the two maps, and return true if they are the same, false otherwise.

value/mapreflect.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package value
18+
19+
import "reflect"
20+
21+
type mapReflect struct {
22+
Value reflect.Value
23+
}
24+
25+
func (r mapReflect) Length() int {
26+
val := r.Value
27+
return val.Len()
28+
}
29+
30+
func (r mapReflect) Get(key string) (Value, bool) {
31+
var val reflect.Value
32+
val = r.Value.MapIndex(r.toMapKey(key))
33+
if !val.IsValid() {
34+
return nil, false
35+
}
36+
return mustWrapValueReflect(val), val != reflect.Value{}
37+
}
38+
39+
func (r mapReflect) Has(key string) bool {
40+
var val reflect.Value
41+
val = r.Value.MapIndex(r.toMapKey(key))
42+
if !val.IsValid() {
43+
return false
44+
}
45+
return val != reflect.Value{}
46+
}
47+
48+
func (r mapReflect) Set(key string, val Value) {
49+
r.Value.SetMapIndex(r.toMapKey(key), reflect.ValueOf(val.Unstructured()))
50+
}
51+
52+
func (r mapReflect) Delete(key string) {
53+
val := r.Value
54+
val.SetMapIndex(r.toMapKey(key), reflect.Value{})
55+
}
56+
57+
// TODO: Do we need to support types that implement json.Marshaler and are used as string keys
58+
func (r mapReflect) toMapKey(key string) reflect.Value {
59+
val := r.Value
60+
return reflect.ValueOf(key).Convert(val.Type().Key())
61+
}
62+
63+
func (r mapReflect) Iterate(fn func(string, Value) bool) bool {
64+
return eachMapEntry(r.Value, func(s string, value reflect.Value) bool {
65+
mapVal := mustWrapValueReflect(value)
66+
defer mapVal.Recycle()
67+
return fn(s, mapVal)
68+
})
69+
}
70+
71+
func eachMapEntry(val reflect.Value, fn func(string, reflect.Value) bool) bool {
72+
iter := val.MapRange()
73+
for iter.Next() {
74+
next := iter.Value()
75+
if !next.IsValid() {
76+
continue
77+
}
78+
if !fn(iter.Key().String(), next) {
79+
return false
80+
}
81+
}
82+
return true
83+
}
84+
85+
func (r mapReflect) Unstructured() interface{} {
86+
result := make(map[string]interface{}, r.Length())
87+
r.Iterate(func(s string, value Value) bool {
88+
result[s] = value.Unstructured()
89+
return true
90+
})
91+
return result
92+
}
93+
94+
func (r mapReflect) Equals(m Map) bool {
95+
if r.Length() != m.Length() {
96+
return false
97+
}
98+
return m.Iterate(func(key string, value Value) bool {
99+
lhsVal, ok := r.Get(key)
100+
if !ok {
101+
return false
102+
}
103+
return Equals(lhsVal, value)
104+
})
105+
}

value/mapunstructured.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ func (m mapUnstructuredInterface) Get(key string) (Value, bool) {
3030
}
3131
}
3232

33+
func (m mapUnstructuredInterface) Has(key string) bool {
34+
_, ok := m[key]
35+
return ok
36+
}
37+
3338
func (m mapUnstructuredInterface) Delete(key string) {
3439
delete(m, key)
3540
}
@@ -93,6 +98,11 @@ func (m mapUnstructuredString) Get(key string) (Value, bool) {
9398
}
9499
}
95100

101+
func (m mapUnstructuredString) Has(key string) bool {
102+
_, ok := m[key]
103+
return ok
104+
}
105+
96106
func (m mapUnstructuredString) Delete(key string) {
97107
delete(m, key)
98108
}

0 commit comments

Comments
 (0)