Home | History | Annotate | Download | only in proptools
      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 
     15 package proptools
     16 
     17 import (
     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.
     36 func 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.
     54 func 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.
     74 func 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.
     95 func PrependMatchingProperties(dst []interface{}, src interface{},
     96 	filter ExtendPropertyFilterFunc) error {
     97 	return extendMatchingProperties(dst, src, filter, true)
     98 }
     99 
    100 type ExtendPropertyFilterFunc func(property string,
    101 	dstField, srcField reflect.StructField,
    102 	dstValue, srcValue interface{}) (bool, error)
    103 
    104 type ExtendPropertyError struct {
    105 	Err      error
    106 	Property string
    107 }
    108 
    109 func (e *ExtendPropertyError) Error() string {
    110 	return fmt.Sprintf("can't extend property %q: %s", e.Property, e.Err)
    111 }
    112 
    113 func extendPropertyErrorf(property string, format string, a ...interface{}) *ExtendPropertyError {
    114 	return &ExtendPropertyError{
    115 		Err:      fmt.Errorf(format, a...),
    116 		Property: property,
    117 	}
    118 }
    119 
    120 func 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 
    141 func 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 
    161 func 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 
    345 func 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