コード例 #1
0
ファイル: convert.go プロジェクト: jcf/go-osx-plist
// we shouldn't ever get an error from this, but I'd rather not panic
func convertCFTypeToInterface(cfType cfTypeRef) (interface{}, error) {
	typeId := C.CFGetTypeID(C.CFTypeRef(cfType))
	switch typeId {
	case C.CFStringGetTypeID():
		return convertCFStringToString(C.CFStringRef(cfType)), nil
	case C.CFNumberGetTypeID():
		return convertCFNumberToInterface(C.CFNumberRef(cfType)), nil
	case C.CFBooleanGetTypeID():
		return convertCFBooleanToBool(C.CFBooleanRef(cfType)), nil
	case C.CFDataGetTypeID():
		return convertCFDataToBytes(C.CFDataRef(cfType)), nil
	case C.CFDateGetTypeID():
		return convertCFDateToTime(C.CFDateRef(cfType)), nil
	case C.CFArrayGetTypeID():
		ary, err := convertCFArrayToSlice(C.CFArrayRef(cfType))
		return ary, err
	case C.CFDictionaryGetTypeID():
		dict, err := convertCFDictionaryToMap(C.CFDictionaryRef(cfType))
		return dict, err
	}
	return nil, &UnknownCFTypeError{typeId}
}
コード例 #2
0
ファイル: marshal.go プロジェクト: kballard/go-osx-plist
func (state *unmarshalState) unmarshalValue(cfObj cfTypeRef, v reflect.Value) error {
	vType := v.Type()
	var unmarshaler Unmarshaler
	if u, ok := v.Interface().(Unmarshaler); ok {
		unmarshaler = u
	} else if vType.Kind() != reflect.Ptr && vType.Name() != "" && v.CanAddr() {
		// matching the encoding/json behavior here
		// If v is a named type and is addressable, check its address for Unmarshaler.
		vA := v.Addr()
		if u, ok := vA.Interface().(Unmarshaler); ok {
			unmarshaler = u
		}
	}
	if unmarshaler != nil {
		// flip over to the dumb conversion routine so we have something to give UnmarshalPlist()
		plist, err := convertCFTypeToInterface(cfObj)
		if err != nil {
			return err
		}
		if vType.Kind() == reflect.Ptr && v.IsNil() {
			v.Set(reflect.New(vType.Elem()))
			unmarshaler = v.Interface().(Unmarshaler)
		}
		return unmarshaler.UnmarshalPlist(plist)
	}
	if vType.Kind() == reflect.Ptr {
		if v.IsNil() {
			v.Set(reflect.New(vType.Elem()))
		}
		return state.unmarshalValue(cfObj, v.Elem())
	}
	typeID := C.CFGetTypeID(C.CFTypeRef(cfObj))
	vSetter := v      // receiver of any Set* calls
	vAddr := v.Addr() // used for re-setting v for maps/slices
	if vType.Kind() == reflect.Interface {
		if v.IsNil() {
			// pick an appropriate type based on the cfobj
			var typ reflect.Type
			if typeID == cfNumberTypeID {
				typ = cfNumberTypeToType(C.CFNumberGetType(C.CFNumberRef(cfObj)))
			} else {
				var ok bool
				typ, ok = cfTypeMap[typeID]
				if !ok {
					return &UnknownCFTypeError{typeID}
				}
			}
			if !typ.AssignableTo(vType) {
				// v must be some interface that our object doesn't conform to
				state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType})
				return nil
			}
			vSetter.Set(reflect.Zero(typ))
		}
		vAddr = v
		v = v.Elem()
		vType = v.Type()
	}
	switch typeID {
	case cfArrayTypeID:
		if vType.Kind() != reflect.Slice && vType.Kind() != reflect.Array {
			state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType})
			return nil
		}
		return convertCFArrayToSliceHelper(C.CFArrayRef(cfObj), func(elem cfTypeRef, idx, count int) (bool, error) {
			if idx == 0 && vType.Kind() == reflect.Slice {
				vSetter.Set(reflect.MakeSlice(vType, count, count))
				v = vAddr.Elem()
			} else if vType.Kind() == reflect.Array && idx >= v.Len() {
				return false, nil
			}
			if err := state.unmarshalValue(elem, v.Index(idx)); err != nil {
				return false, err
			}
			return true, nil
		})
	case cfBooleanTypeID:
		if vType.Kind() != reflect.Bool {
			state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType})
			return nil
		}
		vSetter.Set(reflect.ValueOf(C.CFBooleanGetValue(C.CFBooleanRef(cfObj)) != C.false))
		return nil
	case cfDataTypeID:
		if !byteSliceType.AssignableTo(vType) {
			state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType})
			return nil
		}
		vSetter.Set(reflect.ValueOf(convertCFDataToBytes(C.CFDataRef(cfObj))))
		return nil
	case cfDateTypeID:
		if !timeType.AssignableTo(vType) {
			state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType})
			return nil
		}
		vSetter.Set(reflect.ValueOf(convertCFDateToTime(C.CFDateRef(cfObj))))
		return nil
	case cfDictionaryTypeID:
		if vType.Kind() == reflect.Map {
			// it's a map. Check its key type first
			if !stringType.AssignableTo(vType.Key()) {
				state.recordError(&UnmarshalTypeError{cfTypeNames[cfStringTypeID], vType.Key()})
				return nil
			}
			if v.IsNil() {
				vSetter.Set(reflect.MakeMap(vType))
				v = vAddr.Elem()
			}
			return convertCFDictionaryToMapHelper(C.CFDictionaryRef(cfObj), func(key string, value cfTypeRef, count int) error {
				keyVal := reflect.ValueOf(key)
				val := reflect.New(vType.Elem())
				if err := state.unmarshalValue(value, val); err != nil {
					return err
				}
				v.SetMapIndex(keyVal, val.Elem())
				return nil
			})
		} else if vType.Kind() == reflect.Struct {
			return convertCFDictionaryToMapHelper(C.CFDictionaryRef(cfObj), func(key string, value cfTypeRef, count int) error {
				// we need to iterate the fields because the tag might rename the key
				var f reflect.StructField
				var ok bool
				for i := 0; i < vType.NumField(); i++ {
					sf := vType.Field(i)
					tag := sf.Tag.Get("plist")
					if tag == "-" {
						// Pretend this field doesn't exist
						continue
					}
					if sf.Anonymous {
						// Match encoding/json's behavior here and pretend it doesn't exist
						continue
					}
					name, _ := parseTag(tag)
					if name == key {
						f = sf
						ok = true
						// This is unambiguously the right match
						break
					}
					if sf.Name == key {
						f = sf
						ok = true
					}
					// encoding/json does a case-insensitive match. Lets do that too
					if !ok && strings.EqualFold(sf.Name, key) {
						f = sf
						ok = true
					}
				}
				if ok {
					if f.PkgPath != "" {
						// this is an unexported field
						return &UnmarshalFieldError{key, vType, f}
					}
					vElem := v.FieldByIndex(f.Index)
					if err := state.unmarshalValue(value, vElem); err != nil {
						return err
					}
				}
				return nil
			})
		}
		state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType})
		return nil
	case cfNumberTypeID:
		switch vType.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			i := convertCFNumberToInt64(C.CFNumberRef(cfObj))
			if v.OverflowInt(i) {
				state.recordError(&UnmarshalTypeError{cfTypeNames[typeID] + " " + strconv.FormatInt(i, 10), vType})
				return nil
			}
			if vSetter.Kind() == reflect.Interface {
				vSetter.Set(reflect.ValueOf(i))
			} else {
				vSetter.SetInt(i)
			}
			return nil
		case reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			u := uint64(convertCFNumberToUInt32(C.CFNumberRef(cfObj)))
			if v.OverflowUint(u) {
				state.recordError(&UnmarshalTypeError{cfTypeNames[typeID] + " " + strconv.FormatUint(u, 10), vType})
				return nil
			}
			if vSetter.Kind() == reflect.Interface {
				vSetter.Set(reflect.ValueOf(u))
			} else {
				vSetter.SetUint(u)
			}
			return nil
		case reflect.Float32, reflect.Float64:
			f := convertCFNumberToFloat64(C.CFNumberRef(cfObj))
			if v.OverflowFloat(f) {
				state.recordError(&UnmarshalTypeError{cfTypeNames[typeID] + " " + strconv.FormatFloat(f, 'f', -1, 64), vType})
				return nil
			}
			if vSetter.Kind() == reflect.Interface {
				vSetter.Set(reflect.ValueOf(f))
			} else {
				vSetter.SetFloat(f)
			}
			return nil
		}
		state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType})
		return nil
	case cfStringTypeID:
		if vType.Kind() != reflect.String {
			state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType})
			return nil
		}
		vSetter.Set(reflect.ValueOf(convertCFStringToString(C.CFStringRef(cfObj))))
		return nil
	}
	return &UnknownCFTypeError{typeID}
}