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