package schema import ( "bytes" "fmt" "reflect" "sort" "strconv" "sync" "github.com/hashicorp/terraform/internal/legacy/helper/hashcode" ) // HashString hashes strings. If you want a Set of strings, this is the // SchemaSetFunc you want. func HashString(v interface{}) int { return hashcode.String(v.(string)) } // HashInt hashes integers. If you want a Set of integers, this is the // SchemaSetFunc you want. func HashInt(v interface{}) int { return hashcode.String(strconv.Itoa(v.(int))) } // HashResource hashes complex structures that are described using // a *Resource. This is the default set implementation used when a set's // element type is a full resource. func HashResource(resource *Resource) SchemaSetFunc { return func(v interface{}) int { var buf bytes.Buffer SerializeResourceForHash(&buf, v, resource) return hashcode.String(buf.String()) } } // HashSchema hashes values that are described using a *Schema. This is the // default set implementation used when a set's element type is a single // schema. func HashSchema(schema *Schema) SchemaSetFunc { return func(v interface{}) int { var buf bytes.Buffer SerializeValueForHash(&buf, v, schema) return hashcode.String(buf.String()) } } // Set is a set data structure that is returned for elements of type // TypeSet. type Set struct { F SchemaSetFunc m map[string]interface{} once sync.Once } // NewSet is a convenience method for creating a new set with the given // items. func NewSet(f SchemaSetFunc, items []interface{}) *Set { s := &Set{F: f} for _, i := range items { s.Add(i) } return s } // CopySet returns a copy of another set. func CopySet(otherSet *Set) *Set { return NewSet(otherSet.F, otherSet.List()) } // Add adds an item to the set if it isn't already in the set. func (s *Set) Add(item interface{}) { s.add(item, false) } // Remove removes an item if it's already in the set. Idempotent. func (s *Set) Remove(item interface{}) { s.remove(item) } // Contains checks if the set has the given item. func (s *Set) Contains(item interface{}) bool { _, ok := s.m[s.hash(item)] return ok } // Len returns the amount of items in the set. func (s *Set) Len() int { return len(s.m) } // List returns the elements of this set in slice format. // // The order of the returned elements is deterministic. Given the same // set, the order of this will always be the same. func (s *Set) List() []interface{} { result := make([]interface{}, len(s.m)) for i, k := range s.listCode() { result[i] = s.m[k] } return result } // Difference performs a set difference of the two sets, returning // a new third set that has only the elements unique to this set. func (s *Set) Difference(other *Set) *Set { result := &Set{F: s.F} result.once.Do(result.init) for k, v := range s.m { if _, ok := other.m[k]; !ok { result.m[k] = v } } return result } // Intersection performs the set intersection of the two sets // and returns a new third set. func (s *Set) Intersection(other *Set) *Set { result := &Set{F: s.F} result.once.Do(result.init) for k, v := range s.m { if _, ok := other.m[k]; ok { result.m[k] = v } } return result } // Union performs the set union of the two sets and returns a new third // set. func (s *Set) Union(other *Set) *Set { result := &Set{F: s.F} result.once.Do(result.init) for k, v := range s.m { result.m[k] = v } for k, v := range other.m { result.m[k] = v } return result } func (s *Set) Equal(raw interface{}) bool { other, ok := raw.(*Set) if !ok { return false } return reflect.DeepEqual(s.m, other.m) } // HashEqual simply checks to the keys the top-level map to the keys in the // other set's top-level map to see if they are equal. This obviously assumes // you have a properly working hash function - use HashResource if in doubt. func (s *Set) HashEqual(raw interface{}) bool { other, ok := raw.(*Set) if !ok { return false } ks1 := make([]string, 0) ks2 := make([]string, 0) for k := range s.m { ks1 = append(ks1, k) } for k := range other.m { ks2 = append(ks2, k) } sort.Strings(ks1) sort.Strings(ks2) return reflect.DeepEqual(ks1, ks2) } func (s *Set) GoString() string { return fmt.Sprintf("*Set(%#v)", s.m) } func (s *Set) init() { s.m = make(map[string]interface{}) } func (s *Set) add(item interface{}, computed bool) string { s.once.Do(s.init) code := s.hash(item) if computed { code = "~" + code if isProto5() { tmpCode := code count := 0 for _, exists := s.m[tmpCode]; exists; _, exists = s.m[tmpCode] { count++ tmpCode = fmt.Sprintf("%s%d", code, count) } code = tmpCode } } if _, ok := s.m[code]; !ok { s.m[code] = item } return code } func (s *Set) hash(item interface{}) string { code := s.F(item) // Always return a nonnegative hashcode. if code < 0 { code = -code } return strconv.Itoa(code) } func (s *Set) remove(item interface{}) string { s.once.Do(s.init) code := s.hash(item) delete(s.m, code) return code } func (s *Set) index(item interface{}) int { return sort.SearchStrings(s.listCode(), s.hash(item)) } func (s *Set) listCode() []string { // Sort the hash codes so the order of the list is deterministic keys := make([]string, 0, len(s.m)) for k := range s.m { keys = append(keys, k) } sort.Sort(sort.StringSlice(keys)) return keys }