-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathenum.go
225 lines (198 loc) · 5.61 KB
/
enum.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
package goenum
import (
"encoding"
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
)
type EnumDefinition interface {
fmt.Stringer
json.Marshaler
encoding.TextMarshaler
// Name The name of the enumeration. The enumeration names of the same Type must be unique.
Name() string
// Equals Compare with another enumeration. Only return true if both Type and Name are the same
Equals(other EnumDefinition) bool
// Type String representation of enumeration type
Type() string
// Ordinal Get the ordinal of the enumeration, starting from zero and increasing in declared order.
Ordinal() int
// Compare -Compare with the ordinal value of another enumeration
Compare(other EnumDefinition) int
}
type Enum struct {
name string
_type string
index int
}
func (e Enum) Name() string {
return e.name
}
func (e Enum) Equals(other EnumDefinition) bool {
if e.Name() != other.Name() {
return false
}
return e._type == other.Type()
}
func (e Enum) String() string {
return e.Name()
}
func (e Enum) Ordinal() int {
return e.index
}
func (e Enum) Type() string {
return e._type
}
func (e Enum) Compare(other EnumDefinition) int {
return e.Ordinal() - other.Ordinal()
}
func (e Enum) MarshalJSON() ([]byte, error) {
return json.Marshal(e.Name())
}
func (e Enum) MarshalText() (text []byte, err error) {
return []byte(e.Name()), nil
}
// name2enumsMap Name to enumeration instance mapping.
// Value uses slice to store enumeration instances with different types but conflicting names
var name2enumsMap = make(map[string][]EnumDefinition)
// type2enumsMap The mapping from enumeration type to all enumerations,
// where key is the string representation of the enumeration type
var type2enumsMap = make(map[string][]EnumDefinition)
// typeIndexMap Store instance counters of different enumeration types for calculating Ordinal.
var typeIndexMap = make(map[string]int)
// NewEnum Create a new enumeration. If an enumeration instance with the same Type and Name already exists,
// the current method will throw a panic to prevent duplicate enumeration creation
func NewEnum[T EnumDefinition](name string, src ...T) T {
if IsValidEnum[T](name) {
panic("Enum must be unique")
}
var t T
if len(src) > 0 {
t = src[0]
}
v := reflect.ValueOf(t)
tFullName := typeKey(v.Type())
isPtr := v.Kind() == reflect.Ptr
if isPtr {
if v.IsNil() {
v = reflect.New(v.Type().Elem())
}
} else {
v = reflect.ValueOf(&t)
}
elem := reflect.Indirect(v)
enumFiled := elem.FieldByName(reflect.TypeOf(Enum{}).Name())
idx := typeIndexMap[tFullName]
e := Enum{name: name, _type: tFullName, index: idx}
if enumFiled.Kind() == reflect.Ptr {
enumFiled.Set(reflect.ValueOf(&e))
} else {
enumFiled.Set(reflect.ValueOf(e))
}
typeIndexMap[tFullName] = idx + 1
if isPtr {
t = v.Interface().(T)
} else {
t = reflect.Indirect(v).Interface().(T)
}
type2enumsMap[tFullName] = append(type2enumsMap[tFullName], t)
name2enumsMap[name] = append(name2enumsMap[name], t)
return t
}
// ValueOf Find an enumeration instance based on the string, and return a zero value if not found
func ValueOf[T EnumDefinition](name string) (t T, valid bool) {
enums := name2enumsMap[name]
for _, e := range enums {
if v, ok := e.(T); ok {
return v, true
}
}
return
}
// ValueOfIgnoreCase Ignoring case to obtain enumeration instances.
// Note: This method involves one reflection call,
// and its performance is slightly worse than the ValueOf method
func ValueOfIgnoreCase[T EnumDefinition](name string) (t T, valid bool) {
values := Values[T]()
for _, e := range values {
if strings.EqualFold(e.Name(), name) {
return e, true
}
}
return
}
// Unmarshal Deserialize the enumeration instance from the JSON string,
// and return an error if not found
func Unmarshal[T EnumDefinition](data []byte) (t T, err error) {
var name string
err = json.Unmarshal(data, &name)
if err != nil {
return
}
t, valid := ValueOf[T](name)
if !valid {
return t, errors.New("enum not found")
}
return
}
// Values Return all enumeration instances. The returned slice are sorted by ordinal
func Values[T EnumDefinition]() []T {
var t T
var res []T
tName := typeKey(reflect.TypeOf(t))
for _, e := range type2enumsMap[tName] {
if v, ok := e.(T); ok {
res = append(res, v)
}
}
return res
}
// Size Number of instances of specified enumeration type
func Size[T EnumDefinition]() int {
var t T
tName := typeKey(reflect.TypeOf(t))
return len(type2enumsMap[tName])
}
// GetEnumMap Get all enumeration instances of the specified type.
// The key is the Name of the enumeration instance, and the value is the enumeration instance.
func GetEnumMap[T EnumDefinition]() map[string]T {
values := Values[T]()
res := make(map[string]T)
for _, e := range values {
res[e.Name()] = e
}
return res
}
// EnumNames Get the names of a batch of enumerations. If no instances are passed in,
// return the Name value of all enumeration instances of the type specified by the generic parameter.
func EnumNames[T EnumDefinition](enums ...T) (names []string) {
if len(enums) == 0 {
enums = Values[T]()
}
for _, e := range enums {
names = append(names, e.Name())
}
return
}
// GetEnums Obtain a batch of enumeration instances based on the enumeration name list
func GetEnums[T EnumDefinition](names ...string) (res []T, valid bool) {
for _, n := range names {
t, valid := ValueOf[T](n)
if !valid {
return nil, false
}
res = append(res, t)
}
valid = true
return
}
// IsValidEnum Determine if the incoming string is a valid enumeration
func IsValidEnum[T EnumDefinition](name string) (valid bool) {
_, valid = ValueOf[T](name)
return
}
func typeKey(t reflect.Type) string {
return t.String()
}