github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/set.go (about)

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