Home | History | Annotate | Download | only in proptools
      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 
     15 package proptools
     16 
     17 import (
     18 	"fmt"
     19 	"reflect"
     20 )
     21 
     22 func CloneProperties(structValue reflect.Value) reflect.Value {
     23 	result := reflect.New(structValue.Type())
     24 	CopyProperties(result.Elem(), structValue)
     25 	return result
     26 }
     27 
     28 func 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 
    130 func 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 
    184 func CloneEmptyProperties(structValue reflect.Value) reflect.Value {
    185 	result := reflect.New(structValue.Type())
    186 	cloneEmptyProperties(result.Elem(), structValue)
    187 	return result
    188 }
    189 
    190 func 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