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