• 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	orderFunc 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.Int64, 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			order := Append
377			if orderFunc != nil {
378				var err error
379				order, err = orderFunc(propertyName, dstField, srcField,
380					dstFieldInterface, srcFieldInterface)
381				if err != nil {
382					return &ExtendPropertyError{
383						Property: propertyName,
384						Err:      err,
385					}
386				}
387			}
388
389			ExtendBasicType(dstFieldValue, srcFieldValue, order)
390		}
391
392		if len(recurse) > 0 {
393			err := extendPropertiesRecursive(recurse, srcFieldValue,
394				propertyName+".", filter, sameTypes, orderFunc)
395			if err != nil {
396				return err
397			}
398		} else if !found {
399			return extendPropertyErrorf(propertyName, "failed to find property to extend")
400		}
401	}
402
403	return nil
404}
405
406func ExtendBasicType(dstFieldValue, srcFieldValue reflect.Value, order Order) {
407	prepend := order == Prepend
408
409	switch srcFieldValue.Kind() {
410	case reflect.Bool:
411		// Boolean OR
412		dstFieldValue.Set(reflect.ValueOf(srcFieldValue.Bool() || dstFieldValue.Bool()))
413	case reflect.String:
414		if prepend {
415			dstFieldValue.SetString(srcFieldValue.String() +
416				dstFieldValue.String())
417		} else {
418			dstFieldValue.SetString(dstFieldValue.String() +
419				srcFieldValue.String())
420		}
421	case reflect.Slice:
422		if srcFieldValue.IsNil() {
423			break
424		}
425
426		newSlice := reflect.MakeSlice(srcFieldValue.Type(), 0,
427			dstFieldValue.Len()+srcFieldValue.Len())
428		if prepend {
429			newSlice = reflect.AppendSlice(newSlice, srcFieldValue)
430			newSlice = reflect.AppendSlice(newSlice, dstFieldValue)
431		} else {
432			newSlice = reflect.AppendSlice(newSlice, dstFieldValue)
433			newSlice = reflect.AppendSlice(newSlice, srcFieldValue)
434		}
435		dstFieldValue.Set(newSlice)
436	case reflect.Ptr:
437		if srcFieldValue.IsNil() {
438			break
439		}
440
441		switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind {
442		case reflect.Bool:
443			if prepend {
444				if dstFieldValue.IsNil() {
445					dstFieldValue.Set(reflect.ValueOf(BoolPtr(srcFieldValue.Elem().Bool())))
446				}
447			} else {
448				// For append, replace the original value.
449				dstFieldValue.Set(reflect.ValueOf(BoolPtr(srcFieldValue.Elem().Bool())))
450			}
451		case reflect.Int64:
452			if prepend {
453				if dstFieldValue.IsNil() {
454					// Int() returns Int64
455					dstFieldValue.Set(reflect.ValueOf(Int64Ptr(srcFieldValue.Elem().Int())))
456				}
457			} else {
458				// For append, replace the original value.
459				// Int() returns Int64
460				dstFieldValue.Set(reflect.ValueOf(Int64Ptr(srcFieldValue.Elem().Int())))
461			}
462		case reflect.String:
463			if prepend {
464				if dstFieldValue.IsNil() {
465					dstFieldValue.Set(reflect.ValueOf(StringPtr(srcFieldValue.Elem().String())))
466				}
467			} else {
468				// For append, replace the original value.
469				dstFieldValue.Set(reflect.ValueOf(StringPtr(srcFieldValue.Elem().String())))
470			}
471		default:
472			panic(fmt.Errorf("unexpected pointer kind %s", ptrKind))
473		}
474	}
475}
476
477type getStructEmptyError struct{}
478
479func (getStructEmptyError) Error() string { return "interface containing nil pointer" }
480
481func getOrCreateStruct(in interface{}) (reflect.Value, error) {
482	value, err := getStruct(in)
483	if _, ok := err.(getStructEmptyError); ok {
484		value := reflect.ValueOf(in)
485		newValue := reflect.New(value.Type().Elem())
486		value.Set(newValue)
487	}
488
489	return value, err
490}
491
492func getStruct(in interface{}) (reflect.Value, error) {
493	value := reflect.ValueOf(in)
494	if value.Kind() != reflect.Ptr {
495		return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in)
496	}
497	if value.Type().Elem().Kind() != reflect.Struct {
498		return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in)
499	}
500	if value.IsNil() {
501		return reflect.Value{}, getStructEmptyError{}
502	}
503	value = value.Elem()
504	return value, nil
505}
506