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