diff --git a/Sources/JNICore+JavaCoder.swift b/Sources/JNICore+JavaCoder.swift index d251889..59b4004 100644 --- a/Sources/JNICore+JavaCoder.swift +++ b/Sources/JNICore+JavaCoder.swift @@ -115,7 +115,7 @@ public extension JNICore { return jboolean(JNI_FALSE) } - enum JNIError: Error { + enum JNIError: Error, LocalizedError { case classNotFoundException(String) case methodNotFoundException(String) @@ -124,7 +124,7 @@ public extension JNICore { public func `throw`() { switch self { case .classNotFoundException(let message): - assert(JNI.api.ThrowNew(JNI.env, ExceptionClass, "ClassNotFoundaException: \(message)") == 0) + assert(JNI.api.ThrowNew(JNI.env, ExceptionClass, "ClassNotFoundException: \(message)") == 0) case .methodNotFoundException(let message): assert(JNI.api.ThrowNew(JNI.env, ExceptionClass, "MethodNotFoundException: \(message)") == 0) case .fieldNotFoundException(let message): @@ -132,6 +132,17 @@ public extension JNICore { } } + + public var errorDescription: String? { + switch self { + case .classNotFoundException(let message): + return "ClassNotFoundException: \(message)" + case .methodNotFoundException(let message): + return "MethodNotFoundException: \(message)" + case .fieldNotFoundException(let message): + return "FieldNotFoundException: \(message)" + } + } } // MARK: Global cache functions @@ -253,6 +264,18 @@ public extension JNICore { api.CallLongMethodA(env, object, methodID, argsPtr) }) } + + func CallFloatMethod(_ object: jobject, methodID: jmethodID, args: [jvalue] = []) -> jfloat { + return checkArgument(args: args, { argsPtr in + api.CallFloatMethodA(env, object, methodID, argsPtr) + }) + } + + func CallDoubleMethod(_ object: jobject, methodID: jmethodID, args: [jvalue] = []) -> jdouble { + return checkArgument(args: args, { argsPtr in + api.CallDoubleMethodA(env, object, methodID, argsPtr) + }) + } func CallObjectMethod(_ object: jobject, methodID: jmethodID, args: [jvalue] = []) -> jobject? { return checkArgument(args: args, { argsPtr in @@ -335,6 +358,78 @@ public extension JNICore { }) } + func CallBooleanMethod(_ object: jobject, _ methodID: jmethodID, _ args: JNIArgumentProtocol...) -> jboolean { + return checkArgumentAndWrap(args: args, { argsPtr in + api.CallBooleanMethodA(env, object, methodID, argsPtr) + }) + } + + func CallByteMethod(_ object: jobject, _ methodID: jmethodID, _ args: JNIArgumentProtocol...) -> jbyte { + return checkArgumentAndWrap(args: args, { argsPtr in + api.CallByteMethodA(env, object, methodID, argsPtr) + }) + } + + func CallShortMethod(_ object: jobject, _ methodID: jmethodID, _ args: JNIArgumentProtocol...) -> jshort { + return checkArgumentAndWrap(args: args, { argsPtr in + api.CallShortMethodA(env, object, methodID, argsPtr) + }) + } + + func CallIntMethod(_ object: jobject, _ methodID: jmethodID, _ args: JNIArgumentProtocol...) -> jint { + return checkArgumentAndWrap(args: args, { argsPtr in + api.CallIntMethodA(env, object, methodID, argsPtr) + }) + } + + func CallLongMethod(_ object: jobject, _ methodID: jmethodID, _ args: JNIArgumentProtocol...) -> jlong { + return checkArgumentAndWrap(args: args, { argsPtr in + api.CallLongMethodA(env, object, methodID, argsPtr) + }) + } + + func CallFloatMethod(_ object: jobject, _ methodID: jmethodID, _ args: JNIArgumentProtocol...) -> jfloat { + return checkArgumentAndWrap(args: args, { argsPtr in + api.CallFloatMethodA(env, object, methodID, argsPtr) + }) + } + + func CallDoubleMethod(_ object: jobject, _ methodID: jmethodID, _ args: JNIArgumentProtocol...) -> jdouble { + return checkArgumentAndWrap(args: args, { argsPtr in + api.CallDoubleMethodA(env, object, methodID, argsPtr) + }) + } + + func CallStaticBooleanMethod(_ clazz: jclass, _ methodID: jmethodID, _ args: JNIArgumentProtocol...) -> jboolean { + return checkArgumentAndWrap(args: args, { argsPtr in + api.CallStaticBooleanMethodA(env, clazz, methodID, argsPtr) + }) + } + + func CallStaticByteMethod(_ clazz: jclass, _ methodID: jmethodID, _ args: JNIArgumentProtocol...) -> jbyte { + return checkArgumentAndWrap(args: args, { argsPtr in + api.CallStaticByteMethodA(env, clazz, methodID, argsPtr) + }) + } + + func CallStaticShortMethod(_ clazz: jclass, _ methodID: jmethodID, _ args: JNIArgumentProtocol...) -> jshort { + return checkArgumentAndWrap(args: args, { argsPtr in + api.CallStaticShortMethodA(env, clazz, methodID, argsPtr) + }) + } + + func CallStaticIntMethod(_ clazz: jclass, _ methodID: jmethodID, _ args: JNIArgumentProtocol...) -> jint { + return checkArgumentAndWrap(args: args, { argsPtr in + api.CallStaticIntMethodA(env, clazz, methodID, argsPtr) + }) + } + + func CallStaticLongMethod(_ clazz: jclass, _ methodID: jmethodID, _ args: JNIArgumentProtocol...) -> jlong { + return checkArgumentAndWrap(args: args, { argsPtr in + api.CallStaticLongMethodA(env, clazz, methodID, argsPtr) + }) + } + func CallVoidMethod(_ object: jobject, _ methodID: jmethodID, _ args: JNIArgumentProtocol...) { checkArgumentAndWrap(args: args, { argsPtr in api.CallVoidMethodA(env, object, methodID, argsPtr) diff --git a/Sources/JNIObject+JavaCoder.swift b/Sources/JNIObject+JavaCoder.swift index 73f173e..a71d449 100644 --- a/Sources/JNIObject+JavaCoder.swift +++ b/Sources/JNIObject+JavaCoder.swift @@ -47,15 +47,15 @@ public extension JNIObject { func callStringMethod(method: String? = nil, functionName: String = #function, _ args: JNIArgumentProtocol...) -> String { let methodName = method ?? String(functionName.split(separator: "(")[0]) - return String(javaObject: self.internalcallObjectMethod(method: methodName, returnType: "Ljava/lang/String;", args)) + return String(javaObject: self.internalCallObjectMethod(method: methodName, returnType: "Ljava/lang/String;", args)) } func callObjectMethod(method: String? = nil, functionName: String = #function, returnType: String, _ args: JNIArgumentProtocol...) -> jobject? { let methodName = method ?? String(functionName.split(separator: "(")[0]) - return self.internalcallObjectMethod(method: methodName, returnType: returnType, args) + return self.internalCallObjectMethod(method: methodName, returnType: returnType, args) } - private func internalcallObjectMethod(method: String, returnType: String, _ args: [JNIArgumentProtocol]) -> jobject? { + private func internalCallObjectMethod(method: String, returnType: String, _ args: [JNIArgumentProtocol]) -> jobject? { let sig = "(\(args.map({ $0.sig() }).joined()))\(returnType)" let methodID = try! JNI.getJavaMethod(forClass: self.className, method: method, sig: sig) return checkArgumentAndWrap(args: args, { argsPtr in diff --git a/Sources/JavaCoderConfig.swift b/Sources/JavaCoderConfig.swift index ec5187e..3652001 100644 --- a/Sources/JavaCoderConfig.swift +++ b/Sources/JavaCoderConfig.swift @@ -6,8 +6,8 @@ import Foundation import java_swift import CJavaVM -public typealias JavaEncodableClosure = (Any) throws -> jobject -public typealias JavaDecodableClosure = (jobject) throws -> Decodable +public typealias JavaEncodableClosure = (Any, [CodingKey]) throws -> jobject +public typealias JavaDecodableClosure = (jobject, [CodingKey]) throws -> Decodable public struct JavaCoderConfig { @@ -43,153 +43,170 @@ public struct JavaCoderConfig { public static func RegisterBasicJavaTypes() { - RegisterType(type: Int.self, javaClassname: IntegerClassname, encodableClosure: { - // jint for macOS and Android different, that's why we make cast to jint() here - let args = [jvalue(i: jint($0 as! Int))] + RegisterType(type: Int.self, javaClassname: IntegerClassname, encodableClosure: { any, codingPath in + let value = any as! Int + let primitive = try value.javaPrimitive(codingPath: codingPath) + let args = [jvalue(i: primitive)] return JNI.NewObject(IntegerClass, methodID: IntegerConstructor, args: args)! - }, decodableClosure: { - return Int(JNI.CallIntMethod($0, methodID: NumberIntValueMethod)) + }, decodableClosure: { value, _ in + Int(fromJavaPrimitive: JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) }) - RegisterType(type: Int8.self, javaClassname: ByteClassname, encodableClosure: { - let args = [jvalue(b: $0 as! Int8)] + RegisterType(type: Int8.self, javaClassname: ByteClassname, encodableClosure: { any, _ in + let value = any as! Int8 + let primitive = try value.javaPrimitive() + let args = [jvalue(b: primitive)] return JNI.NewObject(ByteClass, methodID: ByteConstructor, args: args)! - }, decodableClosure: { - return JNI.CallByteMethod($0, methodID: NumberByteValueMethod) + }, decodableClosure: { value, _ in + Int8(fromJavaPrimitive: JNI.CallByteMethod(value, methodID: NumberByteValueMethod)) }) - RegisterType(type: Int16.self, javaClassname: ShortClassname, encodableClosure: { - let args = [jvalue(s: $0 as! Int16)] + RegisterType(type: Int16.self, javaClassname: ShortClassname, encodableClosure: { any, _ in + let value = any as! Int16 + let primitive = try value.javaPrimitive() + let args = [jvalue(s: primitive)] return JNI.NewObject(ShortClass, methodID: ShortConstructor, args: args)! - }, decodableClosure: { - return JNI.CallShortMethod($0, methodID: NumberShortValueMethod) + }, decodableClosure: { value, _ in + Int16(fromJavaPrimitive: JNI.CallShortMethod(value, methodID: NumberShortValueMethod)) }) - RegisterType(type: Int32.self, javaClassname: IntegerClassname, encodableClosure: { - let args = [jvalue(i: jint($0 as! Int32))] + RegisterType(type: Int32.self, javaClassname: IntegerClassname, encodableClosure: { any, _ in + let value = any as! Int32 + let primitive = try value.javaPrimitive() + let args = [jvalue(i: primitive)] return JNI.NewObject(IntegerClass, methodID: IntegerConstructor, args: args)! - }, decodableClosure: { - return Int32(JNI.CallIntMethod($0, methodID: NumberIntValueMethod)) + }, decodableClosure: { value, _ in + Int32(fromJavaPrimitive: JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) }) - RegisterType(type: Int64.self, javaClassname: LongClassname, encodableClosure: { - let args = [jvalue(j: $0 as! Int64)] + RegisterType(type: Int64.self, javaClassname: LongClassname, encodableClosure: { any, _ in + let value = any as! Int64 + let primitive = try value.javaPrimitive() + let args = [jvalue(j: primitive)] return JNI.NewObject(LongClass, methodID: LongConstructor, args: args)! - }, decodableClosure: { - return JNI.CallLongMethod($0, methodID: NumberLongValueMethod) + }, decodableClosure: { value, _ in + Int64(fromJavaPrimitive: JNI.CallLongMethod(value, methodID: NumberLongValueMethod)) }) - RegisterType(type: UInt.self, javaClassname: LongClassname, encodableClosure: { - let args = [jvalue(j: Int64($0 as! UInt))] - return JNI.NewObject(LongClass, methodID: LongConstructor, args: args)! - }, decodableClosure: { - return UInt(JNI.CallLongMethod($0, methodID: NumberLongValueMethod)) + RegisterType(type: UInt.self, javaClassname: IntegerClassname, encodableClosure: { any, codingPath in + let value = any as! UInt + let primitive = try value.javaPrimitive(codingPath: codingPath) + let args = [jvalue(i: primitive)] + return JNI.NewObject(IntegerClass, methodID: IntegerConstructor, args: args)! + }, decodableClosure: { value, _ in + UInt(fromJavaPrimitive: JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) + }) + + RegisterType(type: UInt8.self, javaClassname: ByteClassname, encodableClosure: { any, _ in + let value = any as! UInt8 + let primitive = try value.javaPrimitive() + let args = [jvalue(b: primitive)] + return JNI.NewObject(ByteClass, methodID: ByteConstructor, args: args)! + }, decodableClosure: { value, _ in + UInt8(fromJavaPrimitive: JNI.CallByteMethod(value, methodID: NumberByteValueMethod)) }) - RegisterType(type: UInt8.self, javaClassname: ShortClassname, encodableClosure: { - let args = [jvalue(s: Int16($0 as! UInt8))] + RegisterType(type: UInt16.self, javaClassname: ShortClassname, encodableClosure: { any, _ in + let value = any as! UInt16 + let primitive = try value.javaPrimitive() + let args = [jvalue(s: primitive)] return JNI.NewObject(ShortClass, methodID: ShortConstructor, args: args)! - }, decodableClosure: { - return UInt8(JNI.CallShortMethod($0, methodID: NumberShortValueMethod)) + }, decodableClosure: { value, _ in + UInt16(fromJavaPrimitive: JNI.CallShortMethod(value, methodID: NumberShortValueMethod)) }) - RegisterType(type: UInt16.self, javaClassname: IntegerClassname, encodableClosure: { - let args = [jvalue(i: jint($0 as! UInt16))] + RegisterType(type: UInt32.self, javaClassname: IntegerClassname, encodableClosure: { any, _ in + let value = any as! UInt32 + let primitive = try value.javaPrimitive() + let args = [jvalue(i: primitive)] return JNI.NewObject(IntegerClass, methodID: IntegerConstructor, args: args)! - }, decodableClosure: { - return UInt16(JNI.CallIntMethod($0, methodID: NumberIntValueMethod)) + }, decodableClosure: { value, _ in + UInt32(fromJavaPrimitive: JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) }) - RegisterType(type: UInt32.self, javaClassname: LongClassname, encodableClosure: { - let args = [jvalue(j: Int64($0 as! UInt32))] + RegisterType(type: UInt64.self, javaClassname: LongClassname, encodableClosure: { any, _ in + let value = any as! UInt64 + let primitive = try value.javaPrimitive() + let args = [jvalue(j: primitive)] return JNI.NewObject(LongClass, methodID: LongConstructor, args: args)! - }, decodableClosure: { - return UInt32(JNI.CallLongMethod($0, methodID: NumberLongValueMethod)) - }) - - RegisterType(type: UInt64.self, javaClassname: BigIntegerClassname, encodableClosure: { - var locals = [jobject]() - let args = [jvalue(l: String($0 as! UInt64).localJavaObject(&locals))] - return JNI.check(JNI.NewObject(BigIntegerClass, methodID: BigIntegerConstructor, args: args)!, &locals) - }, decodableClosure: { - let javaString = JNI.CallObjectMethod($0, methodID: ObjectToStringMethod) - defer { - JNI.api.DeleteLocalRef(JNI.env, javaString) - } - let stringRepresentation = String(javaObject: javaString) - return UInt64(stringRepresentation) + }, decodableClosure: { value, _ in + UInt64(fromJavaPrimitive: JNI.CallLongMethod(value, methodID: NumberLongValueMethod)) }) - RegisterType(type: Float.self, javaClassname: FloatClassname, encodableClosure: { - let args = [jvalue(f: $0 as! Float)] + RegisterType(type: Float.self, javaClassname: FloatClassname, encodableClosure: { any, _ in + let value = any as! Float + let primitive = jfloat(value) + let args = [jvalue(f: primitive)] return JNI.NewObject(FloatClass, methodID: FloatConstructor, args: args)! - }, decodableClosure: { - return JNI.api.CallFloatMethodA(JNI.env, $0, NumberFloatValueMethod, nil) + }, decodableClosure: { value, _ in + Float(fromJavaPrimitive: JNI.CallFloatMethod(value, methodID: NumberFloatValueMethod)) }) - RegisterType(type: Double.self, javaClassname: DoubleClassname, encodableClosure: { - let args = [jvalue(d: $0 as! Double)] + RegisterType(type: Double.self, javaClassname: DoubleClassname, encodableClosure: { any, _ in + let value = any as! Double + let primitive = jdouble(value) + let args = [jvalue(d: primitive)] return JNI.NewObject(DoubleClass, methodID: DoubleConstructor, args: args)! - }, decodableClosure: { - return JNI.api.CallDoubleMethodA(JNI.env, $0, NumberDoubleValueMethod, nil) + }, decodableClosure: { value, _ in + Double(fromJavaPrimitive: JNI.CallDoubleMethod(value, methodID: NumberDoubleValueMethod)) }) - RegisterType(type: Bool.self, javaClassname: BooleanClassname, encodableClosure: { - let args = [jvalue(z: $0 as! Bool ? JNI.TRUE : JNI.FALSE)] + RegisterType(type: Bool.self, javaClassname: BooleanClassname, encodableClosure: { value, _ in + let args = [jvalue(z: value as! Bool ? JNI.TRUE : JNI.FALSE)] return JNI.NewObject(BooleanClass, methodID: BooleanConstructor, args: args)! - }, decodableClosure: { - return (JNI.CallBooleanMethod($0, methodID: NumberBooleanValueMethod) == JNI.TRUE) + }, decodableClosure: { value, _ in + JNI.CallBooleanMethod(value, methodID: NumberBooleanValueMethod) == JNI.TRUE }) - RegisterType(type: String.self, javaClassname: StringClassname, encodableClosure: { - let valueString = $0 as! String + RegisterType(type: String.self, javaClassname: StringClassname, encodableClosure: { value, _ in + let valueString = value as! String var locals = [jobject]() // Locals ignored because JNIStorageObject take ownership of LocalReference return valueString.localJavaObject(&locals)! - }, decodableClosure: { - return String(javaObject: $0) + }, decodableClosure: { value, _ in + String(javaObject: value) }) - RegisterType(type: Date.self, javaClassname: DateClassname, encodableClosure: { - let valueDate = $0 as! Date + RegisterType(type: Date.self, javaClassname: DateClassname, encodableClosure: { value, _ in + let valueDate = value as! Date let args = [jvalue(j: jlong(valueDate.timeIntervalSince1970 * 1000))] return JNI.NewObject(DateClass, methodID: DateConstructor, args: args)! - }, decodableClosure: { - let timeInterval = JNI.api.CallLongMethodA(JNI.env, $0, DateGetTimeMethod, nil) + }, decodableClosure: { value, _ in + let timeInterval = JNI.api.CallLongMethodA(JNI.env, value, DateGetTimeMethod, nil) // Java save TimeInterval in UInt64 milliseconds return Date(timeIntervalSince1970: TimeInterval(timeInterval) / 1000.0) }) - RegisterType(type: URL.self, javaClassname: UriClassname, encodableClosure: { + RegisterType(type: URL.self, javaClassname: UriClassname, encodableClosure: { value, _ in var locals = [jobject]() - let javaString = ($0 as! URL).absoluteString.localJavaObject(&locals) + let javaString = (value as! URL).absoluteString.localJavaObject(&locals) let args = [jvalue(l: javaString)] JNI.SaveFatalErrorMessage("UriConstructor") defer { JNI.RemoveFatalErrorMessage() } return JNI.check(JNI.CallStaticObjectMethod(UriClass, methodID: UriConstructor!, args: args)!, &locals) - }, decodableClosure: { - let pathString = JNI.api.CallObjectMethodA(JNI.env, $0, ObjectToStringMethod, nil) + }, decodableClosure: { value, _ in + let pathString = JNI.api.CallObjectMethodA(JNI.env, value, ObjectToStringMethod, nil) return URL(string: String(javaObject: pathString)) }) - RegisterType(type: Data.self, javaClassname: ByteBufferClassname, encodableClosure: { - let valueData = $0 as! Data + RegisterType(type: Data.self, javaClassname: ByteBufferClassname, encodableClosure: { data, codingPath in + let valueData = data as! Data let byteArray = JNI.api.NewByteArray(JNI.env, jint(valueData.count)) if let throwable = JNI.ExceptionCheck() { - throw EncodingError.invalidValue($0, EncodingError.Context(codingPath: [], + throw EncodingError.invalidValue(data, EncodingError.Context(codingPath: codingPath, debugDescription: "Can't create NewByteArray \(valueData.count)")) } try valueData.withUnsafeBytes({ pointer in guard let bytes = pointer.baseAddress?.assumingMemoryBound(to: Int8.self) else { - throw EncodingError.invalidValue(valueData, EncodingError.Context(codingPath: [], + throw EncodingError.invalidValue(valueData, EncodingError.Context(codingPath: codingPath, debugDescription: "Can't get unsafeBytes \(valueData.count)")) } JNI.api.SetByteArrayRegion(JNI.env, byteArray, 0, jint(valueData.count), bytes) }) if let throwable = JNI.ExceptionCheck() { - throw EncodingError.invalidValue($0, EncodingError.Context(codingPath: [], + throw EncodingError.invalidValue(data, EncodingError.Context(codingPath: codingPath, debugDescription: "SetByteArrayRegion failed \(valueData.count)")) } JNI.SaveFatalErrorMessage("java/nio/ByteBuffer wrap") @@ -197,8 +214,8 @@ public struct JavaCoderConfig { JNI.RemoveFatalErrorMessage() } return JNI.CallStaticObjectMethod(ByteBufferClass, methodID: ByteBufferWrap, args: [jvalue(l: byteArray)])! - }, decodableClosure: { - let byteArray = JNI.CallObjectMethod($0, methodID: ByteBufferArray) + }, decodableClosure: { value, _ in + let byteArray = JNI.CallObjectMethod(value, methodID: ByteBufferArray) defer { JNI.api.DeleteLocalRef(JNI.env, byteArray) } diff --git a/Sources/JavaCodingError.swift b/Sources/JavaCodingError.swift new file mode 100644 index 0000000..2f4e450 --- /dev/null +++ b/Sources/JavaCodingError.swift @@ -0,0 +1,78 @@ +// +// Created by Andriy Druk on 19.04.2020. +// + +import Foundation + +public enum JavaCodingError: LocalizedError { + case notSupported(String) + case cantCreateObject(String) + case cantFindObject(String) + + public var errorDescription: String? { + switch self { + case .notSupported(let message): + return "Not supported: \(message)" + case .cantCreateObject(let message): + return "Can't create object: \(message)" + case .cantFindObject(let message): + return "Can't find object: \(message)" + } + } +} + +// We need one more protocol, because we can't override description func in EncodingError & DecodingError +public protocol JavaCodingErrorDescription { + var detailedDescription: String { get } +} + +fileprivate func contextDescription(codingPath: [CodingKey], + debugDescription: String, + underlyingError: Error?) -> String { + var underlyingErrorDescription = "" + if let underlyingError = underlyingError { + underlyingErrorDescription = " with underlying error: \(underlyingError.localizedDescription)" + } + let path = codingPath.map({ $0.stringValue }).joined(separator: "/") + return "\(debugDescription) [\(path)]" + underlyingErrorDescription +} + +extension EncodingError.Context: JavaCodingErrorDescription { + public var detailedDescription: String { + return contextDescription(codingPath: codingPath, debugDescription: debugDescription, underlyingError: underlyingError) + } +} + +extension DecodingError.Context: JavaCodingErrorDescription { + public var detailedDescription: String { + return contextDescription(codingPath: codingPath, debugDescription: debugDescription, underlyingError: underlyingError) + } +} + +extension EncodingError: JavaCodingErrorDescription { + public var detailedDescription: String { + switch self { + case .invalidValue(let value, let context): + return "Invalid value \"\(value)\": \(context.detailedDescription)" + @unknown default: + return "Not supported encoding error" + } + } +} + +extension DecodingError: JavaCodingErrorDescription { + public var detailedDescription: String { + switch self { + case .typeMismatch(let value, let context): + return "Type mismatch \"\(value)\": \(context.detailedDescription)" + case .valueNotFound(let value, let context): + return "Value not found \"\(value)\": \(context.detailedDescription)" + case .keyNotFound(let codingKey, let context): + return "Key not found \"\(codingKey)\": \(context.detailedDescription)" + case .dataCorrupted(let context): + return "Data corrupted: \(context.detailedDescription)" + @unknown default: + return "Not supported decoding error" + } + } +} \ No newline at end of file diff --git a/Sources/JavaDecoder.swift b/Sources/JavaDecoder.swift index c9500fd..5107de8 100644 --- a/Sources/JavaDecoder.swift +++ b/Sources/JavaDecoder.swift @@ -11,7 +11,7 @@ import AnyCodable public class JavaDecoder: Decoder { - public var codingPath = [CodingKey]() + public var codingPath: [CodingKey] public var userInfo = [CodingUserInfoKey : Any]() @@ -19,14 +19,17 @@ public class JavaDecoder: Decoder { fileprivate let package: String fileprivate let missingFieldsStrategy: MissingFieldsStrategy - public init(forPackage package: String, missingFieldsStrategy: MissingFieldsStrategy = .throw) { + public init(forPackage package: String, + missingFieldsStrategy: MissingFieldsStrategy = .throw, + codingPath: [CodingKey] = []) { self.package = package self.missingFieldsStrategy = missingFieldsStrategy + self.codingPath = codingPath } public func decode(_ type: T.Type, from javaObject: jobject) throws -> T { do { - let value = try unbox(type: type, javaObject: javaObject) + let value = try unbox(type: type, javaObject: javaObject, codingPath: codingPath) assert(self.storage.count == 0, "Missing decoding for \(self.storage.count) objects") return value } @@ -86,21 +89,21 @@ public class JavaDecoder: Decoder { fileprivate class JavaObjectContainer : KeyedDecodingContainerProtocol { typealias Key = K - var codingPath = [CodingKey]() + var codingPath: [CodingKey] var allKeys = [K]() let decoder: JavaDecoder let jniStorage: JNIStorageObject - let javaObject: jobject let javaClass: String fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) { self.decoder = decoder self.jniStorage = jniStorage - self.javaObject = jniStorage.javaObject + + codingPath = jniStorage.codingPath switch jniStorage.type { case let .object(className): - self.javaClass = className + javaClass = className default: fatalError("Wrong container type") } @@ -111,7 +114,7 @@ fileprivate class JavaObjectContainer : KeyedDecodingContainerPro } func decodeNil(forKey key: K) throws -> Bool { - throw JavaCodingError.notSupported("JavaObjectContainer.decodeNil(forKey: \(key)") + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Nil not supported") } private func decodeWithMissingStrategy(defaultValue: T, block: () throws -> T) throws -> T { @@ -128,13 +131,131 @@ fileprivate class JavaObjectContainer : KeyedDecodingContainerPro } } } - + + // MARK: Decode JNI primitive fields + private func decodeBoolean(forKey key: String) throws -> Bool { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "Z") + let javaObject = jniStorage.javaObject + defer { + JNI.DeleteLocalRef(javaObject) + } + return JNI.api.GetBooleanField(JNI.env, javaObject, fieldID) == JNI_TRUE + } + + private func decodeByte(forKey key: String) throws -> Int8 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "B") + let javaObject = jniStorage.javaObject + defer { + JNI.DeleteLocalRef(javaObject) + } + return JNI.api.GetByteField(JNI.env, javaObject, fieldID) + } + + private func decodeShort(forKey key: String) throws -> Int16 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "S") + let javaObject = jniStorage.javaObject + defer { + JNI.DeleteLocalRef(javaObject) + } + return JNI.api.GetShortField(JNI.env, javaObject, fieldID) + } + + private func decodeInteger(forKey key: String) throws -> Int32 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "I") + let javaObject = jniStorage.javaObject + defer { + JNI.DeleteLocalRef(javaObject) + } + #if arch(x86_64) || arch(arm64) + return JNI.api.GetIntField(JNI.env, javaObject, fieldID) + #else + return Int32(JNI.api.GetIntField(JNI.env, javaObject, fieldID)) + #endif + } + + private func decodeLong(forKey key: String) throws -> Int64 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "J") + let javaObject = jniStorage.javaObject + defer { + JNI.DeleteLocalRef(javaObject) + } + return JNI.api.GetLongField(JNI.env, javaObject, fieldID) + } + + private func decodeFloat(forKey key: String) throws -> Float { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "F") + let javaObject = jniStorage.javaObject + defer { + JNI.DeleteLocalRef(javaObject) + } + return JNI.api.GetFloatField(JNI.env, javaObject, fieldID) + } + + private func decodeDouble(forKey key: String) throws -> Double { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "D") + let javaObject = jniStorage.javaObject + defer { + JNI.DeleteLocalRef(javaObject) + } + return JNI.api.GetDoubleField(JNI.env, javaObject, fieldID) + } + + // MARK: KeyedDecodingContainerProtocol protocol public func decode(_ type: Bool.Type, forKey key: K) throws -> Bool { + // TODO: WTF? Delete decodeWithMissingStrategy with default false -> CRASH return try decodeWithMissingStrategy(defaultValue: false) { - return try decodeJava(type, forKey: key) ?? false + return try self.decodeBoolean(forKey: key.stringValue) } } - + + public func decode(_ type: Int.Type, forKey key: K) throws -> Int { + return Int(try decodeInteger(forKey: key.stringValue)) + } + + public func decode(_ type: Int8.Type, forKey key: K) throws -> Int8 { + return try decodeByte(forKey: key.stringValue) + } + + public func decode(_ type: Int16.Type, forKey key: K) throws -> Int16 { + return try decodeShort(forKey: key.stringValue) + } + + public func decode(_ type: Int32.Type, forKey key: K) throws -> Int32 { + return try decodeInteger(forKey: key.stringValue) + } + + public func decode(_ type: Int64.Type, forKey key: K) throws -> Int64 { + return try decodeLong(forKey: key.stringValue) + } + + public func decode(_ type: UInt.Type, forKey key: K) throws -> UInt { + return UInt(UInt32(bitPattern: try decodeInteger(forKey: key.stringValue))) + } + + public func decode(_ type: UInt8.Type, forKey key: K) throws -> UInt8 { + return UInt8(bitPattern: try decodeByte(forKey: key.stringValue)) + } + + public func decode(_ type: UInt16.Type, forKey key: K) throws -> UInt16 { + return UInt16(bitPattern: try decodeShort(forKey: key.stringValue)) + } + + public func decode(_ type: UInt32.Type, forKey key: K) throws -> UInt32 { + return UInt32(bitPattern: try decodeInteger(forKey: key.stringValue)) + } + + public func decode(_ type: UInt64.Type, forKey key: K) throws -> UInt64 { + return UInt64(bitPattern: try decodeLong(forKey: key.stringValue)) + } + + public func decode(_ type: Float.Type, forKey key: K) throws -> Float { + return try decodeFloat(forKey: key.stringValue) + } + + public func decode(_ type: Double.Type, forKey key: K) throws -> Double { + return try decodeDouble(forKey: key.stringValue) + } + // override all decodeIfPresent to prevent calling decodeNil(forKey:) public func decodeIfPresent(_ type: Int.Type, forKey key: K) throws -> Int? { return try self.decodeJava(type, forKey: key) @@ -175,6 +296,14 @@ fileprivate class JavaObjectContainer : KeyedDecodingContainerPro public func decodeIfPresent(_ type: UInt64.Type, forKey key: K) throws -> UInt64? { return try self.decodeJava(type, forKey: key) } + + public func decodeIfPresent(_ type: Float.Type, forKey key: K) throws -> Float? { + return try self.decodeJava(type, forKey: key) + } + + public func decodeIfPresent(_ type: Double.Type, forKey key: K) throws -> Double? { + return try self.decodeJava(type, forKey: key) + } public func decodeIfPresent(_ type: Bool.Type, forKey key: K) throws -> Bool? { return try self.decodeJava(type, forKey: key) @@ -190,7 +319,7 @@ fileprivate class JavaObjectContainer : KeyedDecodingContainerPro func decode(_ type: T.Type, forKey key: K) throws -> T where T : Decodable { guard let result = try self.decodeJava(type, forKey: key) else { - throw JavaCodingError.nilNotSupported("\(javaClass).\(key.stringValue)") + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Nil not supported: \(javaClass).\(key.stringValue)") } return result } @@ -200,13 +329,16 @@ fileprivate class JavaObjectContainer : KeyedDecodingContainerPro let classname: String if type == AnyCodable.self { var locals = [jobject]() + let javaObject = jniStorage.javaObject let cls = JNI.api.GetObjectClass(JNI.env, javaObject)! + JNI.DeleteLocalRef(javaObject) + let javaTypename = key.stringValue.localJavaObject(&locals) guard let field = JNI.CallObjectMethod(cls, ClassGetFieldMethod, javaTypename!) else { JNI.ExceptionReset() let errorMessage = "\(javaClass).\(key.stringValue): JavaDecoder uses reflection for AnyCodable, " + "probably \(key.stringValue) field not public" - throw JavaCodingError.cantFindObject(errorMessage) + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: errorMessage) } let fieldClass = JNI.CallObjectMethod(field, methodID: FieldGetTypedMethod, args: [])! let javaClassName = JNI.api.CallObjectMethodA(JNI.env, fieldClass, ClassGetNameMethod, nil)! @@ -222,22 +354,26 @@ fileprivate class JavaObjectContainer : KeyedDecodingContainerPro } let fieldID = try JNI.getJavaField(forClass: javaClass, field: key.stringValue, sig: "L\(classname);") + let javaObject = jniStorage.javaObject + defer { + JNI.api.DeleteLocalRef(JNI.env, javaObject) + } guard let object = JNI.api.GetObjectField(JNI.env, javaObject, fieldID) else { return nil } defer { JNI.DeleteLocalRef(object) } - return try self.decoder.unbox(type: type, javaObject: object) + return try self.decoder.unbox(type: type, javaObject: object, codingPath: codingPath + [key]) } } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - throw JavaCodingError.notSupported("JavaObjectContainer.nestedContainer(keyedBy: \(type), forKey: \(key))") + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Nested keyed container not supported") } func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer { - throw JavaCodingError.notSupported("JavaObjectContainer.nestedUnkeyedContainer(forKey: \(key))") + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Nested unkeyed container not supported") } func superDecoder() throws -> Decoder { @@ -246,29 +382,30 @@ fileprivate class JavaObjectContainer : KeyedDecodingContainerPro } func superDecoder(forKey key: K) throws -> Decoder { - throw JavaCodingError.notSupported("JavaObjectContainer.superDecoder(forKey: \(key)") + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Super decoder not supported") } } fileprivate class JavaHashMapKeyedContainer: KeyedDecodingContainerProtocol { typealias Key = K - var codingPath = [CodingKey]() + var codingPath: [CodingKey] var allKeys = [K]() private let decoder: JavaDecoder private let jniStorage: JNIStorageObject - private let javaObject: jobject private var javaKeys = [AnyHashable: jobject]() fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) throws { self.decoder = decoder self.jniStorage = jniStorage - self.javaObject = jniStorage.javaObject - + codingPath = jniStorage.codingPath + + let javaObject = jniStorage.javaObject let keySet = JNI.api.CallObjectMethodA(JNI.env, javaObject, HashMapKeySetMethod, nil) let keyArray = JNI.api.CallObjectMethodA(JNI.env, keySet, SetToArrayMethod, nil) + JNI.DeleteLocalRef(javaObject) defer { JNI.DeleteLocalRef(keySet) JNI.DeleteLocalRef(keyArray) @@ -283,21 +420,24 @@ fileprivate class JavaHashMapKeyedContainer: KeyedDecodingContain var keySig: String? for i in 0 ..< size { + guard let indexKey = K(intValue: Int(i)) else { + throw DecodingError.dataCorruptedError(forKey: K(stringValue: "init()")!, in: self, debugDescription: "Wrong array length") + } guard let object = JNI.api.GetObjectArrayElement(JNI.env, keyArray, i) else { - throw JavaCodingError.wrongArrayLength + throw DecodingError.dataCorruptedError(forKey: K(stringValue: "init()")!, in: self, debugDescription: "Wrong array length") } if keySig == nil { keySig = self.decoder.getJavaClassname(from: object).sig } if keySig == "Ljava/lang/String;" { - let stringKey = try self.decoder.unbox(type: String.self, javaObject: object) + let stringKey = try self.decoder.unbox(type: String.self, javaObject: object, codingPath: codingPath + [indexKey]) if let key = K(stringValue: stringKey) { javaKeys[stringKey] = object allKeys.append(key) } } else { - let intKey = try self.decoder.unbox(type: Int.self, javaObject: object) + let intKey = try self.decoder.unbox(type: Int.self, javaObject: object, codingPath: codingPath + [indexKey]) if let key = K(intValue: intKey) { javaKeys[intKey] = object allKeys.append(key) @@ -317,7 +457,7 @@ fileprivate class JavaHashMapKeyedContainer: KeyedDecodingContain } func decodeNil(forKey key: K) throws -> Bool { - throw JavaCodingError.notSupported("JavaHashMapContainer.decodeNil(forKey: \(key))") + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Nil not supported") } func decode(_ type: T.Type, forKey key: K) throws -> T where T : Decodable { @@ -330,21 +470,25 @@ fileprivate class JavaHashMapKeyedContainer: KeyedDecodingContain } let javaKey = javaKeys[typeKey] - guard let object = JNI.CallObjectMethod(self.javaObject, methodID: HashMapGetMethod, args: [jvalue(l: javaKey)]) else { - throw JavaCodingError.cantFindObject("HashMap[\(key.stringValue)]") + let javaObject = jniStorage.javaObject + defer { + JNI.DeleteLocalRef(javaObject) + } + guard let object = JNI.CallObjectMethod(javaObject!, methodID: HashMapGetMethod, args: [jvalue(l: javaKey)]) else { + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Can't find object") } defer { JNI.DeleteLocalRef(object) } - return try self.decoder.unbox(type: type, javaObject: object) + return try self.decoder.unbox(type: type, javaObject: object, codingPath: codingPath + [key]) } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - throw JavaCodingError.notSupported("JavaHashMapContainer.nestedContainer(keyedBy: \(type), forKey: \(key))") + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Nested keyed container not supported") } func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer { - throw JavaCodingError.notSupported("JavaHashMapContainer.nestedUnkeyedContainer(forKey: \(key))") + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Nested unkeyed container not supported") } func superDecoder() throws -> Decoder { @@ -352,13 +496,13 @@ fileprivate class JavaHashMapKeyedContainer: KeyedDecodingContain } func superDecoder(forKey key: K) throws -> Decoder { - throw JavaCodingError.notSupported("JavaHashMapContainer.superDecoder(forKey: \(key))") + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Super decoder not supported") } } fileprivate class JavaHashMapUnkeyedContainer: UnkeyedDecodingContainer { - var codingPath = [CodingKey]() + var codingPath: [CodingKey] var count: Int? @@ -374,7 +518,6 @@ fileprivate class JavaHashMapUnkeyedContainer: UnkeyedDecodingContainer { let decoder: JavaDecoder let jniStorage: JNIStorageObject - let javaObject: jobject private var javaKeys: jarray private var javaCurrentKey: jobject? @@ -382,14 +525,14 @@ fileprivate class JavaHashMapUnkeyedContainer: UnkeyedDecodingContainer { fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) throws { self.decoder = decoder self.jniStorage = jniStorage - self.javaObject = jniStorage.javaObject - self.count = Int(JNI.CallIntMethod(self.javaObject, methodID: HashMapSizeMethod)) * 2 - + codingPath = jniStorage.codingPath + + let javaObject = jniStorage.javaObject + count = Int(JNI.CallIntMethod(javaObject!, methodID: HashMapSizeMethod)) * 2 let keySet = JNI.api.CallObjectMethodA(JNI.env, javaObject, HashMapKeySetMethod, nil) + JNI.DeleteLocalRef(javaObject) javaKeys = JNI.api.CallObjectMethodA(JNI.env, keySet, SetToArrayMethod, nil)! - defer { - JNI.api.DeleteLocalRef(JNI.env, keySet) - } + JNI.DeleteLocalRef(keySet) } deinit { @@ -397,13 +540,18 @@ fileprivate class JavaHashMapUnkeyedContainer: UnkeyedDecodingContainer { } func decodeNil() throws -> Bool { - throw JavaCodingError.notSupported("JavaUnkeyedDecodingContainer.decodeNil") + throw DecodingError.dataCorruptedError(in: self, debugDescription: "Nil not supported") } func decode(_ type: T.Type) throws -> T where T : Decodable { + let codingKey = JavaKey(intValue: currentIndex) if let javaCurrentKey = javaCurrentKey { - guard let object = JNI.CallObjectMethod(self.javaObject, methodID: HashMapGetMethod, args: [jvalue(l: javaCurrentKey)]) else { - throw JavaCodingError.cantFindObject("HashMap[]") + let javaObject = jniStorage.javaObject + defer { + JNI.DeleteLocalRef(javaObject) + } + guard let object = JNI.CallObjectMethod(javaObject!, methodID: HashMapGetMethod, args: [jvalue(l: javaCurrentKey)]) else { + throw DecodingError.dataCorruptedError(in: self, debugDescription: "Can't find object") } currentIndex += 1 defer { @@ -411,34 +559,34 @@ fileprivate class JavaHashMapUnkeyedContainer: UnkeyedDecodingContainer { JNI.DeleteLocalRef(self.javaCurrentKey) self.javaCurrentKey = nil } - return try self.decoder.unbox(type: type, javaObject: object) + return try self.decoder.unbox(type: type, javaObject: object, codingPath: codingPath + [codingKey]) } else { guard let object = JNI.api.GetObjectArrayElement(JNI.env, javaKeys, jsize(self.currentIndex / 2)) else { - throw JavaCodingError.wrongArrayLength + throw DecodingError.dataCorruptedError(in: self, debugDescription: "Wrong array length") } self.javaCurrentKey = object currentIndex += 1 - return try self.decoder.unbox(type: type, javaObject: object) + return try self.decoder.unbox(type: type, javaObject: object, codingPath: codingPath + [codingKey]) } } func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - throw JavaCodingError.notSupported("JavaUnkeyedDecodingContainer.nestedContainer(keyedBy: \(type))") + throw DecodingError.dataCorruptedError(in: self, debugDescription: "Nested keyed container not supported") } func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { - throw JavaCodingError.notSupported("JavaUnkeyedDecodingContainer.nestedUnkeyedContainer") + throw DecodingError.dataCorruptedError(in: self, debugDescription: "Nested unkeyed container not supported") } func superDecoder() throws -> Decoder { - throw JavaCodingError.notSupported("JavaUnkeyedDecodingContainer.superDecoder") + throw DecodingError.dataCorruptedError(in: self, debugDescription: "Super decoder not supported") } } fileprivate class JavaArrayContainer: UnkeyedDecodingContainer { - var codingPath = [CodingKey]() + var codingPath: [CodingKey] var count: Int? @@ -459,8 +607,11 @@ fileprivate class JavaArrayContainer: UnkeyedDecodingContainer { fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) { self.decoder = decoder self.jniStorage = jniStorage - self.count = Int(JNI.CallIntMethod(jniStorage.javaObject, methodID: CollectionSizeMethod)) - self.javaIterator = JNI.CallObjectMethod(jniStorage.javaObject, methodID: CollectionIteratorMethod)! + self.codingPath = jniStorage.codingPath + let javaObject = jniStorage.javaObject + self.count = Int(JNI.CallIntMethod(javaObject!, methodID: CollectionSizeMethod)) + self.javaIterator = JNI.CallObjectMethod(javaObject!, methodID: CollectionIteratorMethod)! + JNI.DeleteLocalRef(javaObject) } deinit { @@ -468,48 +619,49 @@ fileprivate class JavaArrayContainer: UnkeyedDecodingContainer { } func decodeNil() throws -> Bool { - throw JavaCodingError.notSupported("JavaUnkeyedDecodingContainer.decodeNil") + throw DecodingError.dataCorruptedError(in: self, debugDescription: "Nil not supported") } func decode(_ type: T.Type) throws -> T where T : Decodable { + let codingKey = JavaKey(intValue: currentIndex) guard let object = JNI.CallObjectMethod(self.javaIterator, methodID: IteratorNextMethod) else { - throw JavaCodingError.cantFindObject("Array out of range: \(self.currentIndex)") + throw DecodingError.dataCorruptedError(in: self, debugDescription: "Array out of range: \(self.currentIndex)") } defer { JNI.DeleteLocalRef(object) } currentIndex += 1 - return try self.decoder.unbox(type: type, javaObject: object) + return try self.decoder.unbox(type: type, javaObject: object, codingPath: codingPath + [codingKey]) } func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - throw JavaCodingError.notSupported("JavaUnkeyedDecodingContainer.nestedContainer(keyedBy: \(type))") + throw DecodingError.dataCorruptedError(in: self, debugDescription: "Nested keyed container not supported") } func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { - throw JavaCodingError.notSupported("JavaUnkeyedDecodingContainer.nestedUnkeyedContainer") + throw DecodingError.dataCorruptedError(in: self, debugDescription: "Nested unkeyed container not supported") } func superDecoder() throws -> Decoder { - throw JavaCodingError.notSupported("JavaUnkeyedDecodingContainer.superDecoder") + throw DecodingError.dataCorruptedError(in: self, debugDescription: "Super decoder not supported") } } fileprivate class JavaEnumContainer: SingleValueDecodingContainer { - var codingPath: [CodingKey] = [] + var codingPath: [CodingKey] let decoder: JavaDecoder let jniStorage: JNIStorageObject - let javaObject: jobject let javaClass: String fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) { self.decoder = decoder self.jniStorage = jniStorage - self.javaObject = jniStorage.javaObject + + codingPath = jniStorage.codingPath switch jniStorage.type { case let .object(className): - self.javaClass = className + javaClass = className default: fatalError("Wrong container type") } @@ -518,24 +670,108 @@ fileprivate class JavaEnumContainer: SingleValueDecodingContainer { func decodeNil() -> Bool { fatalError("Unsupported: JavaEnumDecodingContainer.decodeNil") } + + func decode(_ type: Int.Type) throws -> Int { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "I") + let javaObject = jniStorage.javaObject + let value = JNI.api.GetIntField(JNI.env, javaObject, fieldID) + JNI.DeleteLocalRef(javaObject) + return Int(fromJavaPrimitive: value) + } + + func decode(_ type: Int8.Type) throws -> Int8 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "B") + let javaObject = jniStorage.javaObject + let value = JNI.api.GetByteField(JNI.env, javaObject, fieldID) + JNI.DeleteLocalRef(javaObject) + return Int8(fromJavaPrimitive: value) + } + + func decode(_ type: Int16.Type) throws -> Int16 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "S") + let javaObject = jniStorage.javaObject + let value = JNI.api.GetShortField(JNI.env, javaObject, fieldID) + JNI.DeleteLocalRef(javaObject) + return Int16(fromJavaPrimitive: value) + } + + func decode(_ type: Int32.Type) throws -> Int32 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "I") + let javaObject = jniStorage.javaObject + let value = JNI.api.GetIntField(JNI.env, javaObject, fieldID) + JNI.DeleteLocalRef(javaObject) + return Int32(fromJavaPrimitive: value) + } + + func decode(_ type: Int64.Type) throws -> Int64 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "J") + let javaObject = jniStorage.javaObject + let value = JNI.api.GetLongField(JNI.env, javaObject, fieldID) + JNI.DeleteLocalRef(javaObject) + return Int64(fromJavaPrimitive: value) + } + + func decode(_ type: UInt.Type) throws -> UInt { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "I") + let javaObject = jniStorage.javaObject + let value = JNI.api.GetIntField(JNI.env, javaObject, fieldID) + JNI.DeleteLocalRef(javaObject) + return UInt(fromJavaPrimitive: value) + } + + func decode(_ type: UInt8.Type) throws -> UInt8 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "B") + let javaObject = jniStorage.javaObject + let value = JNI.api.GetByteField(JNI.env, javaObject, fieldID) + JNI.DeleteLocalRef(javaObject) + return UInt8(fromJavaPrimitive: value) + } + + func decode(_ type: UInt16.Type) throws -> UInt16 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "S") + let javaObject = jniStorage.javaObject + let value = JNI.api.GetShortField(JNI.env, javaObject, fieldID) + JNI.DeleteLocalRef(javaObject) + return UInt16(fromJavaPrimitive: value) + } + + func decode(_ type: UInt32.Type) throws -> UInt32 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "I") + let javaObject = jniStorage.javaObject + let value = JNI.api.GetIntField(JNI.env, javaObject, fieldID) + JNI.DeleteLocalRef(javaObject) + return UInt32(fromJavaPrimitive: value) + } + + func decode(_ type: UInt64.Type) throws -> UInt64 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "J") + let javaObject = jniStorage.javaObject + let value = JNI.api.GetLongField(JNI.env, javaObject, fieldID) + JNI.DeleteLocalRef(javaObject) + return UInt64(fromJavaPrimitive: value) + } - func decode(_ type: T.Type) throws -> T where T : Decodable { - let classname = self.decoder.getJavaClassname(forType: type) + func decode(_ valueType: T.Type) throws -> T where T : Decodable { + let classname = decoder.getJavaClassname(forType: valueType) let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "L\(classname);") + let javaObject = jniStorage.javaObject + defer { + JNI.DeleteLocalRef(javaObject) + } guard let object = JNI.api.GetObjectField(JNI.env, javaObject, fieldID) else { - throw JavaCodingError.nilNotSupported("\(javaClass).rawValue") + throw JavaCodingError.notSupported(javaClass) } defer { JNI.DeleteLocalRef(object) } - return try self.decoder.unbox(type: type, javaObject: object) + return try decoder.unbox(type: valueType, javaObject: object, codingPath: codingPath) } } fileprivate class JavaAnyCodableContainer : KeyedDecodingContainerProtocol { typealias Key = K - var codingPath = [CodingKey]() + var codingPath: [CodingKey] var allKeys = [K]() let decoder: JavaDecoder @@ -545,6 +781,7 @@ fileprivate class JavaAnyCodableContainer : KeyedDecodingContaine fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) { self.decoder = decoder self.jniStorage = jniStorage + self.codingPath = jniStorage.codingPath switch jniStorage.type { case let .anyCodable(codable): self.jniCodableType = codable @@ -558,7 +795,7 @@ fileprivate class JavaAnyCodableContainer : KeyedDecodingContaine } func decodeNil(forKey key: K) throws -> Bool { - throw JavaCodingError.notSupported("JavaObjectContainer.decodeNil(forKey: \(key)") + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Nil not supported") } func decode(_ type: T.Type, forKey key: K) throws -> T where T : Decodable { @@ -584,7 +821,11 @@ fileprivate class JavaAnyCodableContainer : KeyedDecodingContaine } } else if key.stringValue == "value" { - return try self.decoder.unbox(type: type, javaObject: self.jniStorage.javaObject) + let javaObject = jniStorage.javaObject + defer { + JNI.DeleteLocalRef(javaObject) + } + return try self.decoder.unbox(type: type, javaObject: javaObject!, codingPath: codingPath + [key]) } else { fatalError("Unknown key: \(key.stringValue)") @@ -592,7 +833,7 @@ fileprivate class JavaAnyCodableContainer : KeyedDecodingContaine } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - throw JavaCodingError.notSupported("JavaAnyCodableContainer.nestedContainer(keyedBy: \(type), forKey: \(key))") + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Nested keyed container not supported") } func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer { @@ -602,7 +843,7 @@ fileprivate class JavaAnyCodableContainer : KeyedDecodingContaine case .dictionary: return try JavaHashMapUnkeyedContainer(decoder: self.decoder, jniStorage: self.jniStorage) default: - fatalError("Unsupported type here") + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Nested unkeyed container not supported") } } @@ -611,16 +852,16 @@ fileprivate class JavaAnyCodableContainer : KeyedDecodingContaine } func superDecoder(forKey key: K) throws -> Decoder { - throw JavaCodingError.notSupported("JavaAnyCodableContainer.superDecoder(forKey: \(key)") + throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Super decoder not supported") } } extension JavaDecoder { - fileprivate func unbox(type: T.Type, javaObject: jobject) throws -> T { + fileprivate func unbox(type: T.Type, javaObject: jobject, codingPath: [CodingKey]) throws -> T { let typeName = String(describing: type) if let decodableClosure = JavaCoderConfig.decodableClosures[typeName] { - return try decodableClosure(javaObject) as! T + return try decodableClosure(javaObject, codingPath) as! T } else if type == AnyCodable.self { let cls = JNI.api.GetObjectClass(JNI.env, javaObject) @@ -642,7 +883,9 @@ extension JavaDecoder { codableType = .object(className: className) } let obj = JNI.api.NewLocalRef(JNI.env, javaObject)! - let storageObject = JNIStorageObject(type: .anyCodable(codable: codableType), javaObject: obj) + let storageObject = JNIStorageObject(type: .anyCodable(codable: codableType), + javaObject: obj, + codingPath: codingPath) self.storage.append(storageObject) return try T.init(from: self) } @@ -652,13 +895,19 @@ extension JavaDecoder { let obj = JNI.api.NewLocalRef(JNI.env, javaObject)! switch stringType { case _ where stringType.starts(with: "Array<"): - storageObject = JNIStorageObject(type: .array(className: ArrayListClassname), javaObject: obj) + storageObject = JNIStorageObject(type: .array(className: ArrayListClassname), + javaObject: obj, + codingPath: codingPath) case _ where stringType.starts(with: "Set<"): - storageObject = JNIStorageObject(type: .array(className: HashSetClassname), javaObject: obj) + storageObject = JNIStorageObject(type: .array(className: HashSetClassname), + javaObject: obj, + codingPath: codingPath) case _ where stringType.starts(with: "Dictionary<"): storageObject = JNIStorageObject(type: .dictionary, javaObject: obj) default: - storageObject = JNIStorageObject(type: .object(className: "\(package)/\(type)"), javaObject: obj) + storageObject = JNIStorageObject(type: .object(className: "\(package)/\(type)"), + javaObject: obj, + codingPath: codingPath) } self.storage.append(storageObject) return try T.init(from: self) diff --git a/Sources/JavaEncoder.swift b/Sources/JavaEncoder.swift index 022444e..c8b7a77 100644 --- a/Sources/JavaEncoder.swift +++ b/Sources/JavaEncoder.swift @@ -15,12 +15,32 @@ public enum MissingFieldsStrategy: Error { case ignore } -public enum JavaCodingError: Error { - case notSupported(String) - case cantCreateObject(String) - case cantFindObject(String) - case nilNotSupported(String) - case wrongArrayLength +internal struct JavaKey : CodingKey { + + public var stringValue: String + public var intValue: Int? + + public init(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } + + public init(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } + + public init(stringValue: String, intValue: Int?) { + self.stringValue = stringValue + self.intValue = intValue + } + + init(index: Int) { + self.stringValue = "Index \(index)" + self.intValue = index + } + + static let `super` = JavaKey(stringValue: "super") } indirect enum JNIStorageType { @@ -45,25 +65,39 @@ indirect enum JNIStorageType { class JNIStorageObject { let type: JNIStorageType + let codingPath: [CodingKey] + + private var _javaObject: jobject! + var javaObject: jobject! { - didSet { - if let value = oldValue { + get { + return JNI.api.NewLocalRef(JNI.env, _javaObject) + } + set { + if let value = _javaObject { JNI.api.DeleteLocalRef(JNI.env, value) } + _javaObject = newValue } } + + var hasJavaObject: Bool { + return _javaObject != nil + } - init(type: JNIStorageType, javaObject: jobject) { + init(type: JNIStorageType, javaObject: jobject, codingPath: [CodingKey] = []) { self.type = type - self.javaObject = javaObject + self._javaObject = javaObject + self.codingPath = codingPath } - init(type: JNIStorageType) { + init(type: JNIStorageType, codingPath: [CodingKey] = []) { self.type = type + self.codingPath = codingPath } deinit { - if let value = javaObject { + if let value = _javaObject { JNI.api.DeleteLocalRef(JNI.env, value) } } @@ -88,8 +122,10 @@ open class JavaEncoder: Encoder { // MARK: - Constructing a JSON Encoder /// Initializes `self` with default strategies. - public init(forPackage: String, missingFieldsStrategy: MissingFieldsStrategy = .throw) { - self.codingPath = [CodingKey]() + public init(forPackage: String, + missingFieldsStrategy: MissingFieldsStrategy = .throw, + codingPath: [CodingKey] = []) { + self.codingPath = codingPath self.package = forPackage self.javaObjects = [JNIStorageObject]() self.missingFieldsStrategy = missingFieldsStrategy @@ -104,9 +140,9 @@ open class JavaEncoder: Encoder { /// - throws: An error if any value throws an error during encoding. open func encode(_ value: T) throws -> jobject { do { - let storage = try self.box(value) + let storage = try self.box(value, codingPath: codingPath) assert(self.javaObjects.count == 0, "Missing encoding for \(self.javaObjects.count) objects") - return JNI.api.NewLocalRef(JNI.env, storage.javaObject)! + return storage.javaObject! } catch { // clean all reference if failed @@ -187,21 +223,211 @@ fileprivate class JavaObjectContainer : KeyedEncodingContainerPro self.javaClass = javaClass self.jniStorage = jniStorage } - - private var javaObject: jobject { - return jniStorage.javaObject + + // MARK: Encode JNI primitive fields + func encodeBoolean(_ value: jboolean, key: String) throws { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "Z") + let javaObject = jniStorage.javaObject + JNI.api.SetBooleanField(JNI.env, javaObject, fieldID, value) + JNI.DeleteLocalRef(javaObject) + } + + func encodeByte(_ value: jbyte, key: String) throws { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "B") + let javaObject = jniStorage.javaObject + JNI.api.SetByteField(JNI.env, javaObject, fieldID, value) + JNI.DeleteLocalRef(javaObject) + } + + func encodeShort(_ value: jshort, key: String) throws { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "S") + let javaObject = jniStorage.javaObject + JNI.api.SetShortField(JNI.env, javaObject, fieldID, value) + JNI.DeleteLocalRef(javaObject) + } + + func encodeInteger(_ value: jint, key: String) throws { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "I") + let javaObject = jniStorage.javaObject + JNI.api.SetIntField(JNI.env, javaObject, fieldID, value) + JNI.DeleteLocalRef(javaObject) + } + + func encodeLong(_ value: jlong, key: String) throws { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "J") + let javaObject = jniStorage.javaObject + JNI.api.SetLongField(JNI.env, javaObject, fieldID, value) + JNI.DeleteLocalRef(javaObject) + } + + func encodeFloat(_ value: jfloat, key: String) throws { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "F") + let javaObject = jniStorage.javaObject + JNI.api.SetFloatField(JNI.env, javaObject, fieldID, value) + JNI.DeleteLocalRef(javaObject) + } + + func encodeDouble(_ value: jdouble, key: String) throws { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "D") + let javaObject = jniStorage.javaObject + JNI.api.SetDoubleField(JNI.env, javaObject, fieldID, value) + JNI.DeleteLocalRef(javaObject) } // MARK: - KeyedEncodingContainerProtocol Methods public func encodeNil(forKey key: Key) throws { - throw JavaCodingError.notSupported("JavaObjectContainer.encodeNil(forKey: \(key)") + throw EncodingError.invalidValue(NSNotFound, EncodingError.Context(codingPath: codingPath, debugDescription: "Nil not supported")) } - + + func encode(_ value: Bool, forKey key: K) throws { + try encodeBoolean(try value.javaPrimitive(), key: key.stringValue) + } + + func encode(_ value: Double, forKey key: K) throws { + try encodeDouble(jdouble(value), key: key.stringValue) + } + + func encode(_ value: Float, forKey key: K) throws { + try encodeFloat(jfloat(value), key: key.stringValue) + } + + func encode(_ value: Int, forKey key: K) throws { + try encodeInteger(try value.javaPrimitive(codingPath: codingPath + [key]), key: key.stringValue) + } + + func encode(_ value: Int8, forKey key: K) throws { + try encodeByte(try value.javaPrimitive(), key: key.stringValue) + } + + func encode(_ value: Int16, forKey key: K) throws { + try encodeShort(try value.javaPrimitive(), key: key.stringValue) + } + + func encode(_ value: Int32, forKey key: K) throws { + try encodeInteger(try value.javaPrimitive(), key: key.stringValue) + } + + func encode(_ value: Int64, forKey key: K) throws { + try encodeLong(try value.javaPrimitive(), key: key.stringValue) + } + + func encode(_ value: UInt, forKey key: K) throws { + try encodeInteger(try value.javaPrimitive(codingPath: codingPath + [key]), key: key.stringValue) + } + + func encode(_ value: UInt8, forKey key: K) throws { + try encodeByte(try value.javaPrimitive(), key: key.stringValue) + } + + func encode(_ value: UInt16, forKey key: K) throws { + try encodeShort(try value.javaPrimitive(), key: key.stringValue) + } + + func encode(_ value: UInt32, forKey key: K) throws { + try encodeInteger(try value.javaPrimitive(), key: key.stringValue) + } + + func encode(_ value: UInt64, forKey key: K) throws { + try encodeLong(try value.javaPrimitive(), key: key.stringValue) + } + + func encodeIfPresent(_ value: Bool?, forKey key: K) throws { + if let value = value { + try encodeObject(value, forKey: key) + } + } + + func encodeIfPresent(_ value: String?, forKey key: K) throws { + if let value = value { + try encodeObject(value, forKey: key) + } + } + + func encodeIfPresent(_ value: Double?, forKey key: K) throws { + if let value = value { + try encodeObject(value, forKey: key) + } + } + + func encodeIfPresent(_ value: Float?, forKey key: K) throws { + if let value = value { + try encodeObject(value, forKey: key) + } + } + + func encodeIfPresent(_ value: Int?, forKey key: K) throws { + if let value = value { + try encodeObject(value, forKey: key) + } + } + + func encodeIfPresent(_ value: Int8?, forKey key: K) throws { + if let value = value { + try encodeObject(value, forKey: key) + } + } + + func encodeIfPresent(_ value: Int16?, forKey key: K) throws { + if let value = value { + try encodeObject(value, forKey: key) + } + } + + func encodeIfPresent(_ value: Int32?, forKey key: K) throws { + if let value = value { + try encodeObject(value, forKey: key) + } + } + + func encodeIfPresent(_ value: Int64?, forKey key: K) throws { + if let value = value { + try encodeObject(value, forKey: key) + } + } + + func encodeIfPresent(_ value: UInt?, forKey key: K) throws { + if let value = value { + try encodeObject(value, forKey: key) + } + } + + func encodeIfPresent(_ value: UInt8?, forKey key: K) throws { + if let value = value { + try encodeObject(value, forKey: key) + } + } + + func encodeIfPresent(_ value: UInt16?, forKey key: K) throws { + if let value = value { + try encodeObject(value, forKey: key) + } + } + + func encodeIfPresent(_ value: UInt32?, forKey key: K) throws { + if let value = value { + try encodeObject(value, forKey: key) + } + } + + func encodeIfPresent(_ value: UInt64?, forKey key: K) throws { + if let value = value { + try encodeObject(value, forKey: key) + } + } + public func encode(_ value: T, forKey key: Key) throws { + try self.encodeObject(value, forKey: key) + } + + private func encodeObject(_ value: T, forKey key: Key) throws { do { - let object = try self.encoder.box(value) + let object = try self.encoder.box(value, codingPath: codingPath + [key]) let filed = try JNI.getJavaField(forClass: self.javaClass, field: key.stringValue, sig: object.type.sig) - JNI.api.SetObjectField(JNI.env, self.javaObject, filed, object.javaObject) + let javaObject = jniStorage.javaObject + let javaField = object.javaObject + JNI.api.SetObjectField(JNI.env, javaObject, filed, javaField) + JNI.DeleteLocalRef(javaObject) + JNI.DeleteLocalRef(javaField) } catch { if self.encoder.missingFieldsStrategy == .ignore { @@ -254,26 +480,28 @@ fileprivate class JavaHashMapKeyedContainer : KeyedEncodingContai self.jniStorage = jniStorage } - private var javaObject: jobject { - return jniStorage.javaObject - } - // MARK: - KeyedEncodingContainerProtocol Methods public func encodeNil(forKey key: Key) throws { - throw JavaCodingError.notSupported("JavaHashMapContainer.encodeNil(forKey: \(key))") + throw EncodingError.invalidValue(NSNotFound, EncodingError.Context(codingPath: codingPath, debugDescription: "Nil not supported")) } public func encode(_ value: T, forKey key: Key) throws { let keyStorage: JNIStorageObject if let intValue = key.intValue { - keyStorage = try self.encoder.box(intValue) + keyStorage = try self.encoder.box(intValue, codingPath: codingPath + [key]) } else { - keyStorage = try self.encoder.box(key.stringValue) + keyStorage = try self.encoder.box(key.stringValue, codingPath: codingPath + [key]) } - let valueStorage = try self.encoder.box(value) - let result = JNI.CallObjectMethod(javaObject, methodID: HashMapPutMethod, args: [jvalue(l: keyStorage.javaObject), jvalue(l: valueStorage.javaObject)]) + let valueStorage = try self.encoder.box(value, codingPath: codingPath + [key]) + let javaObject = jniStorage.javaObject + let javaKey = keyStorage.javaObject + let javaValue = valueStorage.javaObject + let result = JNI.CallObjectMethod(javaObject!, methodID: HashMapPutMethod, args: [jvalue(l: javaKey), jvalue(l: javaValue)]) + JNI.DeleteLocalRef(javaObject) + JNI.DeleteLocalRef(javaKey) + JNI.DeleteLocalRef(javaValue) assert(result == nil, "Rewrite for key \(key.stringValue)") } @@ -317,19 +545,22 @@ fileprivate class JavaHashMapUnkeyedContainer : UnkeyedEncodingContainer { self.jniStorage = jniStorage } - private var javaObject: jobject { - return jniStorage.javaObject - } - // MARK: - UnkeyedEncodingContainer Methods public func encodeNil() throws { - throw JavaCodingError.notSupported("JavaArrayContainer.encodeNil") + throw EncodingError.invalidValue(NSNotFound, EncodingError.Context(codingPath: codingPath, debugDescription: "Nil not supported")) } public func encode(_ value: T) throws { - let javaValue = try self.encoder.box(value) + let indexKey = JavaKey(index: count) + let javaValue = try self.encoder.box(value, codingPath: codingPath + [indexKey]) if let javaKey = self.javaKey { - let result = JNI.CallObjectMethod(javaObject, methodID: HashMapPutMethod, args: [jvalue(l: javaKey.javaObject), jvalue(l: javaValue.javaObject)]) + let javaObject = jniStorage.javaObject + let javaKey = javaKey.javaObject + let javaValue = javaValue.javaObject + let result = JNI.CallObjectMethod(javaObject!, methodID: HashMapPutMethod, args: [jvalue(l: javaKey), jvalue(l: javaValue)]) + JNI.DeleteLocalRef(javaObject) + JNI.DeleteLocalRef(javaKey) + JNI.DeleteLocalRef(javaValue) assert(result == nil, "Rewrite for key") self.javaKey = nil } @@ -373,18 +604,19 @@ fileprivate class JavaArrayContainer : UnkeyedEncodingContainer { self.jniStorage = jniStorage } - private var javaObject: jobject { - return jniStorage.javaObject - } - // MARK: - UnkeyedEncodingContainer Methods public func encodeNil() throws { - throw JavaCodingError.notSupported("JavaArrayContainer.encodeNil") + throw EncodingError.invalidValue(NSNotFound, EncodingError.Context(codingPath: codingPath, debugDescription: "Nil not supported")) } public func encode(_ value: T) throws { - let storeObject = try self.encoder.box(value) - let rewrite = JNI.CallBooleanMethod(self.javaObject, methodID: CollectionAddMethod, args: [jvalue(l: storeObject.javaObject)]) + let indexKey = JavaKey(index: count) + let storeObject = try self.encoder.box(value, codingPath: codingPath + [indexKey]) + let javaObject = jniStorage.javaObject + let javaNewValue = storeObject.javaObject + let rewrite = JNI.CallBooleanMethod(javaObject!, methodID: CollectionAddMethod, args: [jvalue(l: javaNewValue!)]) + JNI.DeleteLocalRef(javaObject) + JNI.DeleteLocalRef(javaNewValue) assert(rewrite == JNI.TRUE, "ArrayList should always return true from add()") count += 1 } @@ -418,27 +650,93 @@ class JavaEnumValueEncodingContainer: SingleValueEncodingContainer { } public func encodeNil() throws { - throw JavaCodingError.notSupported("JavaSingleValueEncodingContainer.encodeNil") + throw EncodingError.invalidValue(NSNotFound, EncodingError.Context(codingPath: codingPath, debugDescription: "Nil not supported")) } - - public func encode(_ value: T) throws { - let rawValue = try self.encoder.box(value) + + public func encode(_ value: Int8) throws { + try encode(jvalue(b: value.javaPrimitive()), sig: "B") + } + + public func encode(_ value: Int16) throws { + try encode(jvalue(s: value.javaPrimitive()), sig: "S") + } + + public func encode(_ value: Int32) throws { + try encode(jvalue(i: value.javaPrimitive()), sig: "I") + } + + public func encode(_ value: Int64) throws { + try encode(jvalue(j: value.javaPrimitive()), sig: "J") + } + + public func encode(_ value: Int) throws { + try encode(jvalue(i: value.javaPrimitive(codingPath: codingPath)), sig: "I") + } + + public func encode(_ value: UInt8) throws { + try encode(jvalue(b: value.javaPrimitive()), sig: "B") + } + + public func encode(_ value: UInt16) throws { + try encode(jvalue(s: value.javaPrimitive()), sig: "S") + } + + public func encode(_ value: UInt32) throws { + try encode(jvalue(i: value.javaPrimitive()), sig: "I") + } + + public func encode(_ value: UInt64) throws { + try encode(jvalue(j: value.javaPrimitive()), sig: "J") + } + + public func encode(_ value: UInt) throws { + try encode(jvalue(i: value.javaPrimitive(codingPath: codingPath)), sig: "I") + } + + public func encode(_ value: jvalue, sig: String) throws { + let clazz = try JNI.getJavaClass(javaClass) + // If jniStorage.javaObject == nil its enum, else optionSet + if let javaObject = jniStorage.javaObject { + let filed = try JNI.getJavaField(forClass: self.javaClass, field: "rawValue", sig: sig) + let setterFunc = setterFuncMap[sig] + setterFunc?(javaObject, filed, value) + JNI.DeleteLocalRef(javaObject) + } + else { + let valueOfMethodID = try JNI.getStaticJavaMethod(forClass: javaClass, method: "valueOf", sig: "(\(sig))L\(javaClass);") + guard let javaObject = JNI.CallStaticObjectMethod(clazz, methodID: valueOfMethodID, args: [value]) else { + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, + debugDescription: "Nil not supported: \\(javaClass).valueOf()")) + } + jniStorage.javaObject = javaObject + } + } + + private let setterFuncMap: [String: (jobject, jfieldID, jvalue) -> Void] = [ + "B": { _ = JNI.api.SetByteField(JNI.env, $0, $1, $2.b) }, + "S": { _ = JNI.api.SetShortField(JNI.env, $0, $1, $2.s) }, + "I": { _ = JNI.api.SetIntField(JNI.env, $0, $1, $2.i) }, + "J": { _ = JNI.api.SetLongField(JNI.env, $0, $1, $2.j) } + ] + + public func encode(_ valueType: T) throws { + let rawValue = try encoder.box(valueType, codingPath: codingPath) let clazz = try JNI.getJavaClass(javaClass) // If jniStorage.javaObject == nil its enum, else optionSet - if jniStorage.javaObject == nil { + if jniStorage.hasJavaObject == false { let valueOfMethodID = try JNI.getStaticJavaMethod(forClass: javaClass, method: "valueOf", sig: "(\(rawValue.type.sig))L\(javaClass);") - JNI.SaveFatalErrorMessage("\(javaClass) valueOf \(rawValue.type.sig)") + let javaRawValue = rawValue.javaObject defer { - JNI.RemoveFatalErrorMessage() + JNI.DeleteLocalRef(javaRawValue) } - guard let javaObject = JNI.CallStaticObjectMethod(clazz, methodID: valueOfMethodID, args: [jvalue(l: rawValue.javaObject)]) else { - throw JavaCodingError.nilNotSupported("\(javaClass).valueOf()") + guard let javaObject = JNI.CallStaticObjectMethod(clazz, methodID: valueOfMethodID, args: [jvalue(l: javaRawValue)]) else { + throw JavaCodingError.cantCreateObject(javaClass) } jniStorage.javaObject = javaObject } else { - let filed = try JNI.getJavaField(forClass: self.javaClass, field: "rawValue", sig: rawValue.type.sig) - JNI.api.SetObjectField(JNI.env, self.jniStorage.javaObject, filed, rawValue.javaObject) + let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Unsupported: type \(type(of: valueType))") + throw EncodingError.invalidValue(valueType, context) } } } @@ -464,13 +762,9 @@ fileprivate class JavaAnyCodableContainer : KeyedEncodingContaine self.jniStorage = jniStorage } - private var javaObject: jobject { - return jniStorage.javaObject - } - // MARK: - KeyedEncodingContainerProtocol Methods public func encodeNil(forKey key: Key) throws { - throw JavaCodingError.notSupported("JavaObjectContainer.encodeNil(forKey: \(key)") + throw EncodingError.invalidValue(NSNotFound, EncodingError.Context(codingPath: codingPath, debugDescription: "Nil not supported")) } public func encode(_ value: T, forKey key: Key) throws { @@ -479,8 +773,8 @@ fileprivate class JavaAnyCodableContainer : KeyedEncodingContaine return } do { - let jniObject = try self.encoder.box(value) - self.jniStorage.javaObject = JNI.api.NewLocalRef(JNI.env, jniObject.javaObject) + let jniObject = try self.encoder.box(value, codingPath: codingPath + [key]) + self.jniStorage.javaObject = jniObject.javaObject } catch { if self.encoder.missingFieldsStrategy == .ignore { @@ -523,18 +817,18 @@ fileprivate class JavaAnyCodableContainer : KeyedEncodingContaine extension JavaEncoder { - fileprivate func box(_ value: T) throws -> JNIStorageObject { + fileprivate func box(_ value: T, codingPath: [CodingKey]) throws -> JNIStorageObject { let storage: JNIStorageObject let typeName = String(describing: type(of: value)) if let encodableClosure = JavaCoderConfig.encodableClosures[typeName] { - let javaObject = try encodableClosure(value) + let javaObject = try encodableClosure(value, codingPath) storage = JNIStorageObject(type: .object(className: JavaCoderConfig.codableClassNames[typeName]!), javaObject: javaObject) } else if T.self == AnyCodable.self { let anyCodableValue = value as! AnyCodable if let javaClassname = JavaCoderConfig.codableClassNames[anyCodableValue.typeName] { let encodableClosure = JavaCoderConfig.encodableClosures[anyCodableValue.typeName]! - let javaObject = try encodableClosure(anyCodableValue.value) + let javaObject = try encodableClosure(anyCodableValue.value, codingPath) storage = JNIStorageObject(type: .object(className: javaClassname), javaObject: javaObject) } else { diff --git a/Sources/JavaPrimitive.swift b/Sources/JavaPrimitive.swift new file mode 100644 index 0000000..d043e66 --- /dev/null +++ b/Sources/JavaPrimitive.swift @@ -0,0 +1,264 @@ +// +// Created by Andriy Druk on 24.01.2020. +// + +import Foundation +import java_swift +import CJavaVM + +public typealias JavaBoolean = jboolean +public typealias JavaByte = jbyte +public typealias JavaShort = jshort +public typealias JavaInt = jint +public typealias JavaLong = jlong + +#if arch(arm) +// Looks like calling convention for ARM32 is broken: probably soft-float vs hard-float +// https://android.googlesource.com/platform/ndk/+/master/docs/HardFloatAbi.md +// We will replace jfloat with 4-byte jint bit pattern +public typealias JavaFloat = jint +#else +public typealias JavaFloat = jfloat +#endif +#if arch(arm) +// We will replace jdouble with 8-byte jlong bit pattern +public typealias JavaDouble = jlong +#else +public typealias JavaDouble = jdouble +#endif + +extension Bool { + + public init(fromJavaPrimitive javaPrimitive: JavaBoolean) { + self.init(javaPrimitive == JNI_TRUE) + } + + public func javaPrimitive() throws -> JavaBoolean { + return jboolean(self ? JNI_TRUE : JNI_FALSE) + } +} + +extension Int { + + public init(fromJavaPrimitive javaPrimitive: JavaInt) { + self.init(javaPrimitive) + } + + public func javaPrimitive(codingPath: [CodingKey] = []) throws -> JavaInt { + if self < Int(Int32.min) || self > Int(Int32.max) { + let errorDescription = "Not enough bits to represent Int" + let context = EncodingError.Context(codingPath: codingPath, debugDescription: errorDescription) + throw EncodingError.invalidValue(self, context) + } + return jint(self) + } +} + +extension Int8 { + + public init(fromJavaPrimitive javaPrimitive: JavaByte) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> JavaByte { + return jbyte(self) + } +} + +extension Int16 { + + public init(fromJavaPrimitive javaPrimitive: JavaShort) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> JavaShort { + return jshort(self) + } +} + +extension Int32 { + + public init(fromJavaPrimitive javaPrimitive: JavaInt) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> JavaInt { + return jint(self) + } +} + +extension Int64 { + + public init(fromJavaPrimitive javaPrimitive: JavaLong) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> JavaLong { + return jlong(self) + } +} + +extension UInt { + + public init(fromJavaPrimitive javaPrimitive: JavaInt) { + #if arch(x86_64) || arch(arm64) + self.init(UInt32(bitPattern: javaPrimitive)) + #else + self.init(bitPattern: javaPrimitive) + #endif + } + + public func javaPrimitive(codingPath: [CodingKey] = []) throws -> JavaInt { + if self < UInt(UInt32.min) || self > UInt(UInt32.max) { + let errorDescription = "Not enough bits to represent UInt" + let context = EncodingError.Context(codingPath: codingPath, debugDescription: errorDescription) + throw EncodingError.invalidValue(self, context) + } + #if arch(x86_64) || arch(arm64) + return jint(bitPattern: UInt32(self)) + #else + return jint(bitPattern: self) + #endif + } +} + +extension UInt8 { + + public init(fromJavaPrimitive javaPrimitive: JavaByte) { + self.init(bitPattern: javaPrimitive) + } + + public func javaPrimitive() throws -> JavaByte { + return jbyte(bitPattern: self) + } +} + +extension UInt16 { + + public init(fromJavaPrimitive javaPrimitive: JavaShort) { + self.init(bitPattern: javaPrimitive) + } + + public func javaPrimitive() throws -> JavaShort { + return jshort(bitPattern: self) + } +} + +extension UInt32 { + + public init(fromJavaPrimitive javaPrimitive: JavaInt) { + #if arch(x86_64) || arch(arm64) + self.init(bitPattern: javaPrimitive) + #else + self.init(UInt(bitPattern: javaPrimitive)) + #endif + } + + public func javaPrimitive() throws -> JavaInt { + #if arch(x86_64) || arch(arm64) + return jint(bitPattern: self) + #else + return jint(bitPattern: UInt(self)) + #endif + } +} + +extension UInt64 { + + public init(fromJavaPrimitive javaPrimitive: JavaLong) { + self.init(bitPattern: javaPrimitive) + } + + public func javaPrimitive() throws -> JavaLong { + return jlong(bitPattern: self) + } +} + +extension Float { + + public init(fromJavaPrimitive javaPrimitive: jfloat) { + self.init(javaPrimitive) + } + + #if arch(arm) + public init(fromJavaPrimitive javaPrimitive: jint) { + self.init(bitPattern: UInt32(bitPattern: Int32(javaPrimitive))) + } + #endif + + public func javaPrimitive() throws -> JavaFloat { + #if arch(arm) + return jint(Int32(bitPattern: bitPattern)) + #else + return self + #endif + } +} + +extension Double { + + public init(fromJavaPrimitive javaPrimitive: jdouble) { + self.init(javaPrimitive) + } + + #if arch(arm) + public init(fromJavaPrimitive javaPrimitive: jlong) { + self.init(bitPattern: UInt64(javaPrimitive)) + } + #endif + + public func javaPrimitive() throws -> JavaDouble { + #if arch(arm) + return jlong(bitPattern: bitPattern) + #else + return self + #endif + } +} + + + +extension JavaBoolean { + public static func defaultValue() -> JavaBoolean { + return jboolean(JNI_FALSE) + } +} + +extension JavaByte { + public static func defaultValue() -> JavaByte { + return 0 + } +} + +extension JavaShort { + public static func defaultValue() -> JavaShort { + return 0 + } +} + +extension JavaInt { + public static func defaultValue() -> JavaInt { + return 0 + } +} + +extension JavaLong { + public static func defaultValue() -> JavaLong { + return 0 + } +} + +#if arch(arm) +#else +extension JavaFloat { + public static func defaultValue() -> JavaFloat { + return 0 + } +} + +extension JavaDouble { + public static func defaultValue() -> JavaDouble { + return 0 + } +} +#endif \ No newline at end of file