// CFNumberToInterface converts the CFNumberRef to the most appropriate numeric // type. // This code is from github.com/kballard/go-osx-plist. func CFNumberToInterface(cfNumber C.CFNumberRef) interface{} { typ := C.CFNumberGetType(cfNumber) switch typ { case C.kCFNumberSInt8Type: var sint C.SInt8 C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) return int8(sint) case C.kCFNumberSInt16Type: var sint C.SInt16 C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) return int16(sint) case C.kCFNumberSInt32Type: var sint C.SInt32 C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) return int32(sint) case C.kCFNumberSInt64Type: var sint C.SInt64 C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) return int64(sint) case C.kCFNumberFloat32Type: var float C.Float32 C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) return float32(float) case C.kCFNumberFloat64Type: var float C.Float64 C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) return float64(float) case C.kCFNumberCharType: var char C.char C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&char)) return byte(char) case C.kCFNumberShortType: var short C.short C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&short)) return int16(short) case C.kCFNumberIntType: var i C.int C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&i)) return int32(i) case C.kCFNumberLongType: var long C.long C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&long)) return int(long) case C.kCFNumberLongLongType: // This is the only type that may actually overflow us var longlong C.longlong C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&longlong)) return int64(longlong) case C.kCFNumberFloatType: var float C.float C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) return float32(float) case C.kCFNumberDoubleType: var double C.double C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&double)) return float64(double) case C.kCFNumberCFIndexType: // CFIndex is a long var index C.CFIndex C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&index)) return int(index) case C.kCFNumberNSIntegerType: // We don't have a definition of NSInteger, but we know it's either an int or a long var nsInt C.long C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&nsInt)) return int(nsInt) } panic("Unknown CFNumber type") }
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} }