github.com/Aoi-hosizora/ahlib@v1.5.1-0.20230404072829-241b93cf91c7/xorderedmap/xorderedmap.go (about)

     1  package xorderedmap
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"github.com/Aoi-hosizora/ahlib/xreflect"
     8  	"reflect"
     9  	"strings"
    10  	"sync"
    11  	_ "unsafe"
    12  )
    13  
    14  // OrderedMap represents a map whose keys are in ordered, it is implemented by go map to store kv data, and slice to store
    15  // key order. Note that this is safe for concurrent use.
    16  type OrderedMap struct {
    17  	// kv represents the inner dictionary
    18  	kv map[string]interface{}
    19  
    20  	// keys represents the inner key list in ordered
    21  	keys []string
    22  
    23  	// mu locks kv and keys
    24  	mu sync.RWMutex
    25  }
    26  
    27  // New creates an empty OrderedMap.
    28  func New() *OrderedMap {
    29  	return &OrderedMap{
    30  		kv:   make(map[string]interface{}, 0),
    31  		keys: make([]string, 0),
    32  	}
    33  }
    34  
    35  // NewWithCap creates an empty OrderedMap with given capacity.
    36  func NewWithCap(c int) *OrderedMap {
    37  	return &OrderedMap{
    38  		kv:   make(map[string]interface{}, c),
    39  		keys: make([]string, 0, c),
    40  	}
    41  }
    42  
    43  // Keys returns the keys in ordered.
    44  func (l *OrderedMap) Keys() []string {
    45  	l.mu.RLock()
    46  	keys := make([]string, len(l.keys))
    47  	copy(keys, l.keys)
    48  	l.mu.RUnlock()
    49  	return keys
    50  }
    51  
    52  // Values returns the values in ordered.
    53  func (l *OrderedMap) Values() []interface{} {
    54  	l.mu.RLock()
    55  	values := make([]interface{}, len(l.keys))
    56  	for idx, key := range l.keys {
    57  		values[idx] = l.kv[key]
    58  	}
    59  	l.mu.RUnlock()
    60  	return values
    61  }
    62  
    63  // Len returns the length of OrderedMap.
    64  func (l *OrderedMap) Len() int {
    65  	l.mu.RLock()
    66  	length := len(l.keys)
    67  	l.mu.RUnlock()
    68  	return length
    69  }
    70  
    71  // Set sets a key-value pair to OrderedMap. Note that it does not change the order for the existed key.
    72  func (l *OrderedMap) Set(key string, value interface{}) {
    73  	l.mu.Lock()
    74  	_, exist := l.kv[key]
    75  	l.kv[key] = value
    76  	if !exist {
    77  		l.keys = append(l.keys, key)
    78  	}
    79  	l.mu.Unlock()
    80  }
    81  
    82  // Has returns true if key exists.
    83  func (l *OrderedMap) Has(key string) bool {
    84  	l.mu.RLock()
    85  	_, exist := l.kv[key]
    86  	l.mu.RUnlock()
    87  	return exist
    88  }
    89  
    90  // Get returns the value by key, returns false if the key not found.
    91  func (l *OrderedMap) Get(key string) (interface{}, bool) {
    92  	l.mu.RLock()
    93  	value, exist := l.kv[key]
    94  	l.mu.RUnlock()
    95  	return value, exist
    96  }
    97  
    98  // GetOr returns the value by key, returns defaultValue if the key not found.
    99  func (l *OrderedMap) GetOr(key string, defaultValue interface{}) interface{} {
   100  	l.mu.RLock()
   101  	value, exist := l.kv[key]
   102  	l.mu.RUnlock()
   103  	if !exist {
   104  		return defaultValue
   105  	}
   106  	return value
   107  }
   108  
   109  const (
   110  	panicKeyNotFound = "xorderedmap: key `%s` not found"
   111  )
   112  
   113  // MustGet returns the value by key, panics if the key not found.
   114  func (l *OrderedMap) MustGet(key string) interface{} {
   115  	l.mu.RLock()
   116  	value, exist := l.kv[key]
   117  	l.mu.RUnlock()
   118  	if !exist {
   119  		panic(fmt.Sprintf(panicKeyNotFound, key))
   120  	}
   121  	return value
   122  }
   123  
   124  // Remove removes the key-value pair by key, returns false if the key not found.
   125  func (l *OrderedMap) Remove(key string) (interface{}, bool) {
   126  	l.mu.Lock()
   127  	defer l.mu.Unlock()
   128  
   129  	value, exist := l.kv[key]
   130  	if !exist {
   131  		return value, false
   132  	}
   133  	delete(l.kv, key)
   134  
   135  	targetIdx := -1
   136  	for idx, k := range l.keys {
   137  		if k == key {
   138  			targetIdx = idx
   139  			break
   140  		}
   141  	}
   142  	if targetIdx != -1 {
   143  		if targetIdx == len(l.keys)-1 {
   144  			l.keys = l.keys[:targetIdx]
   145  		} else {
   146  			l.keys = append(l.keys[:targetIdx], l.keys[targetIdx+1:]...)
   147  		}
   148  	}
   149  
   150  	return value, true
   151  }
   152  
   153  // Clear clears the OrderedMap.
   154  func (l *OrderedMap) Clear() {
   155  	l.mu.Lock()
   156  	l.kv = make(map[string]interface{})
   157  	l.keys = make([]string, 0)
   158  	l.mu.Unlock()
   159  }
   160  
   161  // MarshalJSON marshals OrderedMap to json bytes.
   162  func (l *OrderedMap) MarshalJSON() ([]byte, error) {
   163  	l.mu.RLock()
   164  	defer l.mu.RUnlock()
   165  
   166  	tot := len(l.keys)
   167  	buf := &bytes.Buffer{}
   168  	buf.WriteRune('{')
   169  	for idx, field := range l.keys {
   170  		bs, err := json.Marshal(l.kv[field])
   171  		if err != nil {
   172  			return []byte{}, err
   173  		}
   174  		buf.WriteRune('"')
   175  		buf.WriteString(field)
   176  		buf.WriteString(`":`)
   177  		buf.Write(bs) // "%s":%s
   178  		if idx < tot-1 {
   179  			buf.WriteRune(',')
   180  		}
   181  	}
   182  	buf.WriteRune('}')
   183  
   184  	return buf.Bytes(), nil
   185  }
   186  
   187  // CreateYamlMapSliceFunc represents a function which is used to create a yaml.MapSlice from a slice of kv pair (of
   188  // [2]interface{} type), and it is used in OrderedMap.MarshalYAML. This can make OrderedMap support to marshal it to
   189  // ordered yaml document. For more details, please visit https://blog.labix.org/2014/09/22/announcing-yaml-v2-for-go
   190  // and https://github.com/go-yaml/yaml/issues/30#issuecomment-56246239.
   191  //
   192  // Example:
   193  // 	// import "gopkg.in/yaml.v2"
   194  // 	xorderedmap.CreateYamlMapSliceFunc = func(kvPairs [][2]interface{}) (interface{}, error) {
   195  // 		slice := yaml.MapSlice{}
   196  // 		for _, pair := range kvPairs {
   197  // 			slice = append(slice, yaml.MapItem{Key: pair[0], Value: pair[1]})
   198  // 		}
   199  // 		return slice, nil
   200  // 	}
   201  var CreateYamlMapSliceFunc func(kvPairs [][2]interface{}) (interface{}, error)
   202  
   203  // MarshalYAML marshals the current OrderedMap to yaml supported object, you have to set CreateYamlMapSliceFunc before
   204  // use MarshalYAML, otherwise the yaml.Marshal function will marshal to a map that is in no order. For more details,
   205  // please visit xorderedmap.CreateYamlMapSliceFunc.
   206  func (l *OrderedMap) MarshalYAML() (interface{}, error) {
   207  	if CreateYamlMapSliceFunc == nil {
   208  		l.mu.RLock()
   209  		m := make(map[string]interface{}, len(l.kv))
   210  		for k, v := range l.kv {
   211  			m[k] = v
   212  		}
   213  		l.mu.RUnlock()
   214  		return m, nil
   215  	}
   216  
   217  	l.mu.RLock()
   218  	kvPairs := make([][2]interface{}, 0, len(l.kv))
   219  	for _, k := range l.keys {
   220  		kvPairs = append(kvPairs, [2]interface{}{k, l.kv[k]})
   221  	}
   222  	l.mu.RUnlock()
   223  	return CreateYamlMapSliceFunc(kvPairs)
   224  }
   225  
   226  // String returns the string in json format.
   227  func (l *OrderedMap) String() string {
   228  	buf, err := l.MarshalJSON()
   229  	if err != nil {
   230  		return ""
   231  	}
   232  	return string(buf)
   233  }
   234  
   235  const (
   236  	panicNilObject       = "xorderedmap: nil object"
   237  	panicNonStructObject = "xorderedmap: non-struct or non-struct-pointer object"
   238  )
   239  
   240  // FromInterface creates an OrderedMap from a struct (with json tag), panics if using nil or non-struct object.
   241  func FromInterface(object interface{}) *OrderedMap {
   242  	if object == nil {
   243  		panic(panicNilObject)
   244  	}
   245  	typ := reflect.TypeOf(object)
   246  	val := reflect.ValueOf(object)
   247  	if typ.Kind() == reflect.Ptr {
   248  		typ = typ.Elem()
   249  		val = val.Elem()
   250  	}
   251  	if typ.Kind() != reflect.Struct {
   252  		panic(panicNonStructObject)
   253  	}
   254  
   255  	om := NewWithCap(typ.NumField())
   256  	for i := 0; i < typ.NumField(); i++ {
   257  		field := typ.Field(i)
   258  		tag := field.Tag.Get("json")
   259  		if tag == "" {
   260  			tag = field.Name
   261  		}
   262  		sp := strings.SplitN(tag, ",", 2)
   263  		omitempty := len(sp) >= 2 && strings.TrimSpace(sp[1]) == "omitempty" // ignore null
   264  
   265  		// use json tag value as key name
   266  		key := strings.TrimSpace(sp[0])
   267  		value := val.Field(i).Interface()
   268  		if key != "-" && (!omitempty || !xreflect.IsEmptyValue(value)) {
   269  			om.Set(key, value)
   270  		}
   271  	}
   272  
   273  	return om
   274  }