From c3ecaed1528c53f00ec773918cc352fbff762169 Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Mon, 12 Sep 2016 16:47:07 +0100 Subject: [PATCH] Add package for deep copying --- util/deep/deep.go | 87 +++++++++++++++ util/deep/deep_test.go | 233 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 320 insertions(+) create mode 100644 util/deep/deep.go create mode 100644 util/deep/deep_test.go diff --git a/util/deep/deep.go b/util/deep/deep.go new file mode 100644 index 00000000..37563f1b --- /dev/null +++ b/util/deep/deep.go @@ -0,0 +1,87 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package deep + +import ( + "reflect" +) + +// Copy returns a deep copy of the source object. +func Copy(src interface{}) interface{} { + + if src == nil { + return nil + } + + prime := reflect.ValueOf(src) + + clone := reflect.New(prime.Type()).Elem() + + copy(prime, clone) + + return clone.Interface() + +} + +func copy(prime, clone reflect.Value) { + + switch prime.Kind() { + + case reflect.Ptr: + value := prime.Elem() + if !value.IsValid() { + return + } + clone.Set(reflect.New(value.Type())) + copy(value, clone.Elem()) + + case reflect.Interface: + if prime.IsNil() { + return + } + value := prime.Elem() + alike := reflect.New(value.Type()).Elem() + copy(value, alike) + clone.Set(alike) + + case reflect.Struct: + for i := 0; i < prime.NumField(); i++ { + if prime.Type().Field(i).PkgPath != "" { + continue + } + copy(prime.Field(i), clone.Field(i)) + } + + case reflect.Slice: + clone.Set(reflect.MakeSlice(prime.Type(), prime.Len(), prime.Cap())) + for i := 0; i < prime.Len(); i++ { + copy(prime.Index(i), clone.Index(i)) + } + + case reflect.Map: + clone.Set(reflect.MakeMap(prime.Type())) + for _, key := range prime.MapKeys() { + value := prime.MapIndex(key) + alike := reflect.New(value.Type()).Elem() + copy(value, alike) + clone.SetMapIndex(key, alike) + } + + default: + clone.Set(prime) + + } + +} diff --git a/util/deep/deep_test.go b/util/deep/deep_test.go new file mode 100644 index 00000000..4d16dab9 --- /dev/null +++ b/util/deep/deep_test.go @@ -0,0 +1,233 @@ +package deep + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +type Mini struct { + embedded bool +} + +type Test struct { + PublicNumber int + PublicString string + PublicStruct interface{} + PublicPointr interface{} + PublicSlice []interface{} + PublicMap map[string]interface{} + privateNumber int + privateString string + privateStruct interface{} + privatePointr interface{} + privateSlice []interface{} + privateMap map[string]interface{} +} + +func TestMain(t *testing.T) { + + Convey("Can copy nil", t, func() { + var item interface{} + item = nil + done := Copy(item) + So(item, ShouldResemble, done) + }) + +} + +func TestSimple(t *testing.T) { + + Convey("Can copy bool", t, func() { + item := true + done := Copy(item) + So(item, ShouldResemble, done) + }) + + Convey("Can copy int64", t, func() { + item := int64(1) + done := Copy(item) + So(item, ShouldResemble, done) + }) + + Convey("Can copy float64", t, func() { + item := float64(1) + done := Copy(item) + So(item, ShouldResemble, done) + }) + + Convey("Can copy string", t, func() { + item := "string" + done := Copy(item) + So(item, ShouldResemble, done) + }) + +} + +func TestSlices(t *testing.T) { + + Convey("Can copy slice of bools", t, func() { + item := []bool{ + true, + false, + } + done := Copy(item) + So(item, ShouldResemble, done) + }) + + Convey("Can copy slice of int64s", t, func() { + item := []int64{ + 1, + 2, + 3, + } + done := Copy(item) + So(item, ShouldResemble, done) + }) + + Convey("Can copy slice of float64s", t, func() { + item := []float64{ + 1, + 2, + 3, + } + done := Copy(item) + So(item, ShouldResemble, done) + }) + + Convey("Can copy slice of strings", t, func() { + item := []string{ + "str", + "str", + "str", + } + done := Copy(item) + So(item, ShouldResemble, done) + }) + + Convey("Can copy slice of interfaces", t, func() { + item := []interface{}{ + nil, + int64(1), + float64(2), + "str", + } + done := Copy(item) + So(item, ShouldResemble, done) + }) + +} + +func TestObjects(t *testing.T) { + + Convey("Can copy map of bools", t, func() { + item := map[string]bool{ + "a": true, + "b": false, + } + done := Copy(item) + So(item, ShouldResemble, done) + }) + + Convey("Can copy map of int64s", t, func() { + item := map[string]int64{ + "a": 1, + "b": 2, + "c": 3, + } + done := Copy(item) + So(item, ShouldResemble, done) + }) + + Convey("Can copy map of float64s", t, func() { + item := map[string]float64{ + "a": 1, + "b": 2, + "c": 3, + } + done := Copy(item) + So(item, ShouldResemble, done) + }) + + Convey("Can copy map of strings", t, func() { + item := map[string]string{ + "a": "str", + "b": "str", + "c": "str", + } + done := Copy(item) + So(item, ShouldResemble, done) + }) + + Convey("Can copy map of interfaces", t, func() { + item := map[interface{}]interface{}{ + 1: nil, + "b": int64(1), + true: float64(2), + "d": "str", + } + done := Copy(item) + So(item, ShouldResemble, done) + }) + +} + +func TestStructs(t *testing.T) { + + full := Test{ + PublicNumber: 1, + PublicString: "str", + PublicStruct: Mini{}, + PublicPointr: &Mini{}, + PublicSlice: []interface{}{nil, 1, "str", true}, + PublicMap: map[string]interface{}{ + "a": 1, + "b": "str", + "c": true, + }, + privateNumber: 1, + privateString: "str", + privateStruct: Mini{}, + privatePointr: &Mini{}, + privateSlice: []interface{}{nil, 1, "str", true}, + privateMap: map[string]interface{}{ + "a": 1, + "b": "str", + "c": true, + }, + } + + show := Test{ + PublicNumber: full.PublicNumber, + PublicString: full.PublicString, + PublicStruct: full.PublicStruct, + PublicPointr: full.PublicPointr, + PublicSlice: full.PublicSlice, + PublicMap: full.PublicMap, + } + + Convey("Can copy struct", t, func() { + item := full + done := Copy(item) + So(done, ShouldResemble, show) + }) + + Convey("Can copy pointer", t, func() { + item := &full + done := Copy(item) + So(done, ShouldResemble, &show) + }) + + Convey("Can copy slice of structs", t, func() { + item := []interface{}{full, full} + done := Copy(item) + So(done, ShouldResemble, []interface{}{show, show}) + }) + + Convey("Can copy slice of pointers", t, func() { + item := []interface{}{&full, &full} + done := Copy(item) + So(done, ShouldResemble, []interface{}{&show, &show}) + }) + +}