1// Copyright 2014 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package proptools
16
17import (
18	"fmt"
19	"reflect"
20	"sync"
21)
22
23// CloneProperties takes a reflect.Value of a pointer to a struct and returns a reflect.Value
24// of a pointer to a new struct that copies of the values for its fields.  It recursively clones
25// struct pointers and interfaces that contain struct pointers.
26func CloneProperties(structValue reflect.Value) reflect.Value {
27	if !isStructPtr(structValue.Type()) {
28		panic(fmt.Errorf("CloneProperties expected *struct, got %s", structValue.Type()))
29	}
30	result := reflect.New(structValue.Type().Elem())
31	copyProperties(result.Elem(), structValue.Elem())
32	return result
33}
34
35// CopyProperties takes destination and source reflect.Values of a pointer to structs and returns
36// copies each field from the source into the destination.  It recursively copies struct pointers
37// and interfaces that contain struct pointers.
38func CopyProperties(dstValue, srcValue reflect.Value) {
39	if !isStructPtr(dstValue.Type()) {
40		panic(fmt.Errorf("CopyProperties expected dstValue *struct, got %s", dstValue.Type()))
41	}
42	if !isStructPtr(srcValue.Type()) {
43		panic(fmt.Errorf("CopyProperties expected srcValue *struct, got %s", srcValue.Type()))
44	}
45	copyProperties(dstValue.Elem(), srcValue.Elem())
46}
47
48func copyProperties(dstValue, srcValue reflect.Value) {
49	typ := dstValue.Type()
50	if srcValue.Type() != typ {
51		panic(fmt.Errorf("can't copy mismatching types (%s <- %s)",
52			dstValue.Kind(), srcValue.Kind()))
53	}
54
55	for i, field := range typeFields(typ) {
56		if field.PkgPath != "" {
57			panic(fmt.Errorf("can't copy a private field %q", field.Name))
58		}
59
60		srcFieldValue := srcValue.Field(i)
61		dstFieldValue := dstValue.Field(i)
62		dstFieldInterfaceValue := reflect.Value{}
63		origDstFieldValue := dstFieldValue
64
65		switch srcFieldValue.Kind() {
66		case reflect.Bool, reflect.String, reflect.Int, reflect.Uint:
67			dstFieldValue.Set(srcFieldValue)
68		case reflect.Struct:
69			copyProperties(dstFieldValue, srcFieldValue)
70		case reflect.Slice:
71			if !srcFieldValue.IsNil() {
72				if srcFieldValue != dstFieldValue {
73					newSlice := reflect.MakeSlice(field.Type, srcFieldValue.Len(),
74						srcFieldValue.Len())
75					reflect.Copy(newSlice, srcFieldValue)
76					dstFieldValue.Set(newSlice)
77				}
78			} else {
79				dstFieldValue.Set(srcFieldValue)
80			}
81		case reflect.Interface:
82			if srcFieldValue.IsNil() {
83				dstFieldValue.Set(srcFieldValue)
84				break
85			}
86
87			srcFieldValue = srcFieldValue.Elem()
88
89			if !isStructPtr(srcFieldValue.Type()) {
90				panic(fmt.Errorf("can't clone field %q: expected interface to contain *struct, found %s",
91					field.Name, srcFieldValue.Type()))
92			}
93
94			if dstFieldValue.IsNil() || dstFieldValue.Elem().Type() != srcFieldValue.Type() {
95				// We can't use the existing destination allocation, so
96				// clone a new one.
97				newValue := reflect.New(srcFieldValue.Type()).Elem()
98				dstFieldValue.Set(newValue)
99				dstFieldInterfaceValue = dstFieldValue
100				dstFieldValue = newValue
101			} else {
102				dstFieldValue = dstFieldValue.Elem()
103			}
104			fallthrough
105		case reflect.Ptr:
106			if srcFieldValue.IsNil() {
107				origDstFieldValue.Set(srcFieldValue)
108				break
109			}
110
111			switch srcFieldValue.Elem().Kind() {
112			case reflect.Struct:
113				if !dstFieldValue.IsNil() {
114					// Re-use the existing allocation.
115					copyProperties(dstFieldValue.Elem(), srcFieldValue.Elem())
116					break
117				} else {
118					newValue := CloneProperties(srcFieldValue)
119					if dstFieldInterfaceValue.IsValid() {
120						dstFieldInterfaceValue.Set(newValue)
121					} else {
122						origDstFieldValue.Set(newValue)
123					}
124				}
125			case reflect.Bool, reflect.Int64, reflect.String:
126				newValue := reflect.New(srcFieldValue.Elem().Type())
127				newValue.Elem().Set(srcFieldValue.Elem())
128				origDstFieldValue.Set(newValue)
129			default:
130				panic(fmt.Errorf("can't clone pointer field %q type %s",
131					field.Name, srcFieldValue.Type()))
132			}
133		default:
134			panic(fmt.Errorf("unexpected type for property struct field %q: %s",
135				field.Name, srcFieldValue.Type()))
136		}
137	}
138}
139
140// ZeroProperties takes a reflect.Value of a pointer to a struct and replaces all of its fields
141// with zero values, recursing into struct, pointer to struct and interface fields.
142func ZeroProperties(structValue reflect.Value) {
143	if !isStructPtr(structValue.Type()) {
144		panic(fmt.Errorf("ZeroProperties expected *struct, got %s", structValue.Type()))
145	}
146	zeroProperties(structValue.Elem())
147}
148
149func zeroProperties(structValue reflect.Value) {
150	typ := structValue.Type()
151
152	for i, field := range typeFields(typ) {
153		if field.PkgPath != "" {
154			// The field is not exported so just skip it.
155			continue
156		}
157
158		fieldValue := structValue.Field(i)
159
160		switch fieldValue.Kind() {
161		case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
162			fieldValue.Set(reflect.Zero(fieldValue.Type()))
163		case reflect.Interface:
164			if fieldValue.IsNil() {
165				break
166			}
167
168			// We leave the pointer intact and zero out the struct that's
169			// pointed to.
170			fieldValue = fieldValue.Elem()
171			if !isStructPtr(fieldValue.Type()) {
172				panic(fmt.Errorf("can't zero field %q: expected interface to contain *struct, found %s",
173					field.Name, fieldValue.Type()))
174			}
175			fallthrough
176		case reflect.Ptr:
177			switch fieldValue.Type().Elem().Kind() {
178			case reflect.Struct:
179				if fieldValue.IsNil() {
180					break
181				}
182				zeroProperties(fieldValue.Elem())
183			case reflect.Bool, reflect.Int64, reflect.String:
184				fieldValue.Set(reflect.Zero(fieldValue.Type()))
185			default:
186				panic(fmt.Errorf("can't zero field %q: points to a %s",
187					field.Name, fieldValue.Elem().Kind()))
188			}
189		case reflect.Struct:
190			zeroProperties(fieldValue)
191		default:
192			panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
193				field.Name, fieldValue.Kind()))
194		}
195	}
196}
197
198// CloneEmptyProperties takes a reflect.Value of a pointer to a struct and returns a reflect.Value
199// of a pointer to a new struct that has the zero values for its fields.  It recursively clones
200// struct pointers and interfaces that contain struct pointers.
201func CloneEmptyProperties(structValue reflect.Value) reflect.Value {
202	if !isStructPtr(structValue.Type()) {
203		panic(fmt.Errorf("CloneEmptyProperties expected *struct, got %s", structValue.Type()))
204	}
205	result := reflect.New(structValue.Type().Elem())
206	cloneEmptyProperties(result.Elem(), structValue.Elem())
207	return result
208}
209
210func cloneEmptyProperties(dstValue, srcValue reflect.Value) {
211	typ := srcValue.Type()
212	for i, field := range typeFields(typ) {
213		if field.PkgPath != "" {
214			// The field is not exported so just skip it.
215			continue
216		}
217
218		srcFieldValue := srcValue.Field(i)
219		dstFieldValue := dstValue.Field(i)
220		dstFieldInterfaceValue := reflect.Value{}
221
222		switch srcFieldValue.Kind() {
223		case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
224			// Nothing
225		case reflect.Struct:
226			cloneEmptyProperties(dstFieldValue, srcFieldValue)
227		case reflect.Interface:
228			if srcFieldValue.IsNil() {
229				break
230			}
231
232			srcFieldValue = srcFieldValue.Elem()
233			if !isStructPtr(srcFieldValue.Type()) {
234				panic(fmt.Errorf("can't clone empty field %q: expected interface to contain *struct, found %s",
235					field.Name, srcFieldValue.Type()))
236			}
237
238			newValue := reflect.New(srcFieldValue.Type()).Elem()
239			dstFieldValue.Set(newValue)
240			dstFieldInterfaceValue = dstFieldValue
241			dstFieldValue = newValue
242			fallthrough
243		case reflect.Ptr:
244			switch srcFieldValue.Type().Elem().Kind() {
245			case reflect.Struct:
246				if srcFieldValue.IsNil() {
247					break
248				}
249				newValue := CloneEmptyProperties(srcFieldValue)
250				if dstFieldInterfaceValue.IsValid() {
251					dstFieldInterfaceValue.Set(newValue)
252				} else {
253					dstFieldValue.Set(newValue)
254				}
255			case reflect.Bool, reflect.Int64, reflect.String:
256				// Nothing
257			default:
258				panic(fmt.Errorf("can't clone empty field %q: points to a %s",
259					field.Name, srcFieldValue.Elem().Kind()))
260			}
261
262		default:
263			panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
264				field.Name, srcFieldValue.Kind()))
265		}
266	}
267}
268
269var typeFieldCache sync.Map
270
271func typeFields(typ reflect.Type) []reflect.StructField {
272	// reflect.Type.Field allocates a []int{} to hold the index every time it is called, which ends up
273	// being a significant portion of the GC pressure.  It can't reuse the same one in case a caller
274	// modifies the backing array through the slice.  Since we don't modify it, cache the result
275	// locally to reduce allocations.
276
277	// Fast path
278	if typeFields, ok := typeFieldCache.Load(typ); ok {
279		return typeFields.([]reflect.StructField)
280	}
281
282	// Slow path
283	typeFields := make([]reflect.StructField, typ.NumField())
284
285	for i := range typeFields {
286		typeFields[i] = typ.Field(i)
287	}
288
289	typeFieldCache.Store(typ, typeFields)
290
291	return typeFields
292}
293