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 }