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