• 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 pointers to booleans or strings whether they are nil or not, 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 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	Replace
154)
155
156type ExtendPropertyFilterFunc func(property string,
157	dstField, srcField reflect.StructField,
158	dstValue, srcValue interface{}) (bool, error)
159
160type ExtendPropertyOrderFunc func(property string,
161	dstField, srcField reflect.StructField,
162	dstValue, srcValue interface{}) (Order, error)
163
164func OrderAppend(property string,
165	dstField, srcField reflect.StructField,
166	dstValue, srcValue interface{}) (Order, error) {
167	return Append, nil
168}
169
170func OrderPrepend(property string,
171	dstField, srcField reflect.StructField,
172	dstValue, srcValue interface{}) (Order, error) {
173	return Prepend, nil
174}
175
176func OrderReplace(property string,
177	dstField, srcField reflect.StructField,
178	dstValue, srcValue interface{}) (Order, error) {
179	return Replace, nil
180}
181
182type ExtendPropertyError struct {
183	Err      error
184	Property string
185}
186
187func (e *ExtendPropertyError) Error() string {
188	return fmt.Sprintf("can't extend property %q: %s", e.Property, e.Err)
189}
190
191func extendPropertyErrorf(property string, format string, a ...interface{}) *ExtendPropertyError {
192	return &ExtendPropertyError{
193		Err:      fmt.Errorf(format, a...),
194		Property: property,
195	}
196}
197
198func extendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc,
199	order ExtendPropertyOrderFunc) error {
200
201	srcValue, err := getStruct(src)
202	if err != nil {
203		if _, ok := err.(getStructEmptyError); ok {
204			return nil
205		}
206		return err
207	}
208
209	dstValue, err := getOrCreateStruct(dst)
210	if err != nil {
211		return err
212	}
213
214	if dstValue.Type() != srcValue.Type() {
215		return fmt.Errorf("expected matching types for dst and src, got %T and %T", dst, src)
216	}
217
218	dstValues := []reflect.Value{dstValue}
219
220	return extendPropertiesRecursive(dstValues, srcValue, "", filter, true, order)
221}
222
223func extendMatchingProperties(dst []interface{}, src interface{}, filter ExtendPropertyFilterFunc,
224	order ExtendPropertyOrderFunc) error {
225
226	srcValue, err := getStruct(src)
227	if err != nil {
228		if _, ok := err.(getStructEmptyError); ok {
229			return nil
230		}
231		return err
232	}
233
234	dstValues := make([]reflect.Value, len(dst))
235	for i := range dst {
236		var err error
237		dstValues[i], err = getOrCreateStruct(dst[i])
238		if err != nil {
239			return err
240		}
241	}
242
243	return extendPropertiesRecursive(dstValues, srcValue, "", filter, false, order)
244}
245
246func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value,
247	prefix string, filter ExtendPropertyFilterFunc, sameTypes bool,
248	orderFunc ExtendPropertyOrderFunc) error {
249
250	srcType := srcValue.Type()
251	for i, srcField := range typeFields(srcType) {
252		if srcField.PkgPath != "" {
253			// The field is not exported so just skip it.
254			continue
255		}
256		if HasTag(srcField, "blueprint", "mutated") {
257			continue
258		}
259
260		propertyName := prefix + PropertyNameForField(srcField.Name)
261		srcFieldValue := srcValue.Field(i)
262
263		// Step into source interfaces
264		if srcFieldValue.Kind() == reflect.Interface {
265			if srcFieldValue.IsNil() {
266				continue
267			}
268
269			srcFieldValue = srcFieldValue.Elem()
270
271			if srcFieldValue.Kind() != reflect.Ptr {
272				return extendPropertyErrorf(propertyName, "interface not a pointer")
273			}
274		}
275
276		// Step into source pointers to structs
277		if isStructPtr(srcFieldValue.Type()) {
278			if srcFieldValue.IsNil() {
279				continue
280			}
281
282			srcFieldValue = srcFieldValue.Elem()
283		}
284
285		found := false
286		var recurse []reflect.Value
287		for _, dstValue := range dstValues {
288			dstType := dstValue.Type()
289			var dstField reflect.StructField
290
291			dstFields := typeFields(dstType)
292			if dstType == srcType {
293				dstField = dstFields[i]
294			} else {
295				var ok bool
296				for _, field := range dstFields {
297					if field.Name == srcField.Name {
298						dstField = field
299						ok = true
300					}
301				}
302				if !ok {
303					continue
304				}
305			}
306
307			found = true
308
309			dstFieldValue := dstValue.FieldByIndex(dstField.Index)
310			origDstFieldValue := dstFieldValue
311
312			// Step into destination interfaces
313			if dstFieldValue.Kind() == reflect.Interface {
314				if dstFieldValue.IsNil() {
315					return extendPropertyErrorf(propertyName, "nilitude mismatch")
316				}
317
318				dstFieldValue = dstFieldValue.Elem()
319
320				if dstFieldValue.Kind() != reflect.Ptr {
321					return extendPropertyErrorf(propertyName, "interface not a pointer")
322				}
323			}
324
325			// Step into destination pointers to structs
326			if isStructPtr(dstFieldValue.Type()) {
327				if dstFieldValue.IsNil() {
328					dstFieldValue = reflect.New(dstFieldValue.Type().Elem())
329					origDstFieldValue.Set(dstFieldValue)
330				}
331
332				dstFieldValue = dstFieldValue.Elem()
333			}
334
335			switch srcFieldValue.Kind() {
336			case reflect.Struct:
337				if sameTypes && dstFieldValue.Type() != srcFieldValue.Type() {
338					return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
339						dstFieldValue.Type(), srcFieldValue.Type())
340				}
341
342				// Recursively extend the struct's fields.
343				recurse = append(recurse, dstFieldValue)
344				continue
345			case reflect.Bool, reflect.String, reflect.Slice:
346				if srcFieldValue.Type() != dstFieldValue.Type() {
347					return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
348						dstFieldValue.Type(), srcFieldValue.Type())
349				}
350			case reflect.Ptr:
351				if srcFieldValue.Type() != dstFieldValue.Type() {
352					return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
353						dstFieldValue.Type(), srcFieldValue.Type())
354				}
355				switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind {
356				case reflect.Bool, reflect.Int64, reflect.String, reflect.Struct:
357				// Nothing
358				default:
359					return extendPropertyErrorf(propertyName, "pointer is a %s", ptrKind)
360				}
361			default:
362				return extendPropertyErrorf(propertyName, "unsupported kind %s",
363					srcFieldValue.Kind())
364			}
365
366			dstFieldInterface := dstFieldValue.Interface()
367			srcFieldInterface := srcFieldValue.Interface()
368
369			if filter != nil {
370				b, err := filter(propertyName, dstField, srcField,
371					dstFieldInterface, srcFieldInterface)
372				if err != nil {
373					return &ExtendPropertyError{
374						Property: propertyName,
375						Err:      err,
376					}
377				}
378				if !b {
379					continue
380				}
381			}
382
383			order := Append
384			if orderFunc != nil {
385				var err error
386				order, err = orderFunc(propertyName, dstField, srcField,
387					dstFieldInterface, srcFieldInterface)
388				if err != nil {
389					return &ExtendPropertyError{
390						Property: propertyName,
391						Err:      err,
392					}
393				}
394			}
395
396			ExtendBasicType(dstFieldValue, srcFieldValue, order)
397		}
398
399		if len(recurse) > 0 {
400			err := extendPropertiesRecursive(recurse, srcFieldValue,
401				propertyName+".", filter, sameTypes, orderFunc)
402			if err != nil {
403				return err
404			}
405		} else if !found {
406			return extendPropertyErrorf(propertyName, "failed to find property to extend")
407		}
408	}
409
410	return nil
411}
412
413func ExtendBasicType(dstFieldValue, srcFieldValue reflect.Value, order Order) {
414	prepend := order == Prepend
415
416	switch srcFieldValue.Kind() {
417	case reflect.Bool:
418		// Boolean OR
419		dstFieldValue.Set(reflect.ValueOf(srcFieldValue.Bool() || dstFieldValue.Bool()))
420	case reflect.String:
421		if prepend {
422			dstFieldValue.SetString(srcFieldValue.String() +
423				dstFieldValue.String())
424		} else {
425			dstFieldValue.SetString(dstFieldValue.String() +
426				srcFieldValue.String())
427		}
428	case reflect.Slice:
429		if srcFieldValue.IsNil() {
430			break
431		}
432
433		newSlice := reflect.MakeSlice(srcFieldValue.Type(), 0,
434			dstFieldValue.Len()+srcFieldValue.Len())
435		if prepend {
436			newSlice = reflect.AppendSlice(newSlice, srcFieldValue)
437			newSlice = reflect.AppendSlice(newSlice, dstFieldValue)
438		} else if order == Append {
439			newSlice = reflect.AppendSlice(newSlice, dstFieldValue)
440			newSlice = reflect.AppendSlice(newSlice, srcFieldValue)
441		} else {
442			// replace
443			newSlice = reflect.AppendSlice(newSlice, srcFieldValue)
444		}
445		dstFieldValue.Set(newSlice)
446	case reflect.Ptr:
447		if srcFieldValue.IsNil() {
448			break
449		}
450
451		switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind {
452		case reflect.Bool:
453			if prepend {
454				if dstFieldValue.IsNil() {
455					dstFieldValue.Set(reflect.ValueOf(BoolPtr(srcFieldValue.Elem().Bool())))
456				}
457			} else {
458				// For append, replace the original value.
459				dstFieldValue.Set(reflect.ValueOf(BoolPtr(srcFieldValue.Elem().Bool())))
460			}
461		case reflect.Int64:
462			if prepend {
463				if dstFieldValue.IsNil() {
464					// Int() returns Int64
465					dstFieldValue.Set(reflect.ValueOf(Int64Ptr(srcFieldValue.Elem().Int())))
466				}
467			} else {
468				// For append, replace the original value.
469				// Int() returns Int64
470				dstFieldValue.Set(reflect.ValueOf(Int64Ptr(srcFieldValue.Elem().Int())))
471			}
472		case reflect.String:
473			if prepend {
474				if dstFieldValue.IsNil() {
475					dstFieldValue.Set(reflect.ValueOf(StringPtr(srcFieldValue.Elem().String())))
476				}
477			} else {
478				// For append, replace the original value.
479				dstFieldValue.Set(reflect.ValueOf(StringPtr(srcFieldValue.Elem().String())))
480			}
481		default:
482			panic(fmt.Errorf("unexpected pointer kind %s", ptrKind))
483		}
484	}
485}
486
487type getStructEmptyError struct{}
488
489func (getStructEmptyError) Error() string { return "interface containing nil pointer" }
490
491func getOrCreateStruct(in interface{}) (reflect.Value, error) {
492	value, err := getStruct(in)
493	if _, ok := err.(getStructEmptyError); ok {
494		value := reflect.ValueOf(in)
495		newValue := reflect.New(value.Type().Elem())
496		value.Set(newValue)
497	}
498
499	return value, err
500}
501
502func getStruct(in interface{}) (reflect.Value, error) {
503	value := reflect.ValueOf(in)
504	if !isStructPtr(value.Type()) {
505		return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %s", value.Type())
506	}
507	if value.IsNil() {
508		return reflect.Value{}, getStructEmptyError{}
509	}
510	value = value.Elem()
511	return value, nil
512}
513