• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, orderAppend)
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, orderPrepend)
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, orderAppend)
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, orderPrepend)
98}
99
100// ExtendProperties appends or prepends the values of properties in the property struct src to the
101// property struct dst. dst and src must be the same type, and both must be pointers to structs.
102//
103// The filter function can prevent individual properties from being appended or prepended by
104// returning false, or abort ExtendProperties with an error by returning an error.  Passing nil for
105// filter will append or prepend all properties.
106//
107// The order function is called on each non-filtered property to determine if it should be appended
108// or prepended.
109//
110// An error returned by ExtendProperties that applies to a specific property will be an
111// *ExtendPropertyError, and can have the property name and error extracted from it.
112//
113// The append operation is defined as appending strings and slices of strings normally, OR-ing bool
114// values, replacing non-nil pointers to booleans or strings, and recursing into
115// embedded structs, pointers to structs, and interfaces containing
116// pointers to structs.  Appending or prepending the zero value of a property will always be a
117// no-op.
118func ExtendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc,
119	order ExtendPropertyOrderFunc) error {
120	return extendProperties(dst, src, filter, order)
121}
122
123// ExtendMatchingProperties appends or prepends the values of properties in the property struct src
124// to the property structs in dst.  dst and src do not have to be the same type, but every property
125// in src must be found in at least one property in dst.  dst must be a slice of pointers to
126// structs, and src must be a pointer to a struct.
127//
128// The filter function can prevent individual properties from being appended or prepended by
129// returning false, or abort ExtendMatchingProperties with an error by returning an error.  Passing
130// nil for filter will append or prepend all properties.
131//
132// The order function is called on each non-filtered property to determine if it should be appended
133// or prepended.
134//
135// An error returned by ExtendMatchingProperties that applies to a specific property will be an
136// *ExtendPropertyError, and can have the property name and error extracted from it.
137//
138// The append operation is defined as appending strings, and slices of strings normally, OR-ing bool
139// values, replacing non-nil pointers to booleans or strings, and recursing into
140// embedded structs, pointers to structs, and interfaces containing
141// pointers to structs.  Appending or prepending the zero value of a property will always be a
142// no-op.
143func ExtendMatchingProperties(dst []interface{}, src interface{},
144	filter ExtendPropertyFilterFunc, order ExtendPropertyOrderFunc) error {
145	return extendMatchingProperties(dst, src, filter, order)
146}
147
148type Order int
149
150const (
151	Append Order = iota
152	Prepend
153)
154
155type ExtendPropertyFilterFunc func(property string,
156	dstField, srcField reflect.StructField,
157	dstValue, srcValue interface{}) (bool, error)
158
159type ExtendPropertyOrderFunc func(property string,
160	dstField, srcField reflect.StructField,
161	dstValue, srcValue interface{}) (Order, error)
162
163func orderAppend(property string,
164	dstField, srcField reflect.StructField,
165	dstValue, srcValue interface{}) (Order, error) {
166	return Append, nil
167}
168
169func orderPrepend(property string,
170	dstField, srcField reflect.StructField,
171	dstValue, srcValue interface{}) (Order, error) {
172	return Prepend, nil
173}
174
175type ExtendPropertyError struct {
176	Err      error
177	Property string
178}
179
180func (e *ExtendPropertyError) Error() string {
181	return fmt.Sprintf("can't extend property %q: %s", e.Property, e.Err)
182}
183
184func extendPropertyErrorf(property string, format string, a ...interface{}) *ExtendPropertyError {
185	return &ExtendPropertyError{
186		Err:      fmt.Errorf(format, a...),
187		Property: property,
188	}
189}
190
191func extendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc,
192	order ExtendPropertyOrderFunc) error {
193
194	srcValue, err := getStruct(src)
195	if err != nil {
196		if _, ok := err.(getStructEmptyError); ok {
197			return nil
198		}
199		return err
200	}
201
202	dstValue, err := getOrCreateStruct(dst)
203	if err != nil {
204		return err
205	}
206
207	if dstValue.Type() != srcValue.Type() {
208		return fmt.Errorf("expected matching types for dst and src, got %T and %T", dst, src)
209	}
210
211	dstValues := []reflect.Value{dstValue}
212
213	return extendPropertiesRecursive(dstValues, srcValue, "", filter, true, order)
214}
215
216func extendMatchingProperties(dst []interface{}, src interface{}, filter ExtendPropertyFilterFunc,
217	order ExtendPropertyOrderFunc) error {
218
219	srcValue, err := getStruct(src)
220	if err != nil {
221		if _, ok := err.(getStructEmptyError); ok {
222			return nil
223		}
224		return err
225	}
226
227	dstValues := make([]reflect.Value, len(dst))
228	for i := range dst {
229		var err error
230		dstValues[i], err = getOrCreateStruct(dst[i])
231		if err != nil {
232			return err
233		}
234	}
235
236	return extendPropertiesRecursive(dstValues, srcValue, "", filter, false, order)
237}
238
239func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value,
240	prefix string, filter ExtendPropertyFilterFunc, sameTypes bool,
241	order ExtendPropertyOrderFunc) error {
242
243	srcType := srcValue.Type()
244	for i, srcField := range typeFields(srcType) {
245		if srcField.PkgPath != "" {
246			// The field is not exported so just skip it.
247			continue
248		}
249		if HasTag(srcField, "blueprint", "mutated") {
250			continue
251		}
252
253		propertyName := prefix + PropertyNameForField(srcField.Name)
254		srcFieldValue := srcValue.Field(i)
255
256		// Step into source interfaces
257		if srcFieldValue.Kind() == reflect.Interface {
258			if srcFieldValue.IsNil() {
259				continue
260			}
261
262			srcFieldValue = srcFieldValue.Elem()
263
264			if srcFieldValue.Kind() != reflect.Ptr {
265				return extendPropertyErrorf(propertyName, "interface not a pointer")
266			}
267		}
268
269		// Step into source pointers to structs
270		if srcFieldValue.Kind() == reflect.Ptr && srcFieldValue.Type().Elem().Kind() == reflect.Struct {
271			if srcFieldValue.IsNil() {
272				continue
273			}
274
275			srcFieldValue = srcFieldValue.Elem()
276		}
277
278		found := false
279		var recurse []reflect.Value
280		for _, dstValue := range dstValues {
281			dstType := dstValue.Type()
282			var dstField reflect.StructField
283
284			dstFields := typeFields(dstType)
285			if dstType == srcType {
286				dstField = dstFields[i]
287			} else {
288				var ok bool
289				for _, field := range dstFields {
290					if field.Name == srcField.Name {
291						dstField = field
292						ok = true
293					}
294				}
295				if !ok {
296					continue
297				}
298			}
299
300			found = true
301
302			dstFieldValue := dstValue.FieldByIndex(dstField.Index)
303			origDstFieldValue := dstFieldValue
304
305			// Step into destination interfaces
306			if dstFieldValue.Kind() == reflect.Interface {
307				if dstFieldValue.IsNil() {
308					return extendPropertyErrorf(propertyName, "nilitude mismatch")
309				}
310
311				dstFieldValue = dstFieldValue.Elem()
312
313				if dstFieldValue.Kind() != reflect.Ptr {
314					return extendPropertyErrorf(propertyName, "interface not a pointer")
315				}
316			}
317
318			// Step into destination pointers to structs
319			if dstFieldValue.Kind() == reflect.Ptr && dstFieldValue.Type().Elem().Kind() == reflect.Struct {
320				if dstFieldValue.IsNil() {
321					dstFieldValue = reflect.New(dstFieldValue.Type().Elem())
322					origDstFieldValue.Set(dstFieldValue)
323				}
324
325				dstFieldValue = dstFieldValue.Elem()
326			}
327
328			switch srcFieldValue.Kind() {
329			case reflect.Struct:
330				if sameTypes && dstFieldValue.Type() != srcFieldValue.Type() {
331					return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
332						dstFieldValue.Type(), srcFieldValue.Type())
333				}
334
335				// Recursively extend the struct's fields.
336				recurse = append(recurse, dstFieldValue)
337				continue
338			case reflect.Bool, reflect.String, reflect.Slice:
339				if srcFieldValue.Type() != dstFieldValue.Type() {
340					return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
341						dstFieldValue.Type(), srcFieldValue.Type())
342				}
343			case reflect.Ptr:
344				if srcFieldValue.Type() != dstFieldValue.Type() {
345					return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
346						dstFieldValue.Type(), srcFieldValue.Type())
347				}
348				switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind {
349				case reflect.Bool, reflect.String, reflect.Struct:
350				// Nothing
351				default:
352					return extendPropertyErrorf(propertyName, "pointer is a %s", ptrKind)
353				}
354			default:
355				return extendPropertyErrorf(propertyName, "unsupported kind %s",
356					srcFieldValue.Kind())
357			}
358
359			dstFieldInterface := dstFieldValue.Interface()
360			srcFieldInterface := srcFieldValue.Interface()
361
362			if filter != nil {
363				b, err := filter(propertyName, dstField, srcField,
364					dstFieldInterface, srcFieldInterface)
365				if err != nil {
366					return &ExtendPropertyError{
367						Property: propertyName,
368						Err:      err,
369					}
370				}
371				if !b {
372					continue
373				}
374			}
375
376			prepend := false
377			if order != nil {
378				b, err := order(propertyName, dstField, srcField,
379					dstFieldInterface, srcFieldInterface)
380				if err != nil {
381					return &ExtendPropertyError{
382						Property: propertyName,
383						Err:      err,
384					}
385				}
386				prepend = b == Prepend
387			}
388
389			switch srcFieldValue.Kind() {
390			case reflect.Bool:
391				// Boolean OR
392				dstFieldValue.Set(reflect.ValueOf(srcFieldValue.Bool() || dstFieldValue.Bool()))
393			case reflect.String:
394				// Append the extension string.
395				if prepend {
396					dstFieldValue.SetString(srcFieldValue.String() +
397						dstFieldValue.String())
398				} else {
399					dstFieldValue.SetString(dstFieldValue.String() +
400						srcFieldValue.String())
401				}
402			case reflect.Slice:
403				if srcFieldValue.IsNil() {
404					break
405				}
406
407				newSlice := reflect.MakeSlice(srcFieldValue.Type(), 0,
408					dstFieldValue.Len()+srcFieldValue.Len())
409				if prepend {
410					newSlice = reflect.AppendSlice(newSlice, srcFieldValue)
411					newSlice = reflect.AppendSlice(newSlice, dstFieldValue)
412				} else {
413					newSlice = reflect.AppendSlice(newSlice, dstFieldValue)
414					newSlice = reflect.AppendSlice(newSlice, srcFieldValue)
415				}
416				dstFieldValue.Set(newSlice)
417			case reflect.Ptr:
418				if srcFieldValue.IsNil() {
419					break
420				}
421
422				switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind {
423				case reflect.Bool:
424					if prepend {
425						if dstFieldValue.IsNil() {
426							dstFieldValue.Set(reflect.ValueOf(BoolPtr(srcFieldValue.Elem().Bool())))
427						}
428					} else {
429						// For append, replace the original value.
430						dstFieldValue.Set(reflect.ValueOf(BoolPtr(srcFieldValue.Elem().Bool())))
431					}
432				case reflect.String:
433					if prepend {
434						if dstFieldValue.IsNil() {
435							dstFieldValue.Set(reflect.ValueOf(StringPtr(srcFieldValue.Elem().String())))
436						}
437					} else {
438						// For append, replace the original value.
439						dstFieldValue.Set(reflect.ValueOf(StringPtr(srcFieldValue.Elem().String())))
440					}
441				default:
442					panic(fmt.Errorf("unexpected pointer kind %s", ptrKind))
443				}
444			}
445		}
446		if len(recurse) > 0 {
447			err := extendPropertiesRecursive(recurse, srcFieldValue,
448				propertyName+".", filter, sameTypes, order)
449			if err != nil {
450				return err
451			}
452		} else if !found {
453			return extendPropertyErrorf(propertyName, "failed to find property to extend")
454		}
455	}
456
457	return nil
458}
459
460type getStructEmptyError struct{}
461
462func (getStructEmptyError) Error() string { return "interface containing nil pointer" }
463
464func getOrCreateStruct(in interface{}) (reflect.Value, error) {
465	value, err := getStruct(in)
466	if _, ok := err.(getStructEmptyError); ok {
467		value := reflect.ValueOf(in)
468		newValue := reflect.New(value.Type().Elem())
469		value.Set(newValue)
470	}
471
472	return value, err
473}
474
475func getStruct(in interface{}) (reflect.Value, error) {
476	value := reflect.ValueOf(in)
477	if value.Kind() != reflect.Ptr {
478		return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in)
479	}
480	if value.Type().Elem().Kind() != reflect.Struct {
481		return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in)
482	}
483	if value.IsNil() {
484		return reflect.Value{}, getStructEmptyError{}
485	}
486	value = value.Elem()
487	return value, nil
488}
489