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