github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/set.go (about)

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