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