stackitem: implement DeepCopy
This commit is contained in:
parent
ef53a45e7a
commit
b8cc33d0b2
2 changed files with 122 additions and 0 deletions
|
@ -1019,3 +1019,66 @@ func (i *Buffer) Convert(typ Type) (Item, error) {
|
||||||
func (i *Buffer) Len() int {
|
func (i *Buffer) Len() int {
|
||||||
return len(i.value)
|
return len(i.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopy returns new deep copy of the provided item.
|
||||||
|
// Values of Interop items are not deeply copied.
|
||||||
|
// It does preserve duplicates only for non-primitive types.
|
||||||
|
func DeepCopy(item Item) Item {
|
||||||
|
seen := make(map[Item]Item)
|
||||||
|
return deepCopy(item, seen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deepCopy(item Item, seen map[Item]Item) Item {
|
||||||
|
if it := seen[item]; it != nil {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
switch it := item.(type) {
|
||||||
|
case Null:
|
||||||
|
return Null{}
|
||||||
|
case *Array:
|
||||||
|
arr := NewArray(make([]Item, len(it.value)))
|
||||||
|
seen[item] = arr
|
||||||
|
for i := range it.value {
|
||||||
|
arr.value[i] = deepCopy(it.value[i], seen)
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
case *Struct:
|
||||||
|
arr := NewStruct(make([]Item, len(it.value)))
|
||||||
|
seen[item] = arr
|
||||||
|
for i := range it.value {
|
||||||
|
arr.value[i] = deepCopy(it.value[i], seen)
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
case *Map:
|
||||||
|
m := NewMap()
|
||||||
|
seen[item] = m
|
||||||
|
for i := range it.value {
|
||||||
|
key := deepCopy(it.value[i].Key, seen)
|
||||||
|
value := deepCopy(it.value[i].Value, seen)
|
||||||
|
m.Add(key, value)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
case *BigInteger:
|
||||||
|
bi := new(big.Int).SetBytes(it.value.Bytes())
|
||||||
|
if it.value.Sign() == -1 {
|
||||||
|
bi.Neg(bi)
|
||||||
|
}
|
||||||
|
return NewBigInteger(bi)
|
||||||
|
case *ByteArray:
|
||||||
|
val := make([]byte, len(it.value))
|
||||||
|
copy(val, it.value)
|
||||||
|
return NewByteArray(val)
|
||||||
|
case *Buffer:
|
||||||
|
val := make([]byte, len(it.value))
|
||||||
|
copy(val, it.value)
|
||||||
|
return NewBuffer(val)
|
||||||
|
case *Bool:
|
||||||
|
return NewBool(it.value)
|
||||||
|
case *Pointer:
|
||||||
|
return NewPointer(it.pos, it.script)
|
||||||
|
case *Interop:
|
||||||
|
return NewInterop(it.value)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var makeStackItemTestCases = []struct {
|
var makeStackItemTestCases = []struct {
|
||||||
|
@ -412,3 +413,61 @@ func TestMarshalJSON(t *testing.T) {
|
||||||
assert.Equal(t, testCase.result, actual)
|
assert.Equal(t, testCase.result, actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeepCopy(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
item Item
|
||||||
|
}{
|
||||||
|
{"Integer", NewBigInteger(big.NewInt(1))},
|
||||||
|
{"ByteArray", NewByteArray([]byte{1, 2, 3})},
|
||||||
|
{"Buffer", NewBuffer([]byte{1, 2, 3})},
|
||||||
|
{"Bool", NewBool(true)},
|
||||||
|
{"Pointer", NewPointer(1, []byte{1, 2, 3})},
|
||||||
|
{"Interop", NewInterop(&[]byte{1, 2})},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
actual := DeepCopy(tc.item)
|
||||||
|
require.Equal(t, tc.item, actual)
|
||||||
|
require.False(t, actual == tc.item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Null", func(t *testing.T) {
|
||||||
|
require.Equal(t, Null{}, DeepCopy(Null{}))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Array", func(t *testing.T) {
|
||||||
|
arr := NewArray(make([]Item, 2))
|
||||||
|
arr.value[0] = NewBool(true)
|
||||||
|
arr.value[1] = arr
|
||||||
|
|
||||||
|
actual := DeepCopy(arr)
|
||||||
|
require.Equal(t, arr, actual)
|
||||||
|
require.False(t, arr == actual)
|
||||||
|
require.True(t, actual == actual.(*Array).value[1])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Struct", func(t *testing.T) {
|
||||||
|
arr := NewStruct(make([]Item, 2))
|
||||||
|
arr.value[0] = NewBool(true)
|
||||||
|
arr.value[1] = arr
|
||||||
|
|
||||||
|
actual := DeepCopy(arr)
|
||||||
|
require.Equal(t, arr, actual)
|
||||||
|
require.False(t, arr == actual)
|
||||||
|
require.True(t, actual == actual.(*Struct).value[1])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Map", func(t *testing.T) {
|
||||||
|
m := NewMapWithValue(make([]MapElement, 2))
|
||||||
|
m.value[0] = MapElement{Key: NewBool(true), Value: m}
|
||||||
|
m.value[1] = MapElement{Key: NewBigInteger(big.NewInt(1)), Value: NewByteArray([]byte{1, 2, 3})}
|
||||||
|
|
||||||
|
actual := DeepCopy(m)
|
||||||
|
require.Equal(t, m, actual)
|
||||||
|
require.False(t, m == actual)
|
||||||
|
require.True(t, actual == actual.(*Map).value[0].Value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue