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