1package schema
2
3import (
4	"bytes"
5	"fmt"
6	"reflect"
7	"sort"
8	"strconv"
9	"sync"
10
11	"github.com/hashicorp/terraform/internal/legacy/helper/hashcode"
12)
13
14// HashString hashes strings. If you want a Set of strings, this is the
15// SchemaSetFunc you want.
16func HashString(v interface{}) int {
17	return hashcode.String(v.(string))
18}
19
20// HashInt hashes integers. If you want a Set of integers, this is the
21// SchemaSetFunc you want.
22func HashInt(v interface{}) int {
23	return hashcode.String(strconv.Itoa(v.(int)))
24}
25
26// HashResource hashes complex structures that are described using
27// a *Resource. This is the default set implementation used when a set's
28// element type is a full resource.
29func HashResource(resource *Resource) SchemaSetFunc {
30	return func(v interface{}) int {
31		var buf bytes.Buffer
32		SerializeResourceForHash(&buf, v, resource)
33		return hashcode.String(buf.String())
34	}
35}
36
37// HashSchema hashes values that are described using a *Schema. This is the
38// default set implementation used when a set's element type is a single
39// schema.
40func HashSchema(schema *Schema) SchemaSetFunc {
41	return func(v interface{}) int {
42		var buf bytes.Buffer
43		SerializeValueForHash(&buf, v, schema)
44		return hashcode.String(buf.String())
45	}
46}
47
48// Set is a set data structure that is returned for elements of type
49// TypeSet.
50type Set struct {
51	F SchemaSetFunc
52
53	m    map[string]interface{}
54	once sync.Once
55}
56
57// NewSet is a convenience method for creating a new set with the given
58// items.
59func NewSet(f SchemaSetFunc, items []interface{}) *Set {
60	s := &Set{F: f}
61	for _, i := range items {
62		s.Add(i)
63	}
64
65	return s
66}
67
68// CopySet returns a copy of another set.
69func CopySet(otherSet *Set) *Set {
70	return NewSet(otherSet.F, otherSet.List())
71}
72
73// Add adds an item to the set if it isn't already in the set.
74func (s *Set) Add(item interface{}) {
75	s.add(item, false)
76}
77
78// Remove removes an item if it's already in the set. Idempotent.
79func (s *Set) Remove(item interface{}) {
80	s.remove(item)
81}
82
83// Contains checks if the set has the given item.
84func (s *Set) Contains(item interface{}) bool {
85	_, ok := s.m[s.hash(item)]
86	return ok
87}
88
89// Len returns the amount of items in the set.
90func (s *Set) Len() int {
91	return len(s.m)
92}
93
94// List returns the elements of this set in slice format.
95//
96// The order of the returned elements is deterministic. Given the same
97// set, the order of this will always be the same.
98func (s *Set) List() []interface{} {
99	result := make([]interface{}, len(s.m))
100	for i, k := range s.listCode() {
101		result[i] = s.m[k]
102	}
103
104	return result
105}
106
107// Difference performs a set difference of the two sets, returning
108// a new third set that has only the elements unique to this set.
109func (s *Set) Difference(other *Set) *Set {
110	result := &Set{F: s.F}
111	result.once.Do(result.init)
112
113	for k, v := range s.m {
114		if _, ok := other.m[k]; !ok {
115			result.m[k] = v
116		}
117	}
118
119	return result
120}
121
122// Intersection performs the set intersection of the two sets
123// and returns a new third set.
124func (s *Set) Intersection(other *Set) *Set {
125	result := &Set{F: s.F}
126	result.once.Do(result.init)
127
128	for k, v := range s.m {
129		if _, ok := other.m[k]; ok {
130			result.m[k] = v
131		}
132	}
133
134	return result
135}
136
137// Union performs the set union of the two sets and returns a new third
138// set.
139func (s *Set) Union(other *Set) *Set {
140	result := &Set{F: s.F}
141	result.once.Do(result.init)
142
143	for k, v := range s.m {
144		result.m[k] = v
145	}
146	for k, v := range other.m {
147		result.m[k] = v
148	}
149
150	return result
151}
152
153func (s *Set) Equal(raw interface{}) bool {
154	other, ok := raw.(*Set)
155	if !ok {
156		return false
157	}
158
159	return reflect.DeepEqual(s.m, other.m)
160}
161
162// HashEqual simply checks to the keys the top-level map to the keys in the
163// other set's top-level map to see if they are equal. This obviously assumes
164// you have a properly working hash function - use HashResource if in doubt.
165func (s *Set) HashEqual(raw interface{}) bool {
166	other, ok := raw.(*Set)
167	if !ok {
168		return false
169	}
170
171	ks1 := make([]string, 0)
172	ks2 := make([]string, 0)
173
174	for k := range s.m {
175		ks1 = append(ks1, k)
176	}
177	for k := range other.m {
178		ks2 = append(ks2, k)
179	}
180
181	sort.Strings(ks1)
182	sort.Strings(ks2)
183
184	return reflect.DeepEqual(ks1, ks2)
185}
186
187func (s *Set) GoString() string {
188	return fmt.Sprintf("*Set(%#v)", s.m)
189}
190
191func (s *Set) init() {
192	s.m = make(map[string]interface{})
193}
194
195func (s *Set) add(item interface{}, computed bool) string {
196	s.once.Do(s.init)
197
198	code := s.hash(item)
199	if computed {
200		code = "~" + code
201
202		if isProto5() {
203			tmpCode := code
204			count := 0
205			for _, exists := s.m[tmpCode]; exists; _, exists = s.m[tmpCode] {
206				count++
207				tmpCode = fmt.Sprintf("%s%d", code, count)
208			}
209			code = tmpCode
210		}
211	}
212
213	if _, ok := s.m[code]; !ok {
214		s.m[code] = item
215	}
216
217	return code
218}
219
220func (s *Set) hash(item interface{}) string {
221	code := s.F(item)
222	// Always return a nonnegative hashcode.
223	if code < 0 {
224		code = -code
225	}
226	return strconv.Itoa(code)
227}
228
229func (s *Set) remove(item interface{}) string {
230	s.once.Do(s.init)
231
232	code := s.hash(item)
233	delete(s.m, code)
234
235	return code
236}
237
238func (s *Set) index(item interface{}) int {
239	return sort.SearchStrings(s.listCode(), s.hash(item))
240}
241
242func (s *Set) listCode() []string {
243	// Sort the hash codes so the order of the list is deterministic
244	keys := make([]string, 0, len(s.m))
245	for k := range s.m {
246		keys = append(keys, k)
247	}
248	sort.Sort(sort.StringSlice(keys))
249	return keys
250}
251