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)
21
22func CloneProperties(structValue reflect.Value) reflect.Value {
23	result := reflect.New(structValue.Type())
24	CopyProperties(result.Elem(), structValue)
25	return result
26}
27
28func CopyProperties(dstValue, srcValue reflect.Value) {
29	typ := dstValue.Type()
30	if srcValue.Type() != typ {
31		panic(fmt.Errorf("can't copy mismatching types (%s <- %s)",
32			dstValue.Kind(), srcValue.Kind()))
33	}
34
35	for i := 0; i < srcValue.NumField(); i++ {
36		field := typ.Field(i)
37		if field.PkgPath != "" {
38			// The field is not exported so just skip it.
39			continue
40		}
41
42		srcFieldValue := srcValue.Field(i)
43		dstFieldValue := dstValue.Field(i)
44		dstFieldInterfaceValue := reflect.Value{}
45
46		switch srcFieldValue.Kind() {
47		case reflect.Bool, reflect.String, reflect.Int, reflect.Uint:
48			dstFieldValue.Set(srcFieldValue)
49		case reflect.Struct:
50			CopyProperties(dstFieldValue, srcFieldValue)
51		case reflect.Slice:
52			if !srcFieldValue.IsNil() {
53				if field.Type.Elem().Kind() != reflect.String {
54					panic(fmt.Errorf("can't copy field %q: slice elements are not strings", field.Name))
55				}
56				if srcFieldValue != dstFieldValue {
57					newSlice := reflect.MakeSlice(field.Type, srcFieldValue.Len(),
58						srcFieldValue.Len())
59					reflect.Copy(newSlice, srcFieldValue)
60					dstFieldValue.Set(newSlice)
61				}
62			} else {
63				dstFieldValue.Set(srcFieldValue)
64			}
65		case reflect.Interface:
66			if srcFieldValue.IsNil() {
67				dstFieldValue.Set(srcFieldValue)
68				break
69			}
70
71			srcFieldValue = srcFieldValue.Elem()
72
73			if srcFieldValue.Kind() != reflect.Ptr {
74				panic(fmt.Errorf("can't clone field %q: interface refers to a non-pointer",
75					field.Name))
76			}
77			if srcFieldValue.Type().Elem().Kind() != reflect.Struct {
78				panic(fmt.Errorf("can't clone field %q: interface points to a non-struct",
79					field.Name))
80			}
81
82			if dstFieldValue.IsNil() || dstFieldValue.Elem().Type() != srcFieldValue.Type() {
83				// We can't use the existing destination allocation, so
84				// clone a new one.
85				newValue := reflect.New(srcFieldValue.Type()).Elem()
86				dstFieldValue.Set(newValue)
87				dstFieldInterfaceValue = dstFieldValue
88				dstFieldValue = newValue
89			} else {
90				dstFieldValue = dstFieldValue.Elem()
91			}
92			fallthrough
93		case reflect.Ptr:
94			if srcFieldValue.IsNil() {
95				dstFieldValue.Set(srcFieldValue)
96				break
97			}
98
99			srcFieldValue := srcFieldValue.Elem()
100
101			switch srcFieldValue.Kind() {
102			case reflect.Struct:
103				if !dstFieldValue.IsNil() {
104					// Re-use the existing allocation.
105					CopyProperties(dstFieldValue.Elem(), srcFieldValue)
106					break
107				} else {
108					newValue := CloneProperties(srcFieldValue)
109					if dstFieldInterfaceValue.IsValid() {
110						dstFieldInterfaceValue.Set(newValue)
111					} else {
112						dstFieldValue.Set(newValue)
113					}
114				}
115			case reflect.Bool, reflect.String:
116				newValue := reflect.New(srcFieldValue.Type())
117				newValue.Elem().Set(srcFieldValue)
118				dstFieldValue.Set(newValue)
119			default:
120				panic(fmt.Errorf("can't clone field %q: points to a %s",
121					field.Name, srcFieldValue.Kind()))
122			}
123		default:
124			panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
125				field.Name, srcFieldValue.Kind()))
126		}
127	}
128}
129
130func ZeroProperties(structValue reflect.Value) {
131	typ := structValue.Type()
132
133	for i := 0; i < structValue.NumField(); i++ {
134		field := typ.Field(i)
135		if field.PkgPath != "" {
136			// The field is not exported so just skip it.
137			continue
138		}
139
140		fieldValue := structValue.Field(i)
141
142		switch fieldValue.Kind() {
143		case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
144			fieldValue.Set(reflect.Zero(fieldValue.Type()))
145		case reflect.Interface:
146			if fieldValue.IsNil() {
147				break
148			}
149
150			// We leave the pointer intact and zero out the struct that's
151			// pointed to.
152			fieldValue = fieldValue.Elem()
153			if fieldValue.Kind() != reflect.Ptr {
154				panic(fmt.Errorf("can't zero field %q: interface refers to a non-pointer",
155					field.Name))
156			}
157			if fieldValue.Type().Elem().Kind() != reflect.Struct {
158				panic(fmt.Errorf("can't zero field %q: interface points to a non-struct",
159					field.Name))
160			}
161			fallthrough
162		case reflect.Ptr:
163			switch fieldValue.Type().Elem().Kind() {
164			case reflect.Struct:
165				if fieldValue.IsNil() {
166					break
167				}
168				ZeroProperties(fieldValue.Elem())
169			case reflect.Bool, reflect.String:
170				fieldValue.Set(reflect.Zero(fieldValue.Type()))
171			default:
172				panic(fmt.Errorf("can't zero field %q: points to a %s",
173					field.Name, fieldValue.Elem().Kind()))
174			}
175		case reflect.Struct:
176			ZeroProperties(fieldValue)
177		default:
178			panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
179				field.Name, fieldValue.Kind()))
180		}
181	}
182}
183
184func CloneEmptyProperties(structValue reflect.Value) reflect.Value {
185	result := reflect.New(structValue.Type())
186	cloneEmptyProperties(result.Elem(), structValue)
187	return result
188}
189
190func cloneEmptyProperties(dstValue, srcValue reflect.Value) {
191	typ := srcValue.Type()
192	for i := 0; i < srcValue.NumField(); i++ {
193		field := typ.Field(i)
194		if field.PkgPath != "" {
195			// The field is not exported so just skip it.
196			continue
197		}
198
199		srcFieldValue := srcValue.Field(i)
200		dstFieldValue := dstValue.Field(i)
201		dstFieldInterfaceValue := reflect.Value{}
202
203		switch srcFieldValue.Kind() {
204		case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
205			// Nothing
206		case reflect.Struct:
207			cloneEmptyProperties(dstFieldValue, srcFieldValue)
208		case reflect.Interface:
209			if srcFieldValue.IsNil() {
210				break
211			}
212
213			srcFieldValue = srcFieldValue.Elem()
214			if srcFieldValue.Kind() != reflect.Ptr {
215				panic(fmt.Errorf("can't clone empty field %q: interface refers to a non-pointer",
216					field.Name))
217			}
218			if srcFieldValue.Type().Elem().Kind() != reflect.Struct {
219				panic(fmt.Errorf("can't clone empty field %q: interface points to a non-struct",
220					field.Name))
221			}
222
223			newValue := reflect.New(srcFieldValue.Type()).Elem()
224			dstFieldValue.Set(newValue)
225			dstFieldInterfaceValue = dstFieldValue
226			dstFieldValue = newValue
227			fallthrough
228		case reflect.Ptr:
229			switch srcFieldValue.Type().Elem().Kind() {
230			case reflect.Struct:
231				if srcFieldValue.IsNil() {
232					break
233				}
234				newValue := CloneEmptyProperties(srcFieldValue.Elem())
235				if dstFieldInterfaceValue.IsValid() {
236					dstFieldInterfaceValue.Set(newValue)
237				} else {
238					dstFieldValue.Set(newValue)
239				}
240			case reflect.Bool, reflect.String:
241				// Nothing
242			default:
243				panic(fmt.Errorf("can't clone empty field %q: points to a %s",
244					field.Name, srcFieldValue.Elem().Kind()))
245			}
246
247		default:
248			panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
249				field.Name, srcFieldValue.Kind()))
250		}
251	}
252}
253