301 lines
7.3 KiB
Go
301 lines
7.3 KiB
Go
package orderedmap
|
|
|
|
// mostly from https://github.com/iancoleman/orderedmap
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/lbryio/lbry.go/extras/errors"
|
|
)
|
|
|
|
type keyIndex struct {
|
|
Key string
|
|
Index int
|
|
}
|
|
|
|
type byIndex []keyIndex
|
|
|
|
func (a byIndex) Len() int { return len(a) }
|
|
func (a byIndex) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
func (a byIndex) Less(i, j int) bool { return a[i].Index < a[j].Index }
|
|
|
|
type Map struct {
|
|
l sync.RWMutex
|
|
keys []string
|
|
values map[string]interface{}
|
|
}
|
|
|
|
func New() *Map {
|
|
o := Map{}
|
|
o.l = sync.RWMutex{}
|
|
o.keys = []string{}
|
|
o.values = map[string]interface{}{}
|
|
return &o
|
|
}
|
|
|
|
func (o *Map) Get(key string) (interface{}, bool) {
|
|
o.l.RLock()
|
|
defer o.l.RUnlock()
|
|
val, exists := o.values[key]
|
|
return val, exists
|
|
}
|
|
|
|
func (o *Map) Set(key string, value interface{}) {
|
|
o.l.Lock()
|
|
defer o.l.Unlock()
|
|
_, exists := o.values[key]
|
|
if !exists {
|
|
o.keys = append(o.keys, key)
|
|
}
|
|
o.values[key] = value
|
|
}
|
|
|
|
const outOfRange = "position value %d is outside of the range %d - %d"
|
|
|
|
//InsertAt This is a zero based position index: 0,1,2,3..n(from left) OR -1,-2,-3...-n(from right) where -1 is the last place.
|
|
func (o *Map) InsertAt(key string, value interface{}, position int) error {
|
|
o.l.Lock()
|
|
defer o.l.Unlock()
|
|
var index = position
|
|
if position < 0 {
|
|
// support indexing from the back: -1 = last in array.
|
|
index += len(o.keys) + 1
|
|
if index < 0 || index > len(o.keys) {
|
|
return errors.Err(fmt.Sprintf(outOfRange, position, len(o.keys), len(o.keys)-1))
|
|
}
|
|
} else if index > len(o.keys) {
|
|
return errors.Err(fmt.Sprintf(outOfRange, position, len(o.keys), len(o.keys)-1))
|
|
}
|
|
_, exists := o.values[key]
|
|
if !exists {
|
|
// left + key + right
|
|
o.keys = append(o.keys[0:index], append([]string{key}, o.keys[index:]...)...)
|
|
}
|
|
o.values[key] = value
|
|
|
|
return nil
|
|
}
|
|
|
|
func (o *Map) Prepend(key string, value interface{}) {
|
|
o.l.Lock()
|
|
defer o.l.Unlock()
|
|
_, exists := o.values[key]
|
|
if !exists {
|
|
o.keys = append([]string{key}, o.keys...)
|
|
}
|
|
o.values[key] = value
|
|
}
|
|
|
|
func (o *Map) Delete(key string) {
|
|
o.l.Lock()
|
|
defer o.l.Unlock()
|
|
// check key is in use
|
|
_, ok := o.values[key]
|
|
if !ok {
|
|
return
|
|
}
|
|
// remove from keys
|
|
for i, k := range o.keys {
|
|
if k == key {
|
|
o.keys = append(o.keys[:i], o.keys[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
// remove from values
|
|
delete(o.values, key)
|
|
}
|
|
|
|
func (o *Map) Keys() []string {
|
|
o.l.RLock()
|
|
defer o.l.RUnlock()
|
|
return o.keys
|
|
}
|
|
|
|
func (o *Map) UnmarshalJSON(b []byte) error {
|
|
o.l.Lock()
|
|
defer o.l.Unlock()
|
|
m := map[string]interface{}{}
|
|
if err := json.Unmarshal(b, &m); err != nil {
|
|
return errors.Err(err)
|
|
}
|
|
s := string(b)
|
|
mapToOrderedMap(o, s, m)
|
|
return nil
|
|
}
|
|
|
|
func mapToOrderedMap(o *Map, s string, m map[string]interface{}) {
|
|
// Get the order of the keys
|
|
orderedKeys := []keyIndex{}
|
|
for k := range m {
|
|
kEscaped := strings.Replace(k, `"`, `\"`, -1)
|
|
kQuoted := `"` + kEscaped + `"`
|
|
// Find how much content exists before this key.
|
|
// If all content from this key and after is replaced with a close
|
|
// brace, it should still form a valid json string.
|
|
sTrimmed := s
|
|
for len(sTrimmed) > 0 {
|
|
lastIndex := strings.LastIndex(sTrimmed, kQuoted)
|
|
if lastIndex == -1 {
|
|
break
|
|
}
|
|
sTrimmed = sTrimmed[0:lastIndex]
|
|
sTrimmed = strings.TrimSpace(sTrimmed)
|
|
if len(sTrimmed) > 0 && sTrimmed[len(sTrimmed)-1] == ',' {
|
|
sTrimmed = sTrimmed[0 : len(sTrimmed)-1]
|
|
}
|
|
maybeValidJson := sTrimmed + "}"
|
|
testMap := map[string]interface{}{}
|
|
err := json.Unmarshal([]byte(maybeValidJson), &testMap)
|
|
if err == nil {
|
|
// record the position of this key in s
|
|
ki := keyIndex{
|
|
Key: k,
|
|
Index: len(sTrimmed),
|
|
}
|
|
orderedKeys = append(orderedKeys, ki)
|
|
// shorten the string to get the next key
|
|
startOfValueIndex := lastIndex + len(kQuoted)
|
|
valueStr := s[startOfValueIndex : len(s)-1]
|
|
valueStr = strings.TrimSpace(valueStr)
|
|
if len(valueStr) > 0 && valueStr[0] == ':' {
|
|
valueStr = valueStr[1:]
|
|
}
|
|
valueStr = strings.TrimSpace(valueStr)
|
|
if valueStr[0] == '{' {
|
|
// if the value for this key is a map, convert it to an orderedmap.
|
|
// find end of valueStr by removing everything after last }
|
|
// until it forms valid json
|
|
hasValidJson := false
|
|
i := 1
|
|
for i < len(valueStr) && !hasValidJson {
|
|
if valueStr[i] != '}' {
|
|
i = i + 1
|
|
continue
|
|
}
|
|
subTestMap := map[string]interface{}{}
|
|
testValue := valueStr[0 : i+1]
|
|
err = json.Unmarshal([]byte(testValue), &subTestMap)
|
|
if err == nil {
|
|
hasValidJson = true
|
|
valueStr = testValue
|
|
break
|
|
}
|
|
i = i + 1
|
|
}
|
|
// convert to orderedmap
|
|
if hasValidJson {
|
|
mkTyped := m[k].(map[string]interface{})
|
|
oo := &Map{}
|
|
mapToOrderedMap(oo, valueStr, mkTyped)
|
|
m[k] = oo
|
|
}
|
|
} else if valueStr[0] == '[' {
|
|
// if the value for this key is a []interface, convert any map items to an orderedmap.
|
|
// find end of valueStr by removing everything after last ]
|
|
// until it forms valid json
|
|
hasValidJson := false
|
|
i := 1
|
|
for i < len(valueStr) && !hasValidJson {
|
|
if valueStr[i] != ']' {
|
|
i = i + 1
|
|
continue
|
|
}
|
|
subTestSlice := []interface{}{}
|
|
testValue := valueStr[0 : i+1]
|
|
err = json.Unmarshal([]byte(testValue), &subTestSlice)
|
|
if err == nil {
|
|
hasValidJson = true
|
|
valueStr = testValue
|
|
break
|
|
}
|
|
i = i + 1
|
|
}
|
|
if hasValidJson {
|
|
itemsStr := valueStr[1 : len(valueStr)-1]
|
|
// get next item in the slice
|
|
itemIndex := 0
|
|
startItem := 0
|
|
endItem := 0
|
|
for endItem < len(itemsStr) {
|
|
if itemsStr[endItem] != ',' && endItem < len(itemsStr)-1 {
|
|
endItem = endItem + 1
|
|
continue
|
|
}
|
|
// if this substring compiles to json, it's the next item
|
|
possibleItemStr := strings.TrimSpace(itemsStr[startItem:endItem])
|
|
var possibleItem interface{}
|
|
err = json.Unmarshal([]byte(possibleItemStr), &possibleItem)
|
|
if err != nil {
|
|
endItem = endItem + 1
|
|
continue
|
|
}
|
|
// if item is map, convert to orderedmap
|
|
if possibleItemStr[0] == '{' {
|
|
mkTyped := m[k].([]interface{})
|
|
mkiTyped := mkTyped[itemIndex].(map[string]interface{})
|
|
oo := &Map{}
|
|
mapToOrderedMap(oo, possibleItemStr, mkiTyped)
|
|
// replace original map with orderedmap
|
|
mkTyped[itemIndex] = oo
|
|
m[k] = mkTyped
|
|
}
|
|
// remove this item from itemsStr
|
|
startItem = endItem + 1
|
|
endItem = endItem + 1
|
|
itemIndex = itemIndex + 1
|
|
}
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
// Sort the keys
|
|
sort.Sort(byIndex(orderedKeys))
|
|
// Convert sorted keys to string slice
|
|
k := []string{}
|
|
for _, ki := range orderedKeys {
|
|
k = append(k, ki.Key)
|
|
}
|
|
// Set the Map values
|
|
o.values = m
|
|
o.keys = k
|
|
}
|
|
|
|
func (o *Map) Copy() *Map {
|
|
new := New()
|
|
|
|
for _, k := range o.keys {
|
|
v, _ := o.Get(k)
|
|
new.Set(k, v)
|
|
}
|
|
|
|
return new
|
|
}
|
|
|
|
func (o *Map) MarshalJSON() ([]byte, error) {
|
|
o.l.RLock()
|
|
defer o.l.RUnlock()
|
|
s := "{"
|
|
for _, k := range o.keys {
|
|
// add key
|
|
kEscaped := strings.Replace(k, `"`, `\"`, -1)
|
|
s = s + `"` + kEscaped + `":`
|
|
// add value
|
|
v := o.values[k]
|
|
vBytes, err := json.Marshal(v)
|
|
if err != nil {
|
|
return []byte{}, errors.Err(err)
|
|
}
|
|
s = s + string(vBytes) + ","
|
|
}
|
|
if len(o.keys) > 0 {
|
|
s = s[0 : len(s)-1]
|
|
}
|
|
s = s + "}"
|
|
return []byte(s), nil
|
|
}
|