forked from TrueCloudLab/lego
186 lines
4.2 KiB
Go
186 lines
4.2 KiB
Go
|
package copier
|
||
|
|
||
|
import (
|
||
|
"database/sql"
|
||
|
"errors"
|
||
|
"reflect"
|
||
|
)
|
||
|
|
||
|
// Copy copy things
|
||
|
func Copy(toValue interface{}, fromValue interface{}) (err error) {
|
||
|
var (
|
||
|
isSlice bool
|
||
|
amount = 1
|
||
|
from = indirect(reflect.ValueOf(fromValue))
|
||
|
to = indirect(reflect.ValueOf(toValue))
|
||
|
)
|
||
|
|
||
|
if !to.CanAddr() {
|
||
|
return errors.New("copy to value is unaddressable")
|
||
|
}
|
||
|
|
||
|
// Return is from value is invalid
|
||
|
if !from.IsValid() {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Just set it if possible to assign
|
||
|
if from.Type().AssignableTo(to.Type()) {
|
||
|
to.Set(from)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
fromType := indirectType(from.Type())
|
||
|
toType := indirectType(to.Type())
|
||
|
|
||
|
if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if to.Kind() == reflect.Slice {
|
||
|
isSlice = true
|
||
|
if from.Kind() == reflect.Slice {
|
||
|
amount = from.Len()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for i := 0; i < amount; i++ {
|
||
|
var dest, source reflect.Value
|
||
|
|
||
|
if isSlice {
|
||
|
// source
|
||
|
if from.Kind() == reflect.Slice {
|
||
|
source = indirect(from.Index(i))
|
||
|
} else {
|
||
|
source = indirect(from)
|
||
|
}
|
||
|
|
||
|
// dest
|
||
|
dest = indirect(reflect.New(toType).Elem())
|
||
|
} else {
|
||
|
source = indirect(from)
|
||
|
dest = indirect(to)
|
||
|
}
|
||
|
|
||
|
// Copy from field to field or method
|
||
|
for _, field := range deepFields(fromType) {
|
||
|
name := field.Name
|
||
|
|
||
|
if fromField := source.FieldByName(name); fromField.IsValid() {
|
||
|
// has field
|
||
|
if toField := dest.FieldByName(name); toField.IsValid() {
|
||
|
if toField.CanSet() {
|
||
|
if !set(toField, fromField) {
|
||
|
if err := Copy(toField.Addr().Interface(), fromField.Interface()); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
// try to set to method
|
||
|
var toMethod reflect.Value
|
||
|
if dest.CanAddr() {
|
||
|
toMethod = dest.Addr().MethodByName(name)
|
||
|
} else {
|
||
|
toMethod = dest.MethodByName(name)
|
||
|
}
|
||
|
|
||
|
if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) {
|
||
|
toMethod.Call([]reflect.Value{fromField})
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Copy from method to field
|
||
|
for _, field := range deepFields(toType) {
|
||
|
name := field.Name
|
||
|
|
||
|
var fromMethod reflect.Value
|
||
|
if source.CanAddr() {
|
||
|
fromMethod = source.Addr().MethodByName(name)
|
||
|
} else {
|
||
|
fromMethod = source.MethodByName(name)
|
||
|
}
|
||
|
|
||
|
if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 {
|
||
|
if toField := dest.FieldByName(name); toField.IsValid() && toField.CanSet() {
|
||
|
values := fromMethod.Call([]reflect.Value{})
|
||
|
if len(values) >= 1 {
|
||
|
set(toField, values[0])
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if isSlice {
|
||
|
if dest.Addr().Type().AssignableTo(to.Type().Elem()) {
|
||
|
to.Set(reflect.Append(to, dest.Addr()))
|
||
|
} else if dest.Type().AssignableTo(to.Type().Elem()) {
|
||
|
to.Set(reflect.Append(to, dest))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func deepFields(reflectType reflect.Type) []reflect.StructField {
|
||
|
var fields []reflect.StructField
|
||
|
|
||
|
if reflectType = indirectType(reflectType); reflectType.Kind() == reflect.Struct {
|
||
|
for i := 0; i < reflectType.NumField(); i++ {
|
||
|
v := reflectType.Field(i)
|
||
|
if v.Anonymous {
|
||
|
fields = append(fields, deepFields(v.Type)...)
|
||
|
} else {
|
||
|
fields = append(fields, v)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fields
|
||
|
}
|
||
|
|
||
|
func indirect(reflectValue reflect.Value) reflect.Value {
|
||
|
for reflectValue.Kind() == reflect.Ptr {
|
||
|
reflectValue = reflectValue.Elem()
|
||
|
}
|
||
|
return reflectValue
|
||
|
}
|
||
|
|
||
|
func indirectType(reflectType reflect.Type) reflect.Type {
|
||
|
for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice {
|
||
|
reflectType = reflectType.Elem()
|
||
|
}
|
||
|
return reflectType
|
||
|
}
|
||
|
|
||
|
func set(to, from reflect.Value) bool {
|
||
|
if from.IsValid() {
|
||
|
if to.Kind() == reflect.Ptr {
|
||
|
//set `to` to nil if from is nil
|
||
|
if from.Kind() == reflect.Ptr && from.IsNil() {
|
||
|
to.Set(reflect.Zero(to.Type()))
|
||
|
return true
|
||
|
} else if to.IsNil() {
|
||
|
to.Set(reflect.New(to.Type().Elem()))
|
||
|
}
|
||
|
to = to.Elem()
|
||
|
}
|
||
|
|
||
|
if from.Type().ConvertibleTo(to.Type()) {
|
||
|
to.Set(from.Convert(to.Type()))
|
||
|
} else if scanner, ok := to.Addr().Interface().(sql.Scanner); ok {
|
||
|
err := scanner.Scan(from.Interface())
|
||
|
if err != nil {
|
||
|
return false
|
||
|
}
|
||
|
} else if from.Kind() == reflect.Ptr {
|
||
|
return set(to, from.Elem())
|
||
|
} else {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
return true
|
||
|
}
|