1// Copyright 2015 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
22// AppendProperties appends the values of properties in the property struct src to the property
23// struct dst. dst and src must be the same type, and both must be pointers to structs.
24//
25// The filter function can prevent individual properties from being appended by returning false, or
26// abort AppendProperties with an error by returning an error.  Passing nil for filter will append
27// all properties.
28//
29// An error returned by AppendProperties that applies to a specific property will be an
30// *ExtendPropertyError, and can have the property name and error extracted from it.
31//
32// The append operation is defined as appending strings and slices of strings normally, OR-ing bool
33// values, replacing non-nil pointers to booleans or strings, and recursing into
34// embedded structs, pointers to structs, and interfaces containing
35// pointers to structs.  Appending the zero value of a property will always be a no-op.
36func AppendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc) error {
37	return extendProperties(dst, src, filter, false)
38}
39
40// PrependProperties prepends the values of properties in the property struct src to the property
41// struct dst. dst and src must be the same type, and both must be pointers to structs.
42//
43// The filter function can prevent individual properties from being prepended by returning false, or
44// abort PrependProperties with an error by returning an error.  Passing nil for filter will prepend
45// all properties.
46//
47// An error returned by PrependProperties that applies to a specific property will be an
48// *ExtendPropertyError, and can have the property name and error extracted from it.
49//
50// The prepend operation is defined as prepending strings, and slices of strings normally, OR-ing
51// bool values, replacing non-nil pointers to booleans or strings, and recursing into
52// embedded structs, pointers to structs, and interfaces containing
53// pointers to structs.  Prepending the zero value of a property will always be a no-op.
54func PrependProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc) error {
55	return extendProperties(dst, src, filter, true)
56}
57
58// AppendMatchingProperties appends the values of properties in the property struct src to the
59// property structs in dst.  dst and src do not have to be the same type, but every property in src
60// must be found in at least one property in dst.  dst must be a slice of pointers to structs, and
61// src must be a pointer to a struct.
62//
63// The filter function can prevent individual properties from being appended by returning false, or
64// abort AppendProperties with an error by returning an error.  Passing nil for filter will append
65// all properties.
66//
67// An error returned by AppendMatchingProperties that applies to a specific property will be an
68// *ExtendPropertyError, and can have the property name and error extracted from it.
69//
70// The append operation is defined as appending strings, and slices of strings normally, OR-ing bool
71// values, replacing non-nil pointers to booleans or strings, and recursing into
72// embedded structs, pointers to structs, and interfaces containing
73// pointers to structs.  Appending the zero value of a property will always be a no-op.
74func AppendMatchingProperties(dst []interface{}, src interface{},
75	filter ExtendPropertyFilterFunc) error {
76	return extendMatchingProperties(dst, src, filter, false)
77}
78
79// PrependMatchingProperties prepends the values of properties in the property struct src to the
80// property structs in dst.  dst and src do not have to be the same type, but every property in src
81// must be found in at least one property in dst.  dst must be a slice of pointers to structs, and
82// src must be a pointer to a struct.
83//
84// The filter function can prevent individual properties from being prepended by returning false, or
85// abort PrependProperties with an error by returning an error.  Passing nil for filter will prepend
86// all properties.
87//
88// An error returned by PrependProperties that applies to a specific property will be an
89// *ExtendPropertyError, and can have the property name and error extracted from it.
90//
91// The prepend operation is defined as prepending strings, and slices of strings normally, OR-ing
92// bool values, replacing non-nil pointers to booleans or strings, and recursing into
93// embedded structs, pointers to structs, and interfaces containing
94// pointers to structs.  Prepending the zero value of a property will always be a no-op.
95func PrependMatchingProperties(dst []interface{}, src interface{},
96	filter ExtendPropertyFilterFunc) error {
97	return extendMatchingProperties(dst, src, filter, true)
98}
99
100type ExtendPropertyFilterFunc func(property string,
101	dstField, srcField reflect.StructField,
102	dstValue, srcValue interface{}) (bool, error)
103
104type ExtendPropertyError struct {
105	Err      error
106	Property string
107}
108
109func (e *ExtendPropertyError) Error() string {
110	return fmt.Sprintf("can't extend property %q: %s", e.Property, e.Err)
111}
112
113func extendPropertyErrorf(property string, format string, a ...interface{}) *ExtendPropertyError {
114	return &ExtendPropertyError{
115		Err:      fmt.Errorf(format, a...),
116		Property: property,
117	}
118}
119
120func extendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc,
121	prepend bool) error {
122
123	dstValue, err := getStruct(dst)
124	if err != nil {
125		return err
126	}
127	srcValue, err := getStruct(src)
128	if err != nil {
129		return err
130	}
131
132	if dstValue.Type() != srcValue.Type() {
133		return fmt.Errorf("expected matching types for dst and src, got %T and %T", dst, src)
134	}
135
136	dstValues := []reflect.Value{dstValue}
137
138	return extendPropertiesRecursive(dstValues, srcValue, "", filter, true, prepend)
139}
140
141func extendMatchingProperties(dst []interface{}, src interface{}, filter ExtendPropertyFilterFunc,
142	prepend bool) error {
143
144	dstValues := make([]reflect.Value, len(dst))
145	for i := range dst {
146		var err error
147		dstValues[i], err = getStruct(dst[i])
148		if err != nil {
149			return err
150		}
151	}
152
153	srcValue, err := getStruct(src)
154	if err != nil {
155		return err
156	}
157
158	return extendPropertiesRecursive(dstValues, srcValue, "", filter, false, prepend)
159}
160
161func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value,
162	prefix string, filter ExtendPropertyFilterFunc, sameTypes, prepend bool) error {
163
164	srcType := srcValue.Type()
165	for i := 0; i < srcValue.NumField(); i++ {
166		srcField := srcType.Field(i)
167		if srcField.PkgPath != "" {
168			// The field is not exported so just skip it.
169			continue
170		}
171		if HasTag(srcField, "blueprint", "mutated") {
172			continue
173		}
174
175		propertyName := prefix + PropertyNameForField(srcField.Name)
176		srcFieldValue := srcValue.Field(i)
177
178		found := false
179		for _, dstValue := range dstValues {
180			dstType := dstValue.Type()
181			var dstField reflect.StructField
182
183			if dstType == srcType {
184				dstField = dstType.Field(i)
185			} else {
186				var ok bool
187				dstField, ok = dstType.FieldByName(srcField.Name)
188				if !ok {
189					continue
190				}
191			}
192
193			found = true
194
195			dstFieldValue := dstValue.FieldByIndex(dstField.Index)
196
197			if srcFieldValue.Kind() != dstFieldValue.Kind() {
198				return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
199					dstFieldValue.Type(), srcFieldValue.Type())
200			}
201
202			switch srcFieldValue.Kind() {
203			case reflect.Interface:
204				if dstFieldValue.IsNil() != srcFieldValue.IsNil() {
205					return extendPropertyErrorf(propertyName, "nilitude mismatch")
206				}
207				if dstFieldValue.IsNil() {
208					continue
209				}
210
211				dstFieldValue = dstFieldValue.Elem()
212				srcFieldValue = srcFieldValue.Elem()
213
214				if srcFieldValue.Kind() != reflect.Ptr || dstFieldValue.Kind() != reflect.Ptr {
215					return extendPropertyErrorf(propertyName, "interface not a pointer")
216				}
217
218				fallthrough
219			case reflect.Ptr:
220				ptrKind := srcFieldValue.Type().Elem().Kind()
221				if ptrKind == reflect.Bool || ptrKind == reflect.String {
222					if srcFieldValue.Type() != dstFieldValue.Type() {
223						return extendPropertyErrorf(propertyName, "mismatched pointer types %s and %s",
224							dstFieldValue.Type(), srcFieldValue.Type())
225					}
226					break
227				} else if ptrKind != reflect.Struct {
228					return extendPropertyErrorf(propertyName, "pointer is a %s", ptrKind)
229				}
230
231				// Pointer to a struct
232				if dstFieldValue.IsNil() != srcFieldValue.IsNil() {
233					return extendPropertyErrorf(propertyName, "nilitude mismatch")
234				}
235				if dstFieldValue.IsNil() {
236					continue
237				}
238
239				dstFieldValue = dstFieldValue.Elem()
240				srcFieldValue = srcFieldValue.Elem()
241
242				fallthrough
243			case reflect.Struct:
244				if sameTypes && dstFieldValue.Type() != srcFieldValue.Type() {
245					return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
246						dstFieldValue.Type(), srcFieldValue.Type())
247				}
248
249				// Recursively extend the struct's fields.
250				err := extendPropertiesRecursive([]reflect.Value{dstFieldValue}, srcFieldValue,
251					propertyName+".", filter, sameTypes, prepend)
252				if err != nil {
253					return err
254				}
255				continue
256			case reflect.Bool, reflect.String, reflect.Slice:
257				if srcFieldValue.Type() != dstFieldValue.Type() {
258					return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
259						dstFieldValue.Type(), srcFieldValue.Type())
260				}
261			default:
262				return extendPropertyErrorf(propertyName, "unsupported kind %s",
263					srcFieldValue.Kind())
264			}
265
266			if filter != nil {
267				b, err := filter(propertyName, dstField, srcField,
268					dstFieldValue.Interface(), srcFieldValue.Interface())
269				if err != nil {
270					return &ExtendPropertyError{
271						Property: propertyName,
272						Err:      err,
273					}
274				}
275				if !b {
276					continue
277				}
278			}
279
280			switch srcFieldValue.Kind() {
281			case reflect.Bool:
282				// Boolean OR
283				dstFieldValue.Set(reflect.ValueOf(srcFieldValue.Bool() || dstFieldValue.Bool()))
284			case reflect.String:
285				// Append the extension string.
286				if prepend {
287					dstFieldValue.SetString(srcFieldValue.String() +
288						dstFieldValue.String())
289				} else {
290					dstFieldValue.SetString(dstFieldValue.String() +
291						srcFieldValue.String())
292				}
293			case reflect.Slice:
294				if srcFieldValue.IsNil() {
295					break
296				}
297
298				newSlice := reflect.MakeSlice(srcFieldValue.Type(), 0,
299					dstFieldValue.Len()+srcFieldValue.Len())
300				if prepend {
301					newSlice = reflect.AppendSlice(newSlice, srcFieldValue)
302					newSlice = reflect.AppendSlice(newSlice, dstFieldValue)
303				} else {
304					newSlice = reflect.AppendSlice(newSlice, dstFieldValue)
305					newSlice = reflect.AppendSlice(newSlice, srcFieldValue)
306				}
307				dstFieldValue.Set(newSlice)
308			case reflect.Ptr:
309				if srcFieldValue.IsNil() {
310					break
311				}
312
313				switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind {
314				case reflect.Bool:
315					if prepend {
316						if dstFieldValue.IsNil() {
317							dstFieldValue.Set(reflect.ValueOf(BoolPtr(srcFieldValue.Elem().Bool())))
318						}
319					} else {
320						// For append, replace the original value.
321						dstFieldValue.Set(reflect.ValueOf(BoolPtr(srcFieldValue.Elem().Bool())))
322					}
323				case reflect.String:
324					if prepend {
325						if dstFieldValue.IsNil() {
326							dstFieldValue.Set(reflect.ValueOf(StringPtr(srcFieldValue.Elem().String())))
327						}
328					} else {
329						// For append, replace the original value.
330						dstFieldValue.Set(reflect.ValueOf(StringPtr(srcFieldValue.Elem().String())))
331					}
332				default:
333					panic(fmt.Errorf("unexpected pointer kind %s", ptrKind))
334				}
335			}
336		}
337		if !found {
338			return extendPropertyErrorf(propertyName, "failed to find property to extend")
339		}
340	}
341
342	return nil
343}
344
345func getStruct(in interface{}) (reflect.Value, error) {
346	value := reflect.ValueOf(in)
347	if value.Kind() != reflect.Ptr {
348		return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in)
349	}
350	value = value.Elem()
351	if value.Kind() != reflect.Struct {
352		return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in)
353	}
354	return value, nil
355}
356