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. Properties 24// tagged `blueprint:"mutated"` are skipped. 25// 26// The filter function can prevent individual properties from being appended by returning false, or 27// abort AppendProperties with an error by returning an error. Passing nil for filter will append 28// all properties. 29// 30// An error returned by AppendProperties that applies to a specific property will be an 31// *ExtendPropertyError, and can have the property name and error extracted from it. 32// 33// The append operation is defined as appending strings and slices of strings normally, OR-ing bool 34// values, replacing non-nil pointers to booleans or strings, and recursing into 35// embedded structs, pointers to structs, and interfaces containing 36// pointers to structs. Appending the zero value of a property will always be a no-op. 37func AppendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc) error { 38 return extendProperties(dst, src, filter, OrderAppend) 39} 40 41// PrependProperties prepends the values of properties in the property struct src to the property 42// struct dst. dst and src must be the same type, and both must be pointers to structs. Properties 43// tagged `blueprint:"mutated"` are skipped. 44// 45// The filter function can prevent individual properties from being prepended by returning false, or 46// abort PrependProperties with an error by returning an error. Passing nil for filter will prepend 47// all properties. 48// 49// An error returned by PrependProperties that applies to a specific property will be an 50// *ExtendPropertyError, and can have the property name and error extracted from it. 51// 52// The prepend operation is defined as prepending strings, and slices of strings normally, OR-ing 53// bool values, replacing non-nil pointers to booleans or strings, and recursing into 54// embedded structs, pointers to structs, and interfaces containing 55// pointers to structs. Prepending the zero value of a property will always be a no-op. 56func PrependProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc) error { 57 return extendProperties(dst, src, filter, OrderPrepend) 58} 59 60// AppendMatchingProperties appends the values of properties in the property struct src to the 61// property structs in dst. dst and src do not have to be the same type, but every property in src 62// must be found in at least one property in dst. dst must be a slice of pointers to structs, and 63// src must be a pointer to a struct. Properties tagged `blueprint:"mutated"` are skipped. 64// 65// The filter function can prevent individual properties from being appended by returning false, or 66// abort AppendProperties with an error by returning an error. Passing nil for filter will append 67// all properties. 68// 69// An error returned by AppendMatchingProperties that applies to a specific property will be an 70// *ExtendPropertyError, and can have the property name and error extracted from it. 71// 72// The append operation is defined as appending strings, and slices of strings normally, OR-ing bool 73// values, replacing pointers to booleans or strings whether they are nil or not, and recursing into 74// embedded structs, pointers to structs, and interfaces containing 75// pointers to structs. Appending the zero value of a property will always be a no-op. 76func AppendMatchingProperties(dst []interface{}, src interface{}, 77 filter ExtendPropertyFilterFunc) error { 78 return extendMatchingProperties(dst, src, filter, OrderAppend) 79} 80 81// PrependMatchingProperties prepends the values of properties in the property struct src to the 82// property structs in dst. dst and src do not have to be the same type, but every property in src 83// must be found in at least one property in dst. dst must be a slice of pointers to structs, and 84// src must be a pointer to a struct. Properties tagged `blueprint:"mutated"` are skipped. 85// 86// The filter function can prevent individual properties from being prepended by returning false, or 87// abort PrependProperties with an error by returning an error. Passing nil for filter will prepend 88// all properties. 89// 90// An error returned by PrependProperties that applies to a specific property will be an 91// *ExtendPropertyError, and can have the property name and error extracted from it. 92// 93// The prepend operation is defined as prepending strings, and slices of strings normally, OR-ing 94// bool values, replacing nil pointers to booleans or strings, and recursing into 95// embedded structs, pointers to structs, and interfaces containing 96// pointers to structs. Prepending the zero value of a property will always be a no-op. 97func PrependMatchingProperties(dst []interface{}, src interface{}, 98 filter ExtendPropertyFilterFunc) error { 99 return extendMatchingProperties(dst, src, filter, OrderPrepend) 100} 101 102// ExtendProperties appends or prepends the values of properties in the property struct src to the 103// property struct dst. dst and src must be the same type, and both must be pointers to structs. 104// Properties tagged `blueprint:"mutated"` are skipped. 105// 106// The filter function can prevent individual properties from being appended or prepended by 107// returning false, or abort ExtendProperties with an error by returning an error. Passing nil for 108// filter will append or prepend all properties. 109// 110// The order function is called on each non-filtered property to determine if it should be appended 111// or prepended. 112// 113// An error returned by ExtendProperties that applies to a specific property will be an 114// *ExtendPropertyError, and can have the property name and error extracted from it. 115// 116// The append operation is defined as appending strings and slices of strings normally, OR-ing bool 117// values, replacing non-nil pointers to booleans or strings, and recursing into 118// embedded structs, pointers to structs, and interfaces containing 119// pointers to structs. Appending or prepending the zero value of a property will always be a 120// no-op. 121func ExtendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc, 122 order ExtendPropertyOrderFunc) error { 123 return extendProperties(dst, src, filter, order) 124} 125 126// ExtendMatchingProperties appends or prepends the values of properties in the property struct src 127// to the property structs in dst. dst and src do not have to be the same type, but every property 128// in src must be found in at least one property in dst. dst must be a slice of pointers to 129// structs, and src must be a pointer to a struct. Properties tagged `blueprint:"mutated"` are 130// skipped. 131// 132// The filter function can prevent individual properties from being appended or prepended by 133// returning false, or abort ExtendMatchingProperties with an error by returning an error. Passing 134// nil for filter will append or prepend all properties. 135// 136// The order function is called on each non-filtered property to determine if it should be appended 137// or prepended. 138// 139// An error returned by ExtendMatchingProperties that applies to a specific property will be an 140// *ExtendPropertyError, and can have the property name and error extracted from it. 141// 142// The append operation is defined as appending strings, and slices of strings normally, OR-ing bool 143// values, replacing non-nil pointers to booleans or strings, and recursing into 144// embedded structs, pointers to structs, and interfaces containing 145// pointers to structs. Appending or prepending the zero value of a property will always be a 146// no-op. 147func ExtendMatchingProperties(dst []interface{}, src interface{}, 148 filter ExtendPropertyFilterFunc, order ExtendPropertyOrderFunc) error { 149 return extendMatchingProperties(dst, src, filter, order) 150} 151 152type Order int 153 154const ( 155 Append Order = iota 156 Prepend 157 Replace 158) 159 160type ExtendPropertyFilterFunc func(property string, 161 dstField, srcField reflect.StructField, 162 dstValue, srcValue interface{}) (bool, error) 163 164type ExtendPropertyOrderFunc func(property string, 165 dstField, srcField reflect.StructField, 166 dstValue, srcValue interface{}) (Order, error) 167 168func OrderAppend(property string, 169 dstField, srcField reflect.StructField, 170 dstValue, srcValue interface{}) (Order, error) { 171 return Append, nil 172} 173 174func OrderPrepend(property string, 175 dstField, srcField reflect.StructField, 176 dstValue, srcValue interface{}) (Order, error) { 177 return Prepend, nil 178} 179 180func OrderReplace(property string, 181 dstField, srcField reflect.StructField, 182 dstValue, srcValue interface{}) (Order, error) { 183 return Replace, nil 184} 185 186type ExtendPropertyError struct { 187 Err error 188 Property string 189} 190 191func (e *ExtendPropertyError) Error() string { 192 return fmt.Sprintf("can't extend property %q: %s", e.Property, e.Err) 193} 194 195func extendPropertyErrorf(property string, format string, a ...interface{}) *ExtendPropertyError { 196 return &ExtendPropertyError{ 197 Err: fmt.Errorf(format, a...), 198 Property: property, 199 } 200} 201 202func extendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc, 203 order ExtendPropertyOrderFunc) error { 204 205 srcValue, err := getStruct(src) 206 if err != nil { 207 if _, ok := err.(getStructEmptyError); ok { 208 return nil 209 } 210 return err 211 } 212 213 dstValue, err := getOrCreateStruct(dst) 214 if err != nil { 215 return err 216 } 217 218 if dstValue.Type() != srcValue.Type() { 219 return fmt.Errorf("expected matching types for dst and src, got %T and %T", dst, src) 220 } 221 222 dstValues := []reflect.Value{dstValue} 223 224 return extendPropertiesRecursive(dstValues, srcValue, "", filter, true, order) 225} 226 227func extendMatchingProperties(dst []interface{}, src interface{}, filter ExtendPropertyFilterFunc, 228 order ExtendPropertyOrderFunc) error { 229 230 srcValue, err := getStruct(src) 231 if err != nil { 232 if _, ok := err.(getStructEmptyError); ok { 233 return nil 234 } 235 return err 236 } 237 238 dstValues := make([]reflect.Value, len(dst)) 239 for i := range dst { 240 var err error 241 dstValues[i], err = getOrCreateStruct(dst[i]) 242 if err != nil { 243 return err 244 } 245 } 246 247 return extendPropertiesRecursive(dstValues, srcValue, "", filter, false, order) 248} 249 250func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value, 251 prefix string, filter ExtendPropertyFilterFunc, sameTypes bool, 252 orderFunc ExtendPropertyOrderFunc) error { 253 254 dstValuesCopied := false 255 256 srcType := srcValue.Type() 257 for i, srcField := range typeFields(srcType) { 258 if ShouldSkipProperty(srcField) { 259 continue 260 } 261 262 propertyName := prefix + PropertyNameForField(srcField.Name) 263 srcFieldValue := srcValue.Field(i) 264 265 // Step into source interfaces 266 if srcFieldValue.Kind() == reflect.Interface { 267 if srcFieldValue.IsNil() { 268 continue 269 } 270 271 srcFieldValue = srcFieldValue.Elem() 272 273 if srcFieldValue.Kind() != reflect.Ptr { 274 return extendPropertyErrorf(propertyName, "interface not a pointer") 275 } 276 } 277 278 // Step into source pointers to structs 279 if isStructPtr(srcFieldValue.Type()) { 280 if srcFieldValue.IsNil() { 281 continue 282 } 283 284 srcFieldValue = srcFieldValue.Elem() 285 } 286 287 found := false 288 var recurse []reflect.Value 289 // Use an iteration loop so elements can be added to the end of dstValues inside the loop. 290 for j := 0; j < len(dstValues); j++ { 291 dstValue := dstValues[j] 292 dstType := dstValue.Type() 293 var dstField reflect.StructField 294 295 dstFields := typeFields(dstType) 296 if dstType == srcType { 297 dstField = dstFields[i] 298 } else { 299 var ok bool 300 for _, field := range dstFields { 301 if field.Name == srcField.Name { 302 dstField = field 303 ok = true 304 } else if IsEmbedded(field) { 305 embeddedDstValue := dstValue.FieldByIndex(field.Index) 306 if isStructPtr(embeddedDstValue.Type()) { 307 if embeddedDstValue.IsNil() { 308 newEmbeddedDstValue := reflect.New(embeddedDstValue.Type().Elem()) 309 embeddedDstValue.Set(newEmbeddedDstValue) 310 } 311 embeddedDstValue = embeddedDstValue.Elem() 312 } 313 if !isStruct(embeddedDstValue.Type()) { 314 return extendPropertyErrorf(propertyName, "%s is not a struct (%s)", 315 prefix+field.Name, embeddedDstValue.Type()) 316 } 317 // The destination struct contains an embedded struct, add it to the list 318 // of destinations to consider. Make a copy of dstValues if necessary 319 // to avoid modifying the backing array of an input parameter. 320 if !dstValuesCopied { 321 dstValues = append([]reflect.Value(nil), dstValues...) 322 dstValuesCopied = true 323 } 324 dstValues = append(dstValues, embeddedDstValue) 325 } 326 } 327 if !ok { 328 continue 329 } 330 } 331 332 found = true 333 334 dstFieldValue := dstValue.FieldByIndex(dstField.Index) 335 origDstFieldValue := dstFieldValue 336 337 // Step into destination interfaces 338 if dstFieldValue.Kind() == reflect.Interface { 339 if dstFieldValue.IsNil() { 340 return extendPropertyErrorf(propertyName, "nilitude mismatch") 341 } 342 343 dstFieldValue = dstFieldValue.Elem() 344 345 if dstFieldValue.Kind() != reflect.Ptr { 346 return extendPropertyErrorf(propertyName, "interface not a pointer") 347 } 348 } 349 350 // Step into destination pointers to structs 351 if isStructPtr(dstFieldValue.Type()) { 352 if dstFieldValue.IsNil() { 353 dstFieldValue = reflect.New(dstFieldValue.Type().Elem()) 354 origDstFieldValue.Set(dstFieldValue) 355 } 356 357 dstFieldValue = dstFieldValue.Elem() 358 } 359 360 switch srcFieldValue.Kind() { 361 case reflect.Struct: 362 if sameTypes && dstFieldValue.Type() != srcFieldValue.Type() { 363 return extendPropertyErrorf(propertyName, "mismatched types %s and %s", 364 dstFieldValue.Type(), srcFieldValue.Type()) 365 } 366 367 // Recursively extend the struct's fields. 368 recurse = append(recurse, dstFieldValue) 369 continue 370 case reflect.Bool, reflect.String, reflect.Slice, reflect.Map: 371 if srcFieldValue.Type() != dstFieldValue.Type() { 372 return extendPropertyErrorf(propertyName, "mismatched types %s and %s", 373 dstFieldValue.Type(), srcFieldValue.Type()) 374 } 375 case reflect.Ptr: 376 if srcFieldValue.Type() != dstFieldValue.Type() { 377 return extendPropertyErrorf(propertyName, "mismatched types %s and %s", 378 dstFieldValue.Type(), srcFieldValue.Type()) 379 } 380 switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind { 381 case reflect.Bool, reflect.Int64, reflect.String, reflect.Struct: 382 // Nothing 383 default: 384 return extendPropertyErrorf(propertyName, "pointer is a %s", ptrKind) 385 } 386 default: 387 return extendPropertyErrorf(propertyName, "unsupported kind %s", 388 srcFieldValue.Kind()) 389 } 390 391 dstFieldInterface := dstFieldValue.Interface() 392 srcFieldInterface := srcFieldValue.Interface() 393 394 if filter != nil { 395 b, err := filter(propertyName, dstField, srcField, 396 dstFieldInterface, srcFieldInterface) 397 if err != nil { 398 return &ExtendPropertyError{ 399 Property: propertyName, 400 Err: err, 401 } 402 } 403 if !b { 404 continue 405 } 406 } 407 408 order := Append 409 if orderFunc != nil { 410 var err error 411 order, err = orderFunc(propertyName, dstField, srcField, 412 dstFieldInterface, srcFieldInterface) 413 if err != nil { 414 return &ExtendPropertyError{ 415 Property: propertyName, 416 Err: err, 417 } 418 } 419 } 420 421 ExtendBasicType(dstFieldValue, srcFieldValue, order) 422 } 423 424 if len(recurse) > 0 { 425 err := extendPropertiesRecursive(recurse, srcFieldValue, 426 propertyName+".", filter, sameTypes, orderFunc) 427 if err != nil { 428 return err 429 } 430 } else if !found { 431 return extendPropertyErrorf(propertyName, "failed to find property to extend") 432 } 433 } 434 435 return nil 436} 437 438func ExtendBasicType(dstFieldValue, srcFieldValue reflect.Value, order Order) { 439 prepend := order == Prepend 440 441 switch srcFieldValue.Kind() { 442 case reflect.Bool: 443 // Boolean OR 444 dstFieldValue.Set(reflect.ValueOf(srcFieldValue.Bool() || dstFieldValue.Bool())) 445 case reflect.String: 446 if prepend { 447 dstFieldValue.SetString(srcFieldValue.String() + 448 dstFieldValue.String()) 449 } else { 450 dstFieldValue.SetString(dstFieldValue.String() + 451 srcFieldValue.String()) 452 } 453 case reflect.Slice: 454 if srcFieldValue.IsNil() { 455 break 456 } 457 458 newSlice := reflect.MakeSlice(srcFieldValue.Type(), 0, 459 dstFieldValue.Len()+srcFieldValue.Len()) 460 if prepend { 461 newSlice = reflect.AppendSlice(newSlice, srcFieldValue) 462 newSlice = reflect.AppendSlice(newSlice, dstFieldValue) 463 } else if order == Append { 464 newSlice = reflect.AppendSlice(newSlice, dstFieldValue) 465 newSlice = reflect.AppendSlice(newSlice, srcFieldValue) 466 } else { 467 // replace 468 newSlice = reflect.AppendSlice(newSlice, srcFieldValue) 469 } 470 dstFieldValue.Set(newSlice) 471 case reflect.Map: 472 if srcFieldValue.IsNil() { 473 break 474 } 475 var mapValue reflect.Value 476 // for append/prepend, maintain keys from original value 477 // for replace, replace entire map 478 if order == Replace || dstFieldValue.IsNil() { 479 mapValue = srcFieldValue 480 } else { 481 mapValue = dstFieldValue 482 483 iter := srcFieldValue.MapRange() 484 for iter.Next() { 485 dstValue := dstFieldValue.MapIndex(iter.Key()) 486 if prepend { 487 // if the key exists in the map, keep the original value. 488 if !dstValue.IsValid() { 489 // otherwise, add the new value 490 mapValue.SetMapIndex(iter.Key(), iter.Value()) 491 } 492 } else { 493 // For append, replace the original value. 494 mapValue.SetMapIndex(iter.Key(), iter.Value()) 495 } 496 } 497 } 498 dstFieldValue.Set(mapValue) 499 case reflect.Ptr: 500 if srcFieldValue.IsNil() { 501 break 502 } 503 504 switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind { 505 case reflect.Bool: 506 if prepend { 507 if dstFieldValue.IsNil() { 508 dstFieldValue.Set(reflect.ValueOf(BoolPtr(srcFieldValue.Elem().Bool()))) 509 } 510 } else { 511 // For append, replace the original value. 512 dstFieldValue.Set(reflect.ValueOf(BoolPtr(srcFieldValue.Elem().Bool()))) 513 } 514 case reflect.Int64: 515 if prepend { 516 if dstFieldValue.IsNil() { 517 // Int() returns Int64 518 dstFieldValue.Set(reflect.ValueOf(Int64Ptr(srcFieldValue.Elem().Int()))) 519 } 520 } else { 521 // For append, replace the original value. 522 // Int() returns Int64 523 dstFieldValue.Set(reflect.ValueOf(Int64Ptr(srcFieldValue.Elem().Int()))) 524 } 525 case reflect.String: 526 if prepend { 527 if dstFieldValue.IsNil() { 528 dstFieldValue.Set(reflect.ValueOf(StringPtr(srcFieldValue.Elem().String()))) 529 } 530 } else { 531 // For append, replace the original value. 532 dstFieldValue.Set(reflect.ValueOf(StringPtr(srcFieldValue.Elem().String()))) 533 } 534 default: 535 panic(fmt.Errorf("unexpected pointer kind %s", ptrKind)) 536 } 537 } 538} 539 540// ShouldSkipProperty indicates whether a property should be skipped in processing. 541func ShouldSkipProperty(structField reflect.StructField) bool { 542 return structField.PkgPath != "" || // The field is not exported so just skip it. 543 HasTag(structField, "blueprint", "mutated") // The field is not settable in a blueprint file 544} 545 546// IsEmbedded indicates whether a property is embedded. This is useful for determining nesting name 547// as the name of the embedded field is _not_ used in blueprint files. 548func IsEmbedded(structField reflect.StructField) bool { 549 return structField.Name == "BlueprintEmbed" || structField.Anonymous 550} 551 552type getStructEmptyError struct{} 553 554func (getStructEmptyError) Error() string { return "interface containing nil pointer" } 555 556func getOrCreateStruct(in interface{}) (reflect.Value, error) { 557 value, err := getStruct(in) 558 if _, ok := err.(getStructEmptyError); ok { 559 value := reflect.ValueOf(in) 560 newValue := reflect.New(value.Type().Elem()) 561 value.Set(newValue) 562 } 563 564 return value, err 565} 566 567func getStruct(in interface{}) (reflect.Value, error) { 568 value := reflect.ValueOf(in) 569 if !isStructPtr(value.Type()) { 570 return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %s", value.Type()) 571 } 572 if value.IsNil() { 573 return reflect.Value{}, getStructEmptyError{} 574 } 575 value = value.Elem() 576 return value, nil 577} 578