1// Copyright 2014 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 "sync" 21) 22 23// CloneProperties takes a reflect.Value of a pointer to a struct and returns a reflect.Value 24// of a pointer to a new struct that copies of the values for its fields. It recursively clones 25// struct pointers and interfaces that contain struct pointers. 26func CloneProperties(structValue reflect.Value) reflect.Value { 27 if !isStructPtr(structValue.Type()) { 28 panic(fmt.Errorf("CloneProperties expected *struct, got %s", structValue.Type())) 29 } 30 result := reflect.New(structValue.Type().Elem()) 31 copyProperties(result.Elem(), structValue.Elem()) 32 return result 33} 34 35// CopyProperties takes destination and source reflect.Values of a pointer to structs and returns 36// copies each field from the source into the destination. It recursively copies struct pointers 37// and interfaces that contain struct pointers. 38func CopyProperties(dstValue, srcValue reflect.Value) { 39 if !isStructPtr(dstValue.Type()) { 40 panic(fmt.Errorf("CopyProperties expected dstValue *struct, got %s", dstValue.Type())) 41 } 42 if !isStructPtr(srcValue.Type()) { 43 panic(fmt.Errorf("CopyProperties expected srcValue *struct, got %s", srcValue.Type())) 44 } 45 copyProperties(dstValue.Elem(), srcValue.Elem()) 46} 47 48func copyProperties(dstValue, srcValue reflect.Value) { 49 typ := dstValue.Type() 50 if srcValue.Type() != typ { 51 panic(fmt.Errorf("can't copy mismatching types (%s <- %s)", 52 dstValue.Kind(), srcValue.Kind())) 53 } 54 55 for i, field := range typeFields(typ) { 56 if field.PkgPath != "" { 57 panic(fmt.Errorf("can't copy a private field %q", field.Name)) 58 } 59 60 srcFieldValue := srcValue.Field(i) 61 dstFieldValue := dstValue.Field(i) 62 dstFieldInterfaceValue := reflect.Value{} 63 origDstFieldValue := dstFieldValue 64 65 switch srcFieldValue.Kind() { 66 case reflect.Bool, reflect.String, reflect.Int, reflect.Uint: 67 dstFieldValue.Set(srcFieldValue) 68 case reflect.Struct: 69 if isConfigurable(srcFieldValue.Type()) { 70 dstFieldValue.Set(reflect.ValueOf(srcFieldValue.Interface().(configurableReflection).clone())) 71 } else { 72 copyProperties(dstFieldValue, srcFieldValue) 73 } 74 case reflect.Slice: 75 if !srcFieldValue.IsNil() { 76 if srcFieldValue != dstFieldValue { 77 newSlice := reflect.MakeSlice(field.Type, srcFieldValue.Len(), 78 srcFieldValue.Len()) 79 reflect.Copy(newSlice, srcFieldValue) 80 dstFieldValue.Set(newSlice) 81 } 82 } else { 83 dstFieldValue.Set(srcFieldValue) 84 } 85 case reflect.Map: 86 if !srcFieldValue.IsNil() { 87 newMap := reflect.MakeMap(field.Type) 88 89 iter := srcFieldValue.MapRange() 90 for iter.Next() { 91 newMap.SetMapIndex(iter.Key(), iter.Value()) 92 } 93 dstFieldValue.Set(newMap) 94 } else { 95 dstFieldValue.Set(srcFieldValue) 96 } 97 case reflect.Interface: 98 if srcFieldValue.IsNil() { 99 dstFieldValue.Set(srcFieldValue) 100 break 101 } 102 103 srcFieldValue = srcFieldValue.Elem() 104 105 if !isStructPtr(srcFieldValue.Type()) { 106 panic(fmt.Errorf("can't clone field %q: expected interface to contain *struct, found %s", 107 field.Name, srcFieldValue.Type())) 108 } 109 110 if dstFieldValue.IsNil() || dstFieldValue.Elem().Type() != srcFieldValue.Type() { 111 // We can't use the existing destination allocation, so 112 // clone a new one. 113 newValue := reflect.New(srcFieldValue.Type()).Elem() 114 dstFieldValue.Set(newValue) 115 dstFieldInterfaceValue = dstFieldValue 116 dstFieldValue = newValue 117 } else { 118 dstFieldValue = dstFieldValue.Elem() 119 } 120 fallthrough 121 case reflect.Ptr: 122 if srcFieldValue.IsNil() { 123 origDstFieldValue.Set(srcFieldValue) 124 break 125 } 126 127 switch srcFieldValue.Elem().Kind() { 128 case reflect.Struct: 129 if !dstFieldValue.IsNil() { 130 // Re-use the existing allocation. 131 copyProperties(dstFieldValue.Elem(), srcFieldValue.Elem()) 132 break 133 } else { 134 newValue := CloneProperties(srcFieldValue) 135 if dstFieldInterfaceValue.IsValid() { 136 dstFieldInterfaceValue.Set(newValue) 137 } else { 138 origDstFieldValue.Set(newValue) 139 } 140 } 141 case reflect.Bool, reflect.Int64, reflect.String: 142 newValue := reflect.New(srcFieldValue.Elem().Type()) 143 newValue.Elem().Set(srcFieldValue.Elem()) 144 origDstFieldValue.Set(newValue) 145 default: 146 panic(fmt.Errorf("can't clone pointer field %q type %s", 147 field.Name, srcFieldValue.Type())) 148 } 149 default: 150 panic(fmt.Errorf("unexpected type for property struct field %q: %s", 151 field.Name, srcFieldValue.Type())) 152 } 153 } 154} 155 156// ZeroProperties takes a reflect.Value of a pointer to a struct and replaces all of its fields 157// with zero values, recursing into struct, pointer to struct and interface fields. 158func ZeroProperties(structValue reflect.Value) { 159 if !isStructPtr(structValue.Type()) { 160 panic(fmt.Errorf("ZeroProperties expected *struct, got %s", structValue.Type())) 161 } 162 zeroProperties(structValue.Elem()) 163} 164 165func zeroProperties(structValue reflect.Value) { 166 typ := structValue.Type() 167 168 for i, field := range typeFields(typ) { 169 if field.PkgPath != "" { 170 // The field is not exported so just skip it. 171 continue 172 } 173 174 fieldValue := structValue.Field(i) 175 176 switch fieldValue.Kind() { 177 case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint, reflect.Map: 178 fieldValue.Set(reflect.Zero(fieldValue.Type())) 179 case reflect.Interface: 180 if fieldValue.IsNil() { 181 break 182 } 183 184 // We leave the pointer intact and zero out the struct that's 185 // pointed to. 186 fieldValue = fieldValue.Elem() 187 if !isStructPtr(fieldValue.Type()) { 188 panic(fmt.Errorf("can't zero field %q: expected interface to contain *struct, found %s", 189 field.Name, fieldValue.Type())) 190 } 191 fallthrough 192 case reflect.Ptr: 193 switch fieldValue.Type().Elem().Kind() { 194 case reflect.Struct: 195 if fieldValue.IsNil() { 196 break 197 } 198 zeroProperties(fieldValue.Elem()) 199 case reflect.Bool, reflect.Int64, reflect.String: 200 fieldValue.Set(reflect.Zero(fieldValue.Type())) 201 default: 202 panic(fmt.Errorf("can't zero field %q: points to a %s", 203 field.Name, fieldValue.Elem().Kind())) 204 } 205 case reflect.Struct: 206 zeroProperties(fieldValue) 207 default: 208 panic(fmt.Errorf("unexpected kind for property struct field %q: %s", 209 field.Name, fieldValue.Kind())) 210 } 211 } 212} 213 214// CloneEmptyProperties takes a reflect.Value of a pointer to a struct and returns a reflect.Value 215// of a pointer to a new struct that has the zero values for its fields. It recursively clones 216// struct pointers and interfaces that contain struct pointers. 217func CloneEmptyProperties(structValue reflect.Value) reflect.Value { 218 if !isStructPtr(structValue.Type()) { 219 panic(fmt.Errorf("CloneEmptyProperties expected *struct, got %s", structValue.Type())) 220 } 221 result := reflect.New(structValue.Type().Elem()) 222 cloneEmptyProperties(result.Elem(), structValue.Elem()) 223 return result 224} 225 226func cloneEmptyProperties(dstValue, srcValue reflect.Value) { 227 typ := srcValue.Type() 228 for i, field := range typeFields(typ) { 229 if field.PkgPath != "" { 230 // The field is not exported so just skip it. 231 continue 232 } 233 234 srcFieldValue := srcValue.Field(i) 235 dstFieldValue := dstValue.Field(i) 236 dstFieldInterfaceValue := reflect.Value{} 237 238 switch srcFieldValue.Kind() { 239 case reflect.Bool, reflect.String, reflect.Slice, reflect.Map, reflect.Int, reflect.Uint: 240 // Nothing 241 case reflect.Struct: 242 cloneEmptyProperties(dstFieldValue, srcFieldValue) 243 case reflect.Interface: 244 if srcFieldValue.IsNil() { 245 break 246 } 247 248 srcFieldValue = srcFieldValue.Elem() 249 if !isStructPtr(srcFieldValue.Type()) { 250 panic(fmt.Errorf("can't clone empty field %q: expected interface to contain *struct, found %s", 251 field.Name, srcFieldValue.Type())) 252 } 253 254 newValue := reflect.New(srcFieldValue.Type()).Elem() 255 dstFieldValue.Set(newValue) 256 dstFieldInterfaceValue = dstFieldValue 257 dstFieldValue = newValue 258 fallthrough 259 case reflect.Ptr: 260 switch srcFieldValue.Type().Elem().Kind() { 261 case reflect.Struct: 262 if srcFieldValue.IsNil() { 263 break 264 } 265 newValue := CloneEmptyProperties(srcFieldValue) 266 if dstFieldInterfaceValue.IsValid() { 267 dstFieldInterfaceValue.Set(newValue) 268 } else { 269 dstFieldValue.Set(newValue) 270 } 271 case reflect.Bool, reflect.Int64, reflect.String: 272 // Nothing 273 default: 274 panic(fmt.Errorf("can't clone empty field %q: points to a %s", 275 field.Name, srcFieldValue.Elem().Kind())) 276 } 277 278 default: 279 panic(fmt.Errorf("unexpected kind for property struct field %q: %s", 280 field.Name, srcFieldValue.Kind())) 281 } 282 } 283} 284 285var typeFieldCache sync.Map 286 287func typeFields(typ reflect.Type) []reflect.StructField { 288 // reflect.Type.Field allocates a []int{} to hold the index every time it is called, which ends up 289 // being a significant portion of the GC pressure. It can't reuse the same one in case a caller 290 // modifies the backing array through the slice. Since we don't modify it, cache the result 291 // locally to reduce allocations. 292 293 // Fast path 294 if typeFields, ok := typeFieldCache.Load(typ); ok { 295 return typeFields.([]reflect.StructField) 296 } 297 298 // Slow path 299 typeFields := make([]reflect.StructField, typ.NumField()) 300 301 for i := range typeFields { 302 typeFields[i] = typ.Field(i) 303 } 304 305 typeFieldCache.Store(typ, typeFields) 306 307 return typeFields 308} 309