171 lines
4.2 KiB
Go
171 lines
4.2 KiB
Go
package plist
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// typeInfo holds details for the plist representation of a type.
|
|
type typeInfo struct {
|
|
fields []fieldInfo
|
|
}
|
|
|
|
// fieldInfo holds details for the plist representation of a single field.
|
|
type fieldInfo struct {
|
|
idx []int
|
|
name string
|
|
omitEmpty bool
|
|
}
|
|
|
|
var tinfoMap = make(map[reflect.Type]*typeInfo)
|
|
var tinfoLock sync.RWMutex
|
|
|
|
// getTypeInfo returns the typeInfo structure with details necessary
|
|
// for marshalling and unmarshalling typ.
|
|
func getTypeInfo(typ reflect.Type) (*typeInfo, error) {
|
|
tinfoLock.RLock()
|
|
tinfo, ok := tinfoMap[typ]
|
|
tinfoLock.RUnlock()
|
|
if ok {
|
|
return tinfo, nil
|
|
}
|
|
tinfo = &typeInfo{}
|
|
if typ.Kind() == reflect.Struct {
|
|
n := typ.NumField()
|
|
for i := 0; i < n; i++ {
|
|
f := typ.Field(i)
|
|
if f.PkgPath != "" || f.Tag.Get("plist") == "-" {
|
|
continue // Private field
|
|
}
|
|
|
|
// For embedded structs, embed its fields.
|
|
if f.Anonymous {
|
|
t := f.Type
|
|
if t.Kind() == reflect.Ptr {
|
|
t = t.Elem()
|
|
}
|
|
if t.Kind() == reflect.Struct {
|
|
inner, err := getTypeInfo(t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, finfo := range inner.fields {
|
|
finfo.idx = append([]int{i}, finfo.idx...)
|
|
if err := addFieldInfo(typ, tinfo, &finfo); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
|
|
finfo, err := structFieldInfo(typ, &f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Add the field if it doesn't conflict with other fields.
|
|
if err := addFieldInfo(typ, tinfo, finfo); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
tinfoLock.Lock()
|
|
tinfoMap[typ] = tinfo
|
|
tinfoLock.Unlock()
|
|
return tinfo, nil
|
|
}
|
|
|
|
// structFieldInfo builds and returns a fieldInfo for f.
|
|
func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) {
|
|
finfo := &fieldInfo{idx: f.Index}
|
|
|
|
// Split the tag from the xml namespace if necessary.
|
|
tag := f.Tag.Get("plist")
|
|
|
|
// Parse flags.
|
|
tokens := strings.Split(tag, ",")
|
|
tag = tokens[0]
|
|
if len(tokens) > 1 {
|
|
tag = tokens[0]
|
|
for _, flag := range tokens[1:] {
|
|
switch flag {
|
|
case "omitempty":
|
|
finfo.omitEmpty = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if tag == "" {
|
|
// If the name part of the tag is completely empty,
|
|
// use the field name
|
|
finfo.name = f.Name
|
|
return finfo, nil
|
|
}
|
|
|
|
finfo.name = tag
|
|
return finfo, nil
|
|
}
|
|
|
|
// addFieldInfo adds finfo to tinfo.fields if there are no
|
|
// conflicts, or if conflicts arise from previous fields that were
|
|
// obtained from deeper embedded structures than finfo. In the latter
|
|
// case, the conflicting entries are dropped.
|
|
// A conflict occurs when the path (parent + name) to a field is
|
|
// itself a prefix of another path, or when two paths match exactly.
|
|
// It is okay for field paths to share a common, shorter prefix.
|
|
func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error {
|
|
var conflicts []int
|
|
// First, figure all conflicts. Most working code will have none.
|
|
for i := range tinfo.fields {
|
|
oldf := &tinfo.fields[i]
|
|
if newf.name == oldf.name {
|
|
conflicts = append(conflicts, i)
|
|
}
|
|
}
|
|
|
|
// Without conflicts, add the new field and return.
|
|
if conflicts == nil {
|
|
tinfo.fields = append(tinfo.fields, *newf)
|
|
return nil
|
|
}
|
|
|
|
// If any conflict is shallower, ignore the new field.
|
|
// This matches the Go field resolution on embedding.
|
|
for _, i := range conflicts {
|
|
if len(tinfo.fields[i].idx) < len(newf.idx) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Otherwise, the new field is shallower, and thus takes precedence,
|
|
// so drop the conflicting fields from tinfo and append the new one.
|
|
for c := len(conflicts) - 1; c >= 0; c-- {
|
|
i := conflicts[c]
|
|
copy(tinfo.fields[i:], tinfo.fields[i+1:])
|
|
tinfo.fields = tinfo.fields[:len(tinfo.fields)-1]
|
|
}
|
|
tinfo.fields = append(tinfo.fields, *newf)
|
|
return nil
|
|
}
|
|
|
|
// value returns v's field value corresponding to finfo.
|
|
// It's equivalent to v.FieldByIndex(finfo.idx), but initializes
|
|
// and dereferences pointers as necessary.
|
|
func (finfo *fieldInfo) value(v reflect.Value) reflect.Value {
|
|
for i, x := range finfo.idx {
|
|
if i > 0 {
|
|
t := v.Type()
|
|
if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
|
|
if v.IsNil() {
|
|
v.Set(reflect.New(v.Type().Elem()))
|
|
}
|
|
v = v.Elem()
|
|
}
|
|
}
|
|
v = v.Field(x)
|
|
}
|
|
return v
|
|
}
|