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) 21 22func CloneProperties(structValue reflect.Value) reflect.Value { 23 result := reflect.New(structValue.Type()) 24 CopyProperties(result.Elem(), structValue) 25 return result 26} 27 28func CopyProperties(dstValue, srcValue reflect.Value) { 29 typ := dstValue.Type() 30 if srcValue.Type() != typ { 31 panic(fmt.Errorf("can't copy mismatching types (%s <- %s)", 32 dstValue.Kind(), srcValue.Kind())) 33 } 34 35 for i := 0; i < srcValue.NumField(); i++ { 36 field := typ.Field(i) 37 if field.PkgPath != "" { 38 // The field is not exported so just skip it. 39 continue 40 } 41 42 srcFieldValue := srcValue.Field(i) 43 dstFieldValue := dstValue.Field(i) 44 dstFieldInterfaceValue := reflect.Value{} 45 46 switch srcFieldValue.Kind() { 47 case reflect.Bool, reflect.String, reflect.Int, reflect.Uint: 48 dstFieldValue.Set(srcFieldValue) 49 case reflect.Struct: 50 CopyProperties(dstFieldValue, srcFieldValue) 51 case reflect.Slice: 52 if !srcFieldValue.IsNil() { 53 if field.Type.Elem().Kind() != reflect.String { 54 panic(fmt.Errorf("can't copy field %q: slice elements are not strings", field.Name)) 55 } 56 if srcFieldValue != dstFieldValue { 57 newSlice := reflect.MakeSlice(field.Type, srcFieldValue.Len(), 58 srcFieldValue.Len()) 59 reflect.Copy(newSlice, srcFieldValue) 60 dstFieldValue.Set(newSlice) 61 } 62 } else { 63 dstFieldValue.Set(srcFieldValue) 64 } 65 case reflect.Interface: 66 if srcFieldValue.IsNil() { 67 dstFieldValue.Set(srcFieldValue) 68 break 69 } 70 71 srcFieldValue = srcFieldValue.Elem() 72 73 if srcFieldValue.Kind() != reflect.Ptr { 74 panic(fmt.Errorf("can't clone field %q: interface refers to a non-pointer", 75 field.Name)) 76 } 77 if srcFieldValue.Type().Elem().Kind() != reflect.Struct { 78 panic(fmt.Errorf("can't clone field %q: interface points to a non-struct", 79 field.Name)) 80 } 81 82 if dstFieldValue.IsNil() || dstFieldValue.Elem().Type() != srcFieldValue.Type() { 83 // We can't use the existing destination allocation, so 84 // clone a new one. 85 newValue := reflect.New(srcFieldValue.Type()).Elem() 86 dstFieldValue.Set(newValue) 87 dstFieldInterfaceValue = dstFieldValue 88 dstFieldValue = newValue 89 } else { 90 dstFieldValue = dstFieldValue.Elem() 91 } 92 fallthrough 93 case reflect.Ptr: 94 if srcFieldValue.IsNil() { 95 dstFieldValue.Set(srcFieldValue) 96 break 97 } 98 99 srcFieldValue := srcFieldValue.Elem() 100 101 switch srcFieldValue.Kind() { 102 case reflect.Struct: 103 if !dstFieldValue.IsNil() { 104 // Re-use the existing allocation. 105 CopyProperties(dstFieldValue.Elem(), srcFieldValue) 106 break 107 } else { 108 newValue := CloneProperties(srcFieldValue) 109 if dstFieldInterfaceValue.IsValid() { 110 dstFieldInterfaceValue.Set(newValue) 111 } else { 112 dstFieldValue.Set(newValue) 113 } 114 } 115 case reflect.Bool, reflect.String: 116 newValue := reflect.New(srcFieldValue.Type()) 117 newValue.Elem().Set(srcFieldValue) 118 dstFieldValue.Set(newValue) 119 default: 120 panic(fmt.Errorf("can't clone field %q: points to a %s", 121 field.Name, srcFieldValue.Kind())) 122 } 123 default: 124 panic(fmt.Errorf("unexpected kind for property struct field %q: %s", 125 field.Name, srcFieldValue.Kind())) 126 } 127 } 128} 129 130func ZeroProperties(structValue reflect.Value) { 131 typ := structValue.Type() 132 133 for i := 0; i < structValue.NumField(); i++ { 134 field := typ.Field(i) 135 if field.PkgPath != "" { 136 // The field is not exported so just skip it. 137 continue 138 } 139 140 fieldValue := structValue.Field(i) 141 142 switch fieldValue.Kind() { 143 case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint: 144 fieldValue.Set(reflect.Zero(fieldValue.Type())) 145 case reflect.Interface: 146 if fieldValue.IsNil() { 147 break 148 } 149 150 // We leave the pointer intact and zero out the struct that's 151 // pointed to. 152 fieldValue = fieldValue.Elem() 153 if fieldValue.Kind() != reflect.Ptr { 154 panic(fmt.Errorf("can't zero field %q: interface refers to a non-pointer", 155 field.Name)) 156 } 157 if fieldValue.Type().Elem().Kind() != reflect.Struct { 158 panic(fmt.Errorf("can't zero field %q: interface points to a non-struct", 159 field.Name)) 160 } 161 fallthrough 162 case reflect.Ptr: 163 switch fieldValue.Type().Elem().Kind() { 164 case reflect.Struct: 165 if fieldValue.IsNil() { 166 break 167 } 168 ZeroProperties(fieldValue.Elem()) 169 case reflect.Bool, reflect.String: 170 fieldValue.Set(reflect.Zero(fieldValue.Type())) 171 default: 172 panic(fmt.Errorf("can't zero field %q: points to a %s", 173 field.Name, fieldValue.Elem().Kind())) 174 } 175 case reflect.Struct: 176 ZeroProperties(fieldValue) 177 default: 178 panic(fmt.Errorf("unexpected kind for property struct field %q: %s", 179 field.Name, fieldValue.Kind())) 180 } 181 } 182} 183 184func CloneEmptyProperties(structValue reflect.Value) reflect.Value { 185 result := reflect.New(structValue.Type()) 186 cloneEmptyProperties(result.Elem(), structValue) 187 return result 188} 189 190func cloneEmptyProperties(dstValue, srcValue reflect.Value) { 191 typ := srcValue.Type() 192 for i := 0; i < srcValue.NumField(); i++ { 193 field := typ.Field(i) 194 if field.PkgPath != "" { 195 // The field is not exported so just skip it. 196 continue 197 } 198 199 srcFieldValue := srcValue.Field(i) 200 dstFieldValue := dstValue.Field(i) 201 dstFieldInterfaceValue := reflect.Value{} 202 203 switch srcFieldValue.Kind() { 204 case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint: 205 // Nothing 206 case reflect.Struct: 207 cloneEmptyProperties(dstFieldValue, srcFieldValue) 208 case reflect.Interface: 209 if srcFieldValue.IsNil() { 210 break 211 } 212 213 srcFieldValue = srcFieldValue.Elem() 214 if srcFieldValue.Kind() != reflect.Ptr { 215 panic(fmt.Errorf("can't clone empty field %q: interface refers to a non-pointer", 216 field.Name)) 217 } 218 if srcFieldValue.Type().Elem().Kind() != reflect.Struct { 219 panic(fmt.Errorf("can't clone empty field %q: interface points to a non-struct", 220 field.Name)) 221 } 222 223 newValue := reflect.New(srcFieldValue.Type()).Elem() 224 dstFieldValue.Set(newValue) 225 dstFieldInterfaceValue = dstFieldValue 226 dstFieldValue = newValue 227 fallthrough 228 case reflect.Ptr: 229 switch srcFieldValue.Type().Elem().Kind() { 230 case reflect.Struct: 231 if srcFieldValue.IsNil() { 232 break 233 } 234 newValue := CloneEmptyProperties(srcFieldValue.Elem()) 235 if dstFieldInterfaceValue.IsValid() { 236 dstFieldInterfaceValue.Set(newValue) 237 } else { 238 dstFieldValue.Set(newValue) 239 } 240 case reflect.Bool, reflect.String: 241 // Nothing 242 default: 243 panic(fmt.Errorf("can't clone empty field %q: points to a %s", 244 field.Name, srcFieldValue.Elem().Kind())) 245 } 246 247 default: 248 panic(fmt.Errorf("unexpected kind for property struct field %q: %s", 249 field.Name, srcFieldValue.Kind())) 250 } 251 } 252} 253