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 }