github.com/hedzr/evendeep@v0.4.8/ctrl.go (about) 1 package evendeep 2 3 import ( 4 "github.com/hedzr/log" 5 6 "github.com/hedzr/evendeep/dbglog" 7 "github.com/hedzr/evendeep/flags" 8 "github.com/hedzr/evendeep/flags/cms" 9 "github.com/hedzr/evendeep/internal/tool" 10 "github.com/hedzr/evendeep/typ" 11 12 "gopkg.in/hedzr/errors.v3" 13 14 "reflect" 15 "unsafe" 16 ) 17 18 type cpController struct { 19 copyUnexportedFields bool 20 copyFunctionResultToTarget bool 21 passSourceAsFunctionInArgs bool 22 autoExpandStruct bool // navigate into nested struct? 23 autoNewStruct bool // create new instance if field is a ptr 24 tryApplyConverterAtFirst bool // ValueConverters first, or ValueCopiers? 25 wipeSlice1st bool // wipe Slice or Map before copy/merge from source field 26 27 makeNewClone bool // make a new clone by copying to a fresh new object 28 flags flags.Flags // CopyMergeStrategies globally 29 ignoreNames []string // optional ignored names with wild-matching 30 funcInputs []typ.Any // preset input args for function invoking 31 rethrow bool // panic when error occurs 32 33 advanceTargetFieldPointerEvenIfSourceIgnored bool 34 35 tagKeyName string // struct tag name for cmd.CopyMergeStrategy, default is "" and assumes using "copy" as key name 36 37 valueConverters ValueConverters 38 valueCopiers ValueCopiers 39 40 sourceExtractor SourceValueExtractor // simple struct field value extractor in single depth 41 targetSetter TargetValueSetter // 42 43 // targetOriented indicates both sourceExtractor and target object are available. 44 // 45 // When targetOriented is true or cms.ByName has been specified, Copier 46 // do traverse on a struct with target-oriented way. That is, copier will 47 // pick up a source field and the corresponding target field with same name, 48 // or prefer one after name transformed. 49 // See also Name Conversions. 50 targetOriented bool // loop for target struct fields? default is for source. 51 } 52 53 // SourceValueExtractor provides a hook for handling 54 // the extraction from source field. 55 // 56 // SourceValueExtractor can work for non-nested struct. 57 type SourceValueExtractor func(targetName string) typ.Any 58 59 // TargetValueSetter provide a hook for handling the setup 60 // to a target field. 61 // 62 // In the TargetValueSetter you could return evendeep.ErrShouldFallback to 63 // call the evendeep standard processing. 64 // 65 // TargetValueSetter can work for struct and map. 66 // 67 // NOTE that the sourceNames[0] is current field name, and the whole 68 // sourceNames slice includes the path of the nested struct(s), 69 // in reversal order. 70 type TargetValueSetter func(value *reflect.Value, sourceNames ...string) (err error) 71 72 // CopyTo makes a deep clone of a source object or merges it into the target. 73 func (c *cpController) CopyTo(fromObjOrPtr, toObjPtr interface{}, opts ...Opt) (err error) { 74 if fromObjOrPtr == nil || toObjPtr == nil { 75 return 76 } 77 78 lazyInitRoutines() 79 80 for _, opt := range opts { 81 opt(c) 82 } 83 84 var ( 85 from0 = reflect.ValueOf(fromObjOrPtr) 86 to0 = reflect.ValueOf(toObjPtr) 87 from = tool.Rindirect(from0) 88 to = tool.Rindirect(to0) 89 root = newParams(withOwners(c, nil, &from0, &to0, &from, &to)) 90 ) 91 92 dbglog.Log(" flags: %v", c.flags) 93 dbglog.Log("flags (verbose): %+v", c.flags) 94 dbglog.Log(" from.type: %v | input: %v", tool.Typfmtv(&from), tool.Typfmtv(&from0)) 95 dbglog.Log(" to.type: %v | input: %v", tool.Typfmtv(&to), tool.Typfmtv(&to0)) 96 97 err = c.copyTo(root, from, to) 98 return 99 } 100 101 func (c *cpController) copyTo(params *Params, from, to reflect.Value) (err error) { 102 err = c.copyToInternal(params, from, to, 103 func(c *cpController, params *Params, from, to reflect.Value) (err error) { 104 kind, pkgPath := from.Kind(), from.Type().PkgPath() 105 if c.sourceExtractor != nil && to.IsValid() && !tool.IsNil(to) { 106 // use tool.IsNil because we are checking for: 107 // 1. to,IsNil() if 'to' is an addressable value (such as slice, map, or ptr) 108 // 2. false if 'to' is not an addressable value (such as struct, int, ...) 109 kind, pkgPath, c.targetOriented = to.Kind(), to.Type().PkgPath(), true 110 } else { 111 c.targetOriented = false 112 } 113 if kind != reflect.Struct || !packageisreserved(pkgPath) { 114 if fn, ok := copyToRoutines[kind]; ok && fn != nil { 115 err = fn(c, params, from, to) 116 return 117 } 118 } 119 120 // source is primitive type, or in a reserved package such as time, os, ... 121 dbglog.Log(" - from.type: %v - fallback to copyDefaultHandler | to.type: %v", kind, tool.Typfmtv(&to)) 122 err = copyDefaultHandler(c, params, from, to) 123 return 124 }) 125 return 126 } 127 128 func (c *cpController) copyToInternal( //nolint:gocognit //yes, it is an integrated logic 129 params *Params, from, to reflect.Value, 130 cb copyfn, 131 ) (err error) { 132 // Return is from value is invalid 133 if !from.IsValid() { 134 if params.isGroupedFlagOKDeeply(cms.OmitIfEmpty, cms.OmitIfNil, cms.OmitIfZero) { 135 return // fast fail here 136 } 137 // todo set target to zero 138 return 139 } 140 if !to.IsValid() { 141 dbglog.Log(`target is invalid, cannot be set.`) 142 return 143 } 144 145 if c.testCloneable(params, from, to) { 146 dbglog.Log(`from -> to is NOT cloneable`) 147 return 148 } 149 150 //nolint:lll,nestif //keep it 151 if from.CanAddr() && to.CanAddr() && tool.KindIs(from.Kind(), reflect.Array, reflect.Map, reflect.Slice, reflect.Struct) { 152 addr1 := unsafe.Pointer(from.UnsafeAddr()) 153 addr2 := unsafe.Pointer(to.UnsafeAddr()) 154 if uintptr(addr1) > uintptr(addr2) { 155 // Canonicalize order to reduce number of entries in visited. 156 // Assumes non-moving garbage collector. 157 addr1, addr2 = addr2, addr1 158 } 159 160 if params != nil { 161 params.visiting = visit{addr1, addr2, from.Type()} 162 if params.visited == nil { 163 params.visited = make(map[visit]visiteddestination) 164 } 165 if dest, ok := params.visited[params.visiting]; ok { 166 to.Set(dest.dst) 167 dbglog.Log(`The visited target found, set it from cache`) 168 return 169 } 170 params.visited[params.visiting] = visiteddestination{} 171 } 172 } 173 174 // fromType := c.indirectType(from.Type()) 175 // toType := c.indirectType(to.Type()) 176 177 defer func() { 178 if e := recover(); e != nil { 179 err = errors.New("[recovered] copyTo unsatisfied ([%v] -> [%v])", 180 tool.RindirectType(from.Type()), tool.RindirectType(to.Type())). 181 WithMaxObjectStringLength(maxObjectStringLen). 182 WithData(e). 183 WithTaggedData(errors.TaggedData{ 184 "source": from, 185 "target": to, 186 }) 187 188 // skip go-lib frames and defer-recover frame, back to the point throwing panic 189 n := log.CalcStackFrames(1) // skip defer-recover frame at first 190 if c.rethrow { 191 log.Skip(n).Panicf("%+v", err) 192 } else { 193 log.Skip(n).Errorf("%+v", err) 194 } 195 } 196 }() 197 198 params.resultForNewSlice = nil 199 err = cb(c, params, from, to) 200 return 201 } 202 203 func (c *cpController) testCloneable(params *Params, from, to reflect.Value) (processed bool) { 204 if from.CanInterface() { //nolint:nestif //keep it 205 var fromObj interface{} 206 if params != nil && params.srcOwner != nil { 207 f, t := *params.srcOwner, *params.dstOwner 208 retry: 209 fromObj = f.Interface() 210 if c.testCloneable1(params, fromObj, t) { 211 return true 212 } 213 if k := f.Kind(); k == reflect.Ptr { 214 f = f.Elem() 215 if k = t.Kind(); k == reflect.Ptr { 216 t = t.Elem() 217 } 218 goto retry 219 } 220 } 221 } 222 return 223 } 224 225 func (c *cpController) testCloneable1(params *Params, fromObj interface{}, to reflect.Value) (processed bool) { 226 if dc, ok := fromObj.(Cloneable); ok { //nolint:gocritic // no need to rewrite to 'switch' 227 to.Set(reflect.ValueOf(dc.Clone())) 228 processed = true 229 } else if dc1, ok1 := fromObj.(DeepCopyable); ok1 { 230 to.Set(reflect.ValueOf(dc1.DeepCopy())) 231 processed = true 232 } 233 return 234 } 235 236 func (c *cpController) withConverters(cvt ...ValueConverter) *cpController { //nolint:unused //usable 237 for _, cc := range cvt { 238 if cc != nil { 239 c.valueConverters = append(c.valueConverters, cc) 240 } 241 } 242 return c 243 } 244 245 func (c *cpController) withCopiers(cvt ...ValueCopier) *cpController { //nolint:unused //future 246 for _, cc := range cvt { 247 if cc != nil { 248 c.valueCopiers = append(c.valueCopiers, cc) 249 } 250 } 251 return c 252 } 253 254 func (c *cpController) withFlags(flags1 ...cms.CopyMergeStrategy) *cpController { //nolint:unused //future 255 if c.flags == nil { 256 c.flags = flags.New(flags1...) 257 } else { 258 c.flags.WithFlags(flags1...) 259 } 260 return c 261 } 262 263 func (c *cpController) Flags() flags.Flags { return c.flags } 264 func (c *cpController) SetFlags(f flags.Flags) { c.flags = f } 265 266 // SaveFlagsAndRestore is a defer-function so the best usage is: 267 // 268 // defer c.SaveFlagsAndRestore()() 269 func (c *cpController) SaveFlagsAndRestore() func() { 270 var saved = c.flags.Clone() 271 return func() { 272 c.flags = saved 273 } 274 } 275 276 const maxObjectStringLen = 320