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