From 5b7dd458e6c084ed6c71760e90e226c0b653a5f5 Mon Sep 17 00:00:00 2001 From: Andrew Druk Date: Tue, 25 Feb 2020 14:10:18 +0200 Subject: [PATCH 01/12] Dev: add java primitive support --- Sources/JNICore+JavaCoder.swift | 60 +++++++++++++++++++++ Sources/JavaDecoder.swift | 92 +++++++++++++++++++++++++++++++-- Sources/JavaEncoder.swift | 90 +++++++++++++++++++++++++++++++- Sources/JavaPrimitive.swift | 85 ++++++++++++++++++++++++++++++ 4 files changed, 323 insertions(+), 4 deletions(-) create mode 100644 Sources/JavaPrimitive.swift diff --git a/Sources/JNICore+JavaCoder.swift b/Sources/JNICore+JavaCoder.swift index d251889..8393438 100644 --- a/Sources/JNICore+JavaCoder.swift +++ b/Sources/JNICore+JavaCoder.swift @@ -335,6 +335,66 @@ 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 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/JavaDecoder.swift b/Sources/JavaDecoder.swift index c9500fd..60c4e14 100644 --- a/Sources/JavaDecoder.swift +++ b/Sources/JavaDecoder.swift @@ -128,13 +128,99 @@ 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") + 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") + 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") + 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") + return JNI.api.GetIntField(JNI.env, javaObject, fieldID) + } + + private func decodeLong(forKey key: String) throws -> Int64 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "J") + 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") + 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") + 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(try decodeInteger(forKey: key.stringValue)) + } + + public func decode(_ type: UInt8.Type, forKey key: K) throws -> UInt8 { + return UInt8(try decodeByte(forKey: key.stringValue)) + } + + public func decode(_ type: UInt16.Type, forKey key: K) throws -> UInt16 { + return UInt16(try decodeShort(forKey: key.stringValue)) + } + + public func decode(_ type: UInt32.Type, forKey key: K) throws -> UInt32 { + return UInt32(try decodeInteger(forKey: key.stringValue)) + } + + public func decode(_ type: UInt64.Type, forKey key: K) throws -> UInt64 { + return UInt64(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) diff --git a/Sources/JavaEncoder.swift b/Sources/JavaEncoder.swift index 022444e..9014376 100644 --- a/Sources/JavaEncoder.swift +++ b/Sources/JavaEncoder.swift @@ -191,12 +191,100 @@ fileprivate class JavaObjectContainer : KeyedEncodingContainerPro private var javaObject: jobject { return jniStorage.javaObject } + + // MARK: Encode JNI primitive fields + func encodeBoolean(_ value: Bool, key: String) throws { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "Z") + JNI.api.SetBooleanField(JNI.env, javaObject, fieldID, jboolean(value ? JNI_TRUE : JNI_FALSE)) + } + + func encodeByte(_ value: Int8, key: String) throws { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "B") + JNI.api.SetByteField(JNI.env, javaObject, fieldID, value) + } + + func encodeShort(_ value: Int16, key: String) throws { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "S") + JNI.api.SetShortField(JNI.env, javaObject, fieldID, value) + } + + func encodeInteger(_ value: Int32, key: String) throws { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "I") + JNI.api.SetIntField(JNI.env, javaObject, fieldID, value) + } + + func encodeLong(_ value: Int64, key: String) throws { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "J") + JNI.api.SetLongField(JNI.env, javaObject, fieldID, value) + } + + func encodeFloat(_ value: Float, key: String) throws { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "F") + JNI.api.SetFloatField(JNI.env, javaObject, fieldID, value) + } + + func encodeDouble(_ value: Double, key: String) throws { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "D") + JNI.api.SetDoubleField(JNI.env, javaObject, fieldID, value) + } // MARK: - KeyedEncodingContainerProtocol Methods public func encodeNil(forKey key: Key) throws { throw JavaCodingError.notSupported("JavaObjectContainer.encodeNil(forKey: \(key)") } - + + func encode(_ value: Bool, forKey key: K) throws { + try encodeBoolean(value, key: key.stringValue) + } + + func encode(_ value: Double, forKey key: K) throws { + try encodeDouble(value, key: key.stringValue) + } + + func encode(_ value: Float, forKey key: K) throws { + try encodeFloat(value, key: key.stringValue) + } + + func encode(_ value: Int, forKey key: K) throws { + try encodeInteger(Int32(value), key: key.stringValue) + } + + func encode(_ value: Int8, forKey key: K) throws { + try encodeByte(value, key: key.stringValue) + } + + func encode(_ value: Int16, forKey key: K) throws { + try encodeShort(value, key: key.stringValue) + } + + func encode(_ value: Int32, forKey key: K) throws { + try encodeInteger(value, key: key.stringValue) + } + + func encode(_ value: Int64, forKey key: K) throws { + try encodeLong(value, key: key.stringValue) + } + + func encode(_ value: UInt, forKey key: K) throws { + try encodeInteger(Int32(bitPattern: UInt32(value)), key: key.stringValue) + } + + func encode(_ value: UInt8, forKey key: K) throws { + try encodeByte(Int8(bitPattern: value), key: key.stringValue) + } + + func encode(_ value: UInt16, forKey key: K) throws { + try encodeShort(Int16(bitPattern: value), key: key.stringValue) + } + + func encode(_ value: UInt32, forKey key: K) throws { + try encodeInteger(Int32(bitPattern: value), key: key.stringValue) + } + + func encode(_ value: UInt64, forKey key: K) throws { + try encodeLong(Int64(bitPattern: value), key: key.stringValue) + } + public func encode(_ value: T, forKey key: Key) throws { do { let object = try self.encoder.box(value) diff --git a/Sources/JavaPrimitive.swift b/Sources/JavaPrimitive.swift new file mode 100644 index 0000000..f3d9d36 --- /dev/null +++ b/Sources/JavaPrimitive.swift @@ -0,0 +1,85 @@ +// +// Created by Andriy Druk on 24.01.2020. +// + +import Foundation +import java_swift +import CJavaVM + +public protocol JavaPrimitive: Codable { + + func javaPrimitive() -> JNIArgumentProtocol + +} + +extension Bool: JavaPrimitive { + public func javaPrimitive() -> JNIArgumentProtocol { + return jboolean(self ? JNI_TRUE : JNI_FALSE) + } +} + +extension Int: JavaPrimitive { + public func javaPrimitive() -> JNIArgumentProtocol { + return jint(self) + } +} + +extension Int8: JavaPrimitive { + public func javaPrimitive() -> JNIArgumentProtocol { + return jbyte(self) + } +} + +extension Int16: JavaPrimitive { + public func javaPrimitive() -> JNIArgumentProtocol { + return jshort(self) + } +} + +extension Int32: JavaPrimitive { + public func javaPrimitive() -> JNIArgumentProtocol { + return jint(self) + } +} + +extension Int64: JavaPrimitive { + public func javaPrimitive() -> JNIArgumentProtocol { + return jlong(self) + } +} + +extension UInt: JavaPrimitive { + public func javaPrimitive() -> JNIArgumentProtocol { + return jlong(self) + } +} + +extension UInt8: JavaPrimitive { + public func javaPrimitive() -> JNIArgumentProtocol { + return jshort(self) + } +} + +extension UInt16: JavaPrimitive { + public func javaPrimitive() -> JNIArgumentProtocol { + return jint(self) + } +} + +extension UInt32: JavaPrimitive { + public func javaPrimitive() -> JNIArgumentProtocol { + return jlong(self) + } +} + +extension Float: JavaPrimitive { + public func javaPrimitive() -> JNIArgumentProtocol { + return jfloat(self) + } +} + +extension Double: JavaPrimitive { + public func javaPrimitive() -> JNIArgumentProtocol { + return jdouble(self) + } +} \ No newline at end of file From ca0014b6ebc8de61ad420b6922e829098f7c9d7f Mon Sep 17 00:00:00 2001 From: Andrew Druk Date: Fri, 6 Mar 2020 17:56:21 +0200 Subject: [PATCH 02/12] Dev: fix optional encoding of primitives --- Sources/JavaEncoder.swift | 88 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/Sources/JavaEncoder.swift b/Sources/JavaEncoder.swift index 9014376..9494fa0 100644 --- a/Sources/JavaEncoder.swift +++ b/Sources/JavaEncoder.swift @@ -285,7 +285,95 @@ fileprivate class JavaObjectContainer : KeyedEncodingContainerPro try encodeLong(Int64(bitPattern: value), 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 filed = try JNI.getJavaField(forClass: self.javaClass, field: key.stringValue, sig: object.type.sig) From 20c43bb6ece3c2d63204f7dac5ab15cdfc3ca5e9 Mon Sep 17 00:00:00 2001 From: Andrew Druk Date: Wed, 11 Mar 2020 00:52:43 +0200 Subject: [PATCH 03/12] Dev: fix primitive encoding --- Sources/JavaEncoder.swift | 42 ++++++------ Sources/JavaPrimitive.swift | 126 +++++++++++++++++++++++++++--------- 2 files changed, 118 insertions(+), 50 deletions(-) diff --git a/Sources/JavaEncoder.swift b/Sources/JavaEncoder.swift index 9494fa0..6e025da 100644 --- a/Sources/JavaEncoder.swift +++ b/Sources/JavaEncoder.swift @@ -193,37 +193,37 @@ fileprivate class JavaObjectContainer : KeyedEncodingContainerPro } // MARK: Encode JNI primitive fields - func encodeBoolean(_ value: Bool, key: String) throws { + func encodeBoolean(_ value: jboolean, key: String) throws { let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "Z") - JNI.api.SetBooleanField(JNI.env, javaObject, fieldID, jboolean(value ? JNI_TRUE : JNI_FALSE)) + JNI.api.SetBooleanField(JNI.env, javaObject, fieldID, value) } - func encodeByte(_ value: Int8, key: String) throws { + func encodeByte(_ value: jbyte, key: String) throws { let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "B") JNI.api.SetByteField(JNI.env, javaObject, fieldID, value) } - func encodeShort(_ value: Int16, key: String) throws { + func encodeShort(_ value: jshort, key: String) throws { let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "S") JNI.api.SetShortField(JNI.env, javaObject, fieldID, value) } - func encodeInteger(_ value: Int32, key: String) throws { + func encodeInteger(_ value: jint, key: String) throws { let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "I") JNI.api.SetIntField(JNI.env, javaObject, fieldID, value) } - func encodeLong(_ value: Int64, key: String) throws { + func encodeLong(_ value: jlong, key: String) throws { let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "J") JNI.api.SetLongField(JNI.env, javaObject, fieldID, value) } - func encodeFloat(_ value: Float, key: String) throws { + func encodeFloat(_ value: jfloat, key: String) throws { let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "F") JNI.api.SetFloatField(JNI.env, javaObject, fieldID, value) } - func encodeDouble(_ value: Double, key: String) throws { + func encodeDouble(_ value: jdouble, key: String) throws { let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "D") JNI.api.SetDoubleField(JNI.env, javaObject, fieldID, value) } @@ -234,55 +234,55 @@ fileprivate class JavaObjectContainer : KeyedEncodingContainerPro } func encode(_ value: Bool, forKey key: K) throws { - try encodeBoolean(value, key: key.stringValue) + try encodeBoolean(try value.javaPrimitive(), key: key.stringValue) } func encode(_ value: Double, forKey key: K) throws { - try encodeDouble(value, key: key.stringValue) + try encodeDouble(try value.javaPrimitive(), key: key.stringValue) } func encode(_ value: Float, forKey key: K) throws { - try encodeFloat(value, key: key.stringValue) + try encodeFloat(try value.javaPrimitive(), key: key.stringValue) } func encode(_ value: Int, forKey key: K) throws { - try encodeInteger(Int32(value), key: key.stringValue) + try encodeInteger(try value.javaPrimitive(), key: key.stringValue) } func encode(_ value: Int8, forKey key: K) throws { - try encodeByte(value, key: key.stringValue) + try encodeByte(try value.javaPrimitive(), key: key.stringValue) } func encode(_ value: Int16, forKey key: K) throws { - try encodeShort(value, key: key.stringValue) + try encodeShort(try value.javaPrimitive(), key: key.stringValue) } func encode(_ value: Int32, forKey key: K) throws { - try encodeInteger(value, key: key.stringValue) + try encodeInteger(try value.javaPrimitive(), key: key.stringValue) } func encode(_ value: Int64, forKey key: K) throws { - try encodeLong(value, key: key.stringValue) + try encodeLong(try value.javaPrimitive(), key: key.stringValue) } func encode(_ value: UInt, forKey key: K) throws { - try encodeInteger(Int32(bitPattern: UInt32(value)), key: key.stringValue) + try encodeInteger(try value.javaPrimitive(), key: key.stringValue) } func encode(_ value: UInt8, forKey key: K) throws { - try encodeByte(Int8(bitPattern: value), key: key.stringValue) + try encodeByte(try value.javaPrimitive(), key: key.stringValue) } func encode(_ value: UInt16, forKey key: K) throws { - try encodeShort(Int16(bitPattern: value), key: key.stringValue) + try encodeShort(try value.javaPrimitive(), key: key.stringValue) } func encode(_ value: UInt32, forKey key: K) throws { - try encodeInteger(Int32(bitPattern: value), key: key.stringValue) + try encodeInteger(try value.javaPrimitive(), key: key.stringValue) } func encode(_ value: UInt64, forKey key: K) throws { - try encodeLong(Int64(bitPattern: value), key: key.stringValue) + try encodeLong(try value.javaPrimitive(), key: key.stringValue) } func encodeIfPresent(_ value: Bool?, forKey key: K) throws { diff --git a/Sources/JavaPrimitive.swift b/Sources/JavaPrimitive.swift index f3d9d36..7118abd 100644 --- a/Sources/JavaPrimitive.swift +++ b/Sources/JavaPrimitive.swift @@ -6,80 +6,148 @@ import Foundation import java_swift import CJavaVM -public protocol JavaPrimitive: Codable { +extension Bool { - func javaPrimitive() -> JNIArgumentProtocol - -} + public init(fromJavaPrimitive javaPrimitive: jboolean) { + self.init(javaPrimitive == JNI_TRUE) + } -extension Bool: JavaPrimitive { - public func javaPrimitive() -> JNIArgumentProtocol { + public func javaPrimitive() throws -> jboolean { return jboolean(self ? JNI_TRUE : JNI_FALSE) } } -extension Int: JavaPrimitive { - public func javaPrimitive() -> JNIArgumentProtocol { +extension Int { + + public init(fromJavaPrimitive javaPrimitive: jint) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> jint { + if self > Int(Int32.max) || self < Int(Int32.min) { + throw JavaCodingError.notEnoughBitsToRepresent("Int \(self)") + } return jint(self) } } -extension Int8: JavaPrimitive { - public func javaPrimitive() -> JNIArgumentProtocol { +extension Int8 { + + public init(fromJavaPrimitive javaPrimitive: jbyte) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> jbyte { return jbyte(self) } } -extension Int16: JavaPrimitive { - public func javaPrimitive() -> JNIArgumentProtocol { +extension Int16 { + + public init(fromJavaPrimitive javaPrimitive: jshort) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> jshort { return jshort(self) } } -extension Int32: JavaPrimitive { - public func javaPrimitive() -> JNIArgumentProtocol { +extension Int32 { + + public init(fromJavaPrimitive javaPrimitive: jint) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> jint { return jint(self) } } -extension Int64: JavaPrimitive { - public func javaPrimitive() -> JNIArgumentProtocol { +extension Int64 { + + public init(fromJavaPrimitive javaPrimitive: jlong) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> jlong { return jlong(self) } } -extension UInt: JavaPrimitive { - public func javaPrimitive() -> JNIArgumentProtocol { - return jlong(self) +extension UInt { + + public init(fromJavaPrimitive javaPrimitive: jint) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> jint { + return jint(self) } } -extension UInt8: JavaPrimitive { - public func javaPrimitive() -> JNIArgumentProtocol { +extension UInt8 { + + public init(fromJavaPrimitive javaPrimitive: jbyte) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> jbyte { + return jbyte(self) + } +} + +extension UInt16 { + + public init(fromJavaPrimitive javaPrimitive: jshort) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> jshort { return jshort(self) } } -extension UInt16: JavaPrimitive { - public func javaPrimitive() -> JNIArgumentProtocol { +extension UInt32 { + + public init(fromJavaPrimitive javaPrimitive: jint) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> jint { return jint(self) } } -extension UInt32: JavaPrimitive { - public func javaPrimitive() -> JNIArgumentProtocol { +extension UInt64 { + + public init(fromJavaPrimitive javaPrimitive: jlong) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> jlong { return jlong(self) } } -extension Float: JavaPrimitive { - public func javaPrimitive() -> JNIArgumentProtocol { +extension Float { + + public init(fromJavaPrimitive javaPrimitive: jfloat) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> jfloat { return jfloat(self) } } -extension Double: JavaPrimitive { - public func javaPrimitive() -> JNIArgumentProtocol { +extension Double { + + public init(fromJavaPrimitive javaPrimitive: jdouble) { + self.init(javaPrimitive) + } + + public func javaPrimitive() throws -> jdouble { return jdouble(self) } } \ No newline at end of file From e4a9fc7ff7c755e6faa75b8d6a6143843a1c4134 Mon Sep 17 00:00:00 2001 From: Andrew Druk Date: Wed, 11 Mar 2020 00:53:06 +0200 Subject: [PATCH 04/12] Dev: add errorDescription for JavaCodingError --- Sources/JavaEncoder.swift | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Sources/JavaEncoder.swift b/Sources/JavaEncoder.swift index 6e025da..433ccf1 100644 --- a/Sources/JavaEncoder.swift +++ b/Sources/JavaEncoder.swift @@ -15,12 +15,30 @@ public enum MissingFieldsStrategy: Error { case ignore } -public enum JavaCodingError: Error { +public enum JavaCodingError: LocalizedError { case notSupported(String) case cantCreateObject(String) case cantFindObject(String) case nilNotSupported(String) case wrongArrayLength + case notEnoughBitsToRepresent(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)" + case .nilNotSupported(let message): + return "Nil not supported: \(message)" + case .wrongArrayLength: + return "Wrong array length" + case .notEnoughBitsToRepresent(let message): + return "Not enough bits to represent \(message)" + } + } } indirect enum JNIStorageType { From e4b105dc830e59a116ec48216061b478aaefddff Mon Sep 17 00:00:00 2001 From: Andrew Druk Date: Sun, 19 Apr 2020 17:29:06 +0300 Subject: [PATCH 05/12] Dev: fix support of primitive and support of optional primitives improve support of Encoding&Decoding errors --- Sources/JNICore+JavaCoder.swift | 37 +++++- Sources/JavaCoderConfig.swift | 171 +++++++++++++------------- Sources/JavaCodingError.swift | 78 ++++++++++++ Sources/JavaDecoder.swift | 212 ++++++++++++++++++++++---------- Sources/JavaEncoder.swift | 174 +++++++++++++++++--------- Sources/JavaPrimitive.swift | 36 +++--- 6 files changed, 487 insertions(+), 221 deletions(-) create mode 100644 Sources/JavaCodingError.swift diff --git a/Sources/JNICore+JavaCoder.swift b/Sources/JNICore+JavaCoder.swift index 8393438..db634d1 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) @@ -132,6 +132,17 @@ public extension JNICore { } } + + public var errorDescription: String? { + switch self { + case .classNotFoundException(let message): + return "ClassNotFoundaException: \(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 @@ -365,6 +388,18 @@ public extension JNICore { }) } + 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) diff --git a/Sources/JavaCoderConfig.swift b/Sources/JavaCoderConfig.swift index ec5187e..4bbb144 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 { @@ -44,152 +44,157 @@ 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))] + let value = $0 as! Int + if value < Int(Int32.min) || value > Int(Int32.max) { + let errorDescription = "Not enough bits to represent Int" + let context = EncodingError.Context(codingPath: $1, debugDescription: errorDescription) + throw EncodingError.invalidValue(value, context) + } + let args = [jvalue(i: jint(value))] return JNI.NewObject(IntegerClass, methodID: IntegerConstructor, args: args)! - }, decodableClosure: { - return Int(JNI.CallIntMethod($0, methodID: NumberIntValueMethod)) + }, decodableClosure: { value, _ in + Int(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: { value, _ in + let args = [jvalue(b: value as! Int8)] return JNI.NewObject(ByteClass, methodID: ByteConstructor, args: args)! - }, decodableClosure: { - return JNI.CallByteMethod($0, methodID: NumberByteValueMethod) + }, decodableClosure: { value, _ in + 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: { value, _ in + let args = [jvalue(s: value as! Int16)] return JNI.NewObject(ShortClass, methodID: ShortConstructor, args: args)! - }, decodableClosure: { - return JNI.CallShortMethod($0, methodID: NumberShortValueMethod) + }, decodableClosure: { value, _ in + 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: { value, _ in + let args = [jvalue(i: jint(value as! Int32))] return JNI.NewObject(IntegerClass, methodID: IntegerConstructor, args: args)! - }, decodableClosure: { - return Int32(JNI.CallIntMethod($0, methodID: NumberIntValueMethod)) + }, decodableClosure: { value, _ in + 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: { value, _ in + let args = [jvalue(j: value as! Int64)] return JNI.NewObject(LongClass, methodID: LongConstructor, args: args)! - }, decodableClosure: { - return JNI.CallLongMethod($0, methodID: NumberLongValueMethod) + }, decodableClosure: { value, _ in + 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: { + let value = $0 as! UInt + if value < UInt(UInt32.min) || value > Int(UInt32.max) { + let errorDescription = "Not enough bits to represent UInt" + let context = EncodingError.Context(codingPath: $1, debugDescription: errorDescription) + throw EncodingError.invalidValue(value, context) + } + let args = [jvalue(i: jint(bitPattern: UInt32(value)))] + return JNI.NewObject(IntegerClass, methodID: IntegerConstructor, args: args)! + }, decodableClosure: { value, _ in + UInt(UInt32(bitPattern: JNI.CallIntMethod(value, methodID: NumberIntValueMethod))) + }) + + RegisterType(type: UInt8.self, javaClassname: ByteClassname, encodableClosure: { value, _ in + let args = [jvalue(b: jbyte(bitPattern: value as! UInt8))] + return JNI.NewObject(ByteClass, methodID: ByteConstructor, args: args)! + }, decodableClosure: { value, _ in + UInt8(bitPattern: 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: { value, _ in + let args = [jvalue(s: jshort(bitPattern: value as! UInt16))] return JNI.NewObject(ShortClass, methodID: ShortConstructor, args: args)! - }, decodableClosure: { - return UInt8(JNI.CallShortMethod($0, methodID: NumberShortValueMethod)) + }, decodableClosure: { value, _ in + UInt16(bitPattern: 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: { value, _ in + let args = [jvalue(i: jint(bitPattern: value as! UInt32))] return JNI.NewObject(IntegerClass, methodID: IntegerConstructor, args: args)! - }, decodableClosure: { - return UInt16(JNI.CallIntMethod($0, methodID: NumberIntValueMethod)) + }, decodableClosure: { value, _ in + UInt32(bitPattern: 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: { value, _ in + let args = [jvalue(j: jlong(bitPattern: value as! UInt64))] 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(bitPattern: 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: { value, _ in + let args = [jvalue(f: value as! Float)] return JNI.NewObject(FloatClass, methodID: FloatConstructor, args: args)! - }, decodableClosure: { - return JNI.api.CallFloatMethodA(JNI.env, $0, NumberFloatValueMethod, nil) + }, decodableClosure: { value, _ in + 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: { value, _ in + let args = [jvalue(d: value as! Double)] return JNI.NewObject(DoubleClass, methodID: DoubleConstructor, args: args)! - }, decodableClosure: { - return JNI.api.CallDoubleMethodA(JNI.env, $0, NumberDoubleValueMethod, nil) + }, decodableClosure: { value, _ in + 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 +202,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 60c4e14..d7bf334 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,7 +89,7 @@ public class JavaDecoder: Decoder { fileprivate class JavaObjectContainer : KeyedDecodingContainerProtocol { typealias Key = K - var codingPath = [CodingKey]() + var codingPath: [CodingKey] var allKeys = [K]() let decoder: JavaDecoder @@ -98,6 +101,7 @@ fileprivate class JavaObjectContainer : KeyedDecodingContainerPro self.decoder = decoder self.jniStorage = jniStorage self.javaObject = jniStorage.javaObject + self.codingPath = jniStorage.codingPath switch jniStorage.type { case let .object(className): self.javaClass = className @@ -111,7 +115,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 { @@ -194,23 +198,23 @@ fileprivate class JavaObjectContainer : KeyedDecodingContainerPro } public func decode(_ type: UInt.Type, forKey key: K) throws -> UInt { - return UInt(try decodeInteger(forKey: key.stringValue)) + return UInt(UInt32(bitPattern: try decodeInteger(forKey: key.stringValue))) } public func decode(_ type: UInt8.Type, forKey key: K) throws -> UInt8 { - return UInt8(try decodeByte(forKey: key.stringValue)) + return UInt8(bitPattern: try decodeByte(forKey: key.stringValue)) } public func decode(_ type: UInt16.Type, forKey key: K) throws -> UInt16 { - return UInt16(try decodeShort(forKey: key.stringValue)) + return UInt16(bitPattern: try decodeShort(forKey: key.stringValue)) } public func decode(_ type: UInt32.Type, forKey key: K) throws -> UInt32 { - return UInt32(try decodeInteger(forKey: key.stringValue)) + return UInt32(bitPattern: try decodeInteger(forKey: key.stringValue)) } public func decode(_ type: UInt64.Type, forKey key: K) throws -> UInt64 { - return UInt64(try decodeLong(forKey: key.stringValue)) + return UInt64(bitPattern: try decodeLong(forKey: key.stringValue)) } public func decode(_ type: Float.Type, forKey key: K) throws -> Float { @@ -261,6 +265,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) @@ -276,7 +288,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 } @@ -292,7 +304,7 @@ fileprivate class JavaObjectContainer : KeyedDecodingContainerPro 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)! @@ -314,16 +326,16 @@ fileprivate class JavaObjectContainer : KeyedDecodingContainerPro 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 { @@ -332,14 +344,14 @@ 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 @@ -352,6 +364,7 @@ fileprivate class JavaHashMapKeyedContainer: KeyedDecodingContain self.decoder = decoder self.jniStorage = jniStorage self.javaObject = jniStorage.javaObject + self.codingPath = jniStorage.codingPath let keySet = JNI.api.CallObjectMethodA(JNI.env, javaObject, HashMapKeySetMethod, nil) let keyArray = JNI.api.CallObjectMethodA(JNI.env, keySet, SetToArrayMethod, nil) @@ -369,21 +382,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) @@ -403,7 +419,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 { @@ -417,20 +433,20 @@ 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)]") + 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 { @@ -438,13 +454,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? @@ -469,6 +485,7 @@ fileprivate class JavaHashMapUnkeyedContainer: UnkeyedDecodingContainer { self.decoder = decoder self.jniStorage = jniStorage self.javaObject = jniStorage.javaObject + self.codingPath = jniStorage.codingPath self.count = Int(JNI.CallIntMethod(self.javaObject, methodID: HashMapSizeMethod)) * 2 let keySet = JNI.api.CallObjectMethodA(JNI.env, javaObject, HashMapKeySetMethod, nil) @@ -483,13 +500,14 @@ 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[]") + throw DecodingError.dataCorruptedError(in: self, debugDescription: "Can't find object") } currentIndex += 1 defer { @@ -497,34 +515,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? @@ -545,6 +563,7 @@ fileprivate class JavaArrayContainer: UnkeyedDecodingContainer { fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) { self.decoder = decoder self.jniStorage = jniStorage + self.codingPath = jniStorage.codingPath self.count = Int(JNI.CallIntMethod(jniStorage.javaObject, methodID: CollectionSizeMethod)) self.javaIterator = JNI.CallObjectMethod(jniStorage.javaObject, methodID: CollectionIteratorMethod)! } @@ -554,35 +573,36 @@ 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 @@ -593,6 +613,7 @@ fileprivate class JavaEnumContainer: SingleValueDecodingContainer { self.decoder = decoder self.jniStorage = jniStorage self.javaObject = jniStorage.javaObject + self.codingPath = jniStorage.codingPath switch jniStorage.type { case let .object(className): self.javaClass = className @@ -604,24 +625,76 @@ 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 value = JNI.api.GetIntField(JNI.env, javaObject, fieldID) + return Int(fromJavaPrimitive: value) + } + + func decode(_ type: Int8.Type) throws -> Int8 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "B") + let value = JNI.api.GetByteField(JNI.env, javaObject, fieldID) + return Int8(fromJavaPrimitive: value) + } + + func decode(_ type: Int16.Type) throws -> Int16 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "S") + let value = JNI.api.GetShortField(JNI.env, javaObject, fieldID) + return Int16(fromJavaPrimitive: value) + } + + func decode(_ type: Int32.Type) throws -> Int32 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "I") + let value = JNI.api.GetIntField(JNI.env, javaObject, fieldID) + return Int32(fromJavaPrimitive: value) + } + + func decode(_ type: Int64.Type) throws -> Int64 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "J") + let value = JNI.api.GetLongField(JNI.env, javaObject, fieldID) + return Int64(fromJavaPrimitive: value) + } + + func decode(_ type: UInt.Type) throws -> UInt { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "I") + let value = JNI.api.GetIntField(JNI.env, javaObject, fieldID) + return UInt(fromJavaPrimitive: value) + } + + func decode(_ type: UInt8.Type) throws -> UInt8 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "B") + let value = JNI.api.GetByteField(JNI.env, javaObject, fieldID) + return UInt8(fromJavaPrimitive: value) + } + + func decode(_ type: UInt16.Type) throws -> UInt16 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "S") + let value = JNI.api.GetShortField(JNI.env, javaObject, fieldID) + return UInt16(fromJavaPrimitive: value) + } + + func decode(_ type: UInt32.Type) throws -> UInt32 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "I") + let value = JNI.api.GetIntField(JNI.env, javaObject, fieldID) + return UInt32(fromJavaPrimitive: value) + } + + func decode(_ type: UInt64.Type) throws -> UInt64 { + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "J") + let value = JNI.api.GetLongField(JNI.env, javaObject, fieldID) + return UInt64(fromJavaPrimitive: value) + } - func decode(_ type: T.Type) throws -> T where T : Decodable { - let classname = self.decoder.getJavaClassname(forType: type) - let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "L\(classname);") - guard let object = JNI.api.GetObjectField(JNI.env, javaObject, fieldID) else { - throw JavaCodingError.nilNotSupported("\(javaClass).rawValue") - } - defer { - JNI.DeleteLocalRef(object) - } - return try self.decoder.unbox(type: type, javaObject: object) + func decode(_ valueType: T.Type) throws -> T where T : Decodable { + fatalError("Unsupported: type \(type(of: valueType))") } } fileprivate class JavaAnyCodableContainer : KeyedDecodingContainerProtocol { typealias Key = K - var codingPath = [CodingKey]() + var codingPath: [CodingKey] var allKeys = [K]() let decoder: JavaDecoder @@ -631,6 +704,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 @@ -644,7 +718,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 { @@ -670,7 +744,7 @@ fileprivate class JavaAnyCodableContainer : KeyedDecodingContaine } } else if key.stringValue == "value" { - return try self.decoder.unbox(type: type, javaObject: self.jniStorage.javaObject) + return try self.decoder.unbox(type: type, javaObject: self.jniStorage.javaObject, codingPath: codingPath + [key]) } else { fatalError("Unknown key: \(key.stringValue)") @@ -678,7 +752,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 { @@ -688,7 +762,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") } } @@ -697,16 +771,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) @@ -728,7 +802,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) } @@ -738,13 +814,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 433ccf1..b9a8968 100644 --- a/Sources/JavaEncoder.swift +++ b/Sources/JavaEncoder.swift @@ -15,30 +15,32 @@ public enum MissingFieldsStrategy: Error { case ignore } -public enum JavaCodingError: LocalizedError { - case notSupported(String) - case cantCreateObject(String) - case cantFindObject(String) - case nilNotSupported(String) - case wrongArrayLength - case notEnoughBitsToRepresent(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)" - case .nilNotSupported(let message): - return "Nil not supported: \(message)" - case .wrongArrayLength: - return "Wrong array length" - case .notEnoughBitsToRepresent(let message): - return "Not enough bits to represent \(message)" - } +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 { @@ -63,6 +65,7 @@ indirect enum JNIStorageType { class JNIStorageObject { let type: JNIStorageType + let codingPath: [CodingKey] var javaObject: jobject! { didSet { if let value = oldValue { @@ -71,13 +74,15 @@ class JNIStorageObject { } } - init(type: JNIStorageType, javaObject: jobject) { + init(type: JNIStorageType, javaObject: jobject, codingPath: [CodingKey] = []) { self.type = type self.javaObject = javaObject + self.codingPath = codingPath } - init(type: JNIStorageType) { + init(type: JNIStorageType, codingPath: [CodingKey] = []) { self.type = type + self.codingPath = codingPath } deinit { @@ -106,8 +111,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 @@ -122,7 +129,7 @@ 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)! } @@ -248,7 +255,7 @@ fileprivate class JavaObjectContainer : KeyedEncodingContainerPro // 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 { @@ -264,7 +271,7 @@ fileprivate class JavaObjectContainer : KeyedEncodingContainerPro } func encode(_ value: Int, forKey key: K) throws { - try encodeInteger(try value.javaPrimitive(), key: key.stringValue) + try encodeInteger(try value.javaPrimitive(codingPath: codingPath + [key]), key: key.stringValue) } func encode(_ value: Int8, forKey key: K) throws { @@ -284,7 +291,7 @@ fileprivate class JavaObjectContainer : KeyedEncodingContainerPro } func encode(_ value: UInt, forKey key: K) throws { - try encodeInteger(try value.javaPrimitive(), key: key.stringValue) + try encodeInteger(try value.javaPrimitive(codingPath: codingPath + [key]), key: key.stringValue) } func encode(_ value: UInt8, forKey key: K) throws { @@ -393,7 +400,7 @@ fileprivate class JavaObjectContainer : KeyedEncodingContainerPro 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) } @@ -454,19 +461,19 @@ fileprivate class JavaHashMapKeyedContainer : KeyedEncodingContai // 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 valueStorage = try self.encoder.box(value, codingPath: codingPath + [key]) let result = JNI.CallObjectMethod(javaObject, methodID: HashMapPutMethod, args: [jvalue(l: keyStorage.javaObject), jvalue(l: valueStorage.javaObject)]) assert(result == nil, "Rewrite for key \(key.stringValue)") } @@ -517,11 +524,12 @@ fileprivate class JavaHashMapUnkeyedContainer : UnkeyedEncodingContainer { // 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)]) assert(result == nil, "Rewrite for key") @@ -573,11 +581,12 @@ fileprivate class JavaArrayContainer : UnkeyedEncodingContainer { // 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 indexKey = JavaKey(index: count) + let storeObject = try self.encoder.box(value, codingPath: codingPath + [indexKey]) let rewrite = JNI.CallBooleanMethod(self.javaObject, methodID: CollectionAddMethod, args: [jvalue(l: storeObject.javaObject)]) assert(rewrite == JNI.TRUE, "ArrayList should always return true from add()") count += 1 @@ -612,29 +621,78 @@ 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 jniStorage.javaObject == nil { - let valueOfMethodID = try JNI.getStaticJavaMethod(forClass: javaClass, method: "valueOf", sig: "(\(rawValue.type.sig))L\(javaClass);") - JNI.SaveFatalErrorMessage("\(javaClass) valueOf \(rawValue.type.sig)") - defer { - JNI.RemoveFatalErrorMessage() - } - guard let javaObject = JNI.CallStaticObjectMethod(clazz, methodID: valueOfMethodID, args: [jvalue(l: rawValue.javaObject)]) else { - throw JavaCodingError.nilNotSupported("\(javaClass).valueOf()") + 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 } 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 filed = try JNI.getJavaField(forClass: self.javaClass, field: "rawValue", sig: sig) + let setterFunc = setterFuncMap[sig] + setterFunc?(self.jniStorage.javaObject, filed, value) } } + + 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 context = EncodingError.Context(codingPath: codingPath, debugDescription: "Unsupported: type \(type(of: valueType))") + throw EncodingError.invalidValue(valueType, context) + } } // MARK: - AnyCodable Containers @@ -664,7 +722,7 @@ fileprivate class JavaAnyCodableContainer : KeyedEncodingContaine // 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 { @@ -673,7 +731,7 @@ fileprivate class JavaAnyCodableContainer : KeyedEncodingContaine return } do { - let jniObject = try self.encoder.box(value) + let jniObject = try self.encoder.box(value, codingPath: codingPath + [key]) self.jniStorage.javaObject = JNI.api.NewLocalRef(JNI.env, jniObject.javaObject) } catch { @@ -717,18 +775,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 index 7118abd..8e610b5 100644 --- a/Sources/JavaPrimitive.swift +++ b/Sources/JavaPrimitive.swift @@ -23,9 +23,11 @@ extension Int { self.init(javaPrimitive) } - public func javaPrimitive() throws -> jint { - if self > Int(Int32.max) || self < Int(Int32.min) { - throw JavaCodingError.notEnoughBitsToRepresent("Int \(self)") + public func javaPrimitive(codingPath: [CodingKey] = []) throws -> jint { + 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) } @@ -78,55 +80,61 @@ extension Int64 { extension UInt { public init(fromJavaPrimitive javaPrimitive: jint) { - self.init(javaPrimitive) + self.init(UInt32(bitPattern: javaPrimitive)) } - public func javaPrimitive() throws -> jint { - return jint(self) + public func javaPrimitive(codingPath: [CodingKey] = []) throws -> jint { + if self < UInt(UInt32.min) || self > Int(UInt32.max) { + let errorDescription = "Not enough bits to represent UInt" + let context = EncodingError.Context(codingPath: codingPath, debugDescription: errorDescription) + throw EncodingError.invalidValue(self, context) + } + let uint32 = UInt32(self) + return jint(bitPattern: uint32) } } extension UInt8 { public init(fromJavaPrimitive javaPrimitive: jbyte) { - self.init(javaPrimitive) + self.init(bitPattern: javaPrimitive) } public func javaPrimitive() throws -> jbyte { - return jbyte(self) + return jbyte(bitPattern: self) } } extension UInt16 { public init(fromJavaPrimitive javaPrimitive: jshort) { - self.init(javaPrimitive) + self.init(bitPattern: javaPrimitive) } public func javaPrimitive() throws -> jshort { - return jshort(self) + return jshort(bitPattern: self) } } extension UInt32 { public init(fromJavaPrimitive javaPrimitive: jint) { - self.init(javaPrimitive) + self.init(bitPattern: javaPrimitive) } public func javaPrimitive() throws -> jint { - return jint(self) + return jint(bitPattern: self) } } extension UInt64 { public init(fromJavaPrimitive javaPrimitive: jlong) { - self.init(javaPrimitive) + self.init(bitPattern: javaPrimitive) } public func javaPrimitive() throws -> jlong { - return jlong(self) + return jlong(bitPattern: self) } } From b708c7527a387e8c3830cf90d6e78a687078e0cf Mon Sep 17 00:00:00 2001 From: Andrew Druk Date: Thu, 28 Jan 2021 00:47:32 +0200 Subject: [PATCH 06/12] Dev: restore enum with object encode / decode --- Sources/JavaDecoder.swift | 10 +++++++++- Sources/JavaEncoder.swift | 20 ++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Sources/JavaDecoder.swift b/Sources/JavaDecoder.swift index d7bf334..044f082 100644 --- a/Sources/JavaDecoder.swift +++ b/Sources/JavaDecoder.swift @@ -687,7 +687,15 @@ fileprivate class JavaEnumContainer: SingleValueDecodingContainer { } func decode(_ valueType: T.Type) throws -> T where T : Decodable { - fatalError("Unsupported: type \(type(of: valueType))") + let classname = decoder.getJavaClassname(forType: valueType) + let fieldID = try JNI.getJavaField(forClass: javaClass, field: "rawValue", sig: "L\(classname);") + guard let object = JNI.api.GetObjectField(JNI.env, javaObject, fieldID) else { + throw JavaCodingError.notSupported(javaClass) + } + defer { + JNI.DeleteLocalRef(object) + } + return try decoder.unbox(type: valueType, javaObject: object, codingPath: codingPath) } } diff --git a/Sources/JavaEncoder.swift b/Sources/JavaEncoder.swift index b9a8968..e24e36e 100644 --- a/Sources/JavaEncoder.swift +++ b/Sources/JavaEncoder.swift @@ -690,8 +690,24 @@ class JavaEnumValueEncodingContainer: SingleValueEncodingContainer { ] public func encode(_ valueType: T) throws { - let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Unsupported: type \(type(of: valueType))") - throw EncodingError.invalidValue(valueType, context) + 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 { + let valueOfMethodID = try JNI.getStaticJavaMethod(forClass: javaClass, method: "valueOf", sig: "(\(rawValue.type.sig))L\(javaClass);") + JNI.SaveFatalErrorMessage("\(javaClass) valueOf \(rawValue.type.sig)") + defer { + JNI.RemoveFatalErrorMessage() + } + guard let javaObject = JNI.CallStaticObjectMethod(clazz, methodID: valueOfMethodID, args: [jvalue(l: rawValue.javaObject)]) else { + throw JavaCodingError.cantCreateObject(javaClass) + } + jniStorage.javaObject = javaObject + } + else { + let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Unsupported: type \(type(of: valueType))") + throw EncodingError.invalidValue(valueType, context) + } } } From 677cf1a463e46e02ec4eaed4f4f73be573b3ec2e Mon Sep 17 00:00:00 2001 From: Andrew Druk Date: Wed, 17 Feb 2021 01:18:30 +0200 Subject: [PATCH 07/12] Dev: restore 32-bit support in Java Coder --- Sources/JavaCoderConfig.swift | 20 ++++++++++++++++++-- Sources/JavaDecoder.swift | 4 ++++ Sources/JavaPrimitive.swift | 16 ++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Sources/JavaCoderConfig.swift b/Sources/JavaCoderConfig.swift index 4bbb144..3244042 100644 --- a/Sources/JavaCoderConfig.swift +++ b/Sources/JavaCoderConfig.swift @@ -91,10 +91,18 @@ public struct JavaCoderConfig { let context = EncodingError.Context(codingPath: $1, debugDescription: errorDescription) throw EncodingError.invalidValue(value, context) } + #if arch(x86_64) || arch(arm64) let args = [jvalue(i: jint(bitPattern: UInt32(value)))] + #else + let args = [jvalue(i: jint(value))] + #endif return JNI.NewObject(IntegerClass, methodID: IntegerConstructor, args: args)! }, decodableClosure: { value, _ in - UInt(UInt32(bitPattern: JNI.CallIntMethod(value, methodID: NumberIntValueMethod))) + #if arch(x86_64) || arch(arm64) + return UInt(UInt32(bitPattern: JNI.CallIntMethod(value, methodID: NumberIntValueMethod))) + #else + return UInt(JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) + #endif }) RegisterType(type: UInt8.self, javaClassname: ByteClassname, encodableClosure: { value, _ in @@ -112,10 +120,18 @@ public struct JavaCoderConfig { }) RegisterType(type: UInt32.self, javaClassname: IntegerClassname, encodableClosure: { value, _ in + #if arch(x86_64) || arch(arm64) let args = [jvalue(i: jint(bitPattern: value as! UInt32))] + #else + let args = [jvalue(i: jint(value as! UInt32))] + #endif return JNI.NewObject(IntegerClass, methodID: IntegerConstructor, args: args)! }, decodableClosure: { value, _ in - UInt32(bitPattern: JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) + #if arch(x86_64) || arch(arm64) + return UInt32(bitPattern: JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) + #else + return UInt32(JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) + #endif }) RegisterType(type: UInt64.self, javaClassname: LongClassname, encodableClosure: { value, _ in diff --git a/Sources/JavaDecoder.swift b/Sources/JavaDecoder.swift index 044f082..df7888a 100644 --- a/Sources/JavaDecoder.swift +++ b/Sources/JavaDecoder.swift @@ -151,7 +151,11 @@ fileprivate class JavaObjectContainer : KeyedDecodingContainerPro private func decodeInteger(forKey key: String) throws -> Int32 { let fieldID = try JNI.getJavaField(forClass: javaClass, field: key, sig: "I") + #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 { diff --git a/Sources/JavaPrimitive.swift b/Sources/JavaPrimitive.swift index 8e610b5..e59a3cd 100644 --- a/Sources/JavaPrimitive.swift +++ b/Sources/JavaPrimitive.swift @@ -80,7 +80,11 @@ extension Int64 { extension UInt { public init(fromJavaPrimitive javaPrimitive: jint) { + #if arch(x86_64) || arch(arm64) self.init(UInt32(bitPattern: javaPrimitive)) + #else + self.init(javaPrimitive) + #endif } public func javaPrimitive(codingPath: [CodingKey] = []) throws -> jint { @@ -90,7 +94,11 @@ extension UInt { throw EncodingError.invalidValue(self, context) } let uint32 = UInt32(self) + #if arch(x86_64) || arch(arm64) return jint(bitPattern: uint32) + #else + return jint(uint32) + #endif } } @@ -119,11 +127,19 @@ extension UInt16 { extension UInt32 { public init(fromJavaPrimitive javaPrimitive: jint) { + #if arch(x86_64) || arch(arm64) self.init(bitPattern: javaPrimitive) + #else + self.init(javaPrimitive) + #endif } public func javaPrimitive() throws -> jint { + #if arch(x86_64) || arch(arm64) return jint(bitPattern: self) + #else + return jint(self) + #endif } } From 328decd14703ebd2e44cd1d81386565d1b1f3877 Mon Sep 17 00:00:00 2001 From: Andrew Druk Date: Fri, 26 Mar 2021 00:43:23 +0200 Subject: [PATCH 08/12] Dev: fix primitive encode/decode for 32 bit --- Sources/JavaCoderConfig.swift | 124 ++++++++++++++++------------------ Sources/JavaPrimitive.swift | 13 ++-- 2 files changed, 66 insertions(+), 71 deletions(-) diff --git a/Sources/JavaCoderConfig.swift b/Sources/JavaCoderConfig.swift index 3244042..3cf7561 100644 --- a/Sources/JavaCoderConfig.swift +++ b/Sources/JavaCoderConfig.swift @@ -43,116 +43,112 @@ public struct JavaCoderConfig { public static func RegisterBasicJavaTypes() { - RegisterType(type: Int.self, javaClassname: IntegerClassname, encodableClosure: { - let value = $0 as! Int - if value < Int(Int32.min) || value > Int(Int32.max) { - let errorDescription = "Not enough bits to represent Int" - let context = EncodingError.Context(codingPath: $1, debugDescription: errorDescription) - throw EncodingError.invalidValue(value, context) - } - let args = [jvalue(i: jint(value))] + 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: { value, _ in - Int(JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) + Int(fromJavaPrimitive: JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) }) - RegisterType(type: Int8.self, javaClassname: ByteClassname, encodableClosure: { value, _ in - let args = [jvalue(b: value 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: { value, _ in - JNI.CallByteMethod(value, methodID: NumberByteValueMethod) + Int8(fromJavaPrimitive: JNI.CallByteMethod(value, methodID: NumberByteValueMethod)) }) - RegisterType(type: Int16.self, javaClassname: ShortClassname, encodableClosure: { value, _ in - let args = [jvalue(s: value 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: { value, _ in - JNI.CallShortMethod(value, methodID: NumberShortValueMethod) + Int16(fromJavaPrimitive: JNI.CallShortMethod(value, methodID: NumberShortValueMethod)) }) - RegisterType(type: Int32.self, javaClassname: IntegerClassname, encodableClosure: { value, _ in - let args = [jvalue(i: jint(value 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: { value, _ in - JNI.CallIntMethod(value, methodID: NumberIntValueMethod) + Int32(fromJavaPrimitive: JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) }) - RegisterType(type: Int64.self, javaClassname: LongClassname, encodableClosure: { value, _ in - let args = [jvalue(j: value 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: { value, _ in - JNI.CallLongMethod(value, methodID: NumberLongValueMethod) + Int64(fromJavaPrimitive: JNI.CallLongMethod(value, methodID: NumberLongValueMethod)) }) - RegisterType(type: UInt.self, javaClassname: IntegerClassname, encodableClosure: { - let value = $0 as! UInt - if value < UInt(UInt32.min) || value > Int(UInt32.max) { - let errorDescription = "Not enough bits to represent UInt" - let context = EncodingError.Context(codingPath: $1, debugDescription: errorDescription) - throw EncodingError.invalidValue(value, context) - } - #if arch(x86_64) || arch(arm64) - let args = [jvalue(i: jint(bitPattern: UInt32(value)))] - #else - let args = [jvalue(i: jint(value))] - #endif + 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 - #if arch(x86_64) || arch(arm64) - return UInt(UInt32(bitPattern: JNI.CallIntMethod(value, methodID: NumberIntValueMethod))) - #else - return UInt(JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) - #endif + UInt(fromJavaPrimitive: JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) }) - RegisterType(type: UInt8.self, javaClassname: ByteClassname, encodableClosure: { value, _ in - let args = [jvalue(b: jbyte(bitPattern: value as! UInt8))] + 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(bitPattern: JNI.CallByteMethod(value, methodID: NumberByteValueMethod)) + UInt8(fromJavaPrimitive: JNI.CallByteMethod(value, methodID: NumberByteValueMethod)) }) - RegisterType(type: UInt16.self, javaClassname: ShortClassname, encodableClosure: { value, _ in - let args = [jvalue(s: jshort(bitPattern: value as! UInt16))] + 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: { value, _ in - UInt16(bitPattern: JNI.CallShortMethod(value, methodID: NumberShortValueMethod)) + UInt16(fromJavaPrimitive: JNI.CallShortMethod(value, methodID: NumberShortValueMethod)) }) - RegisterType(type: UInt32.self, javaClassname: IntegerClassname, encodableClosure: { value, _ in - #if arch(x86_64) || arch(arm64) - let args = [jvalue(i: jint(bitPattern: value as! UInt32))] - #else - let args = [jvalue(i: jint(value as! UInt32))] - #endif + 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: { value, _ in - #if arch(x86_64) || arch(arm64) - return UInt32(bitPattern: JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) - #else - return UInt32(JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) - #endif + UInt32(fromJavaPrimitive: JNI.CallIntMethod(value, methodID: NumberIntValueMethod)) }) - RegisterType(type: UInt64.self, javaClassname: LongClassname, encodableClosure: { value, _ in - let args = [jvalue(j: jlong(bitPattern: value as! UInt64))] + 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: { value, _ in - UInt64(bitPattern: JNI.CallLongMethod(value, methodID: NumberLongValueMethod)) + UInt64(fromJavaPrimitive: JNI.CallLongMethod(value, methodID: NumberLongValueMethod)) }) - RegisterType(type: Float.self, javaClassname: FloatClassname, encodableClosure: { value, _ in - let args = [jvalue(f: value as! Float)] + RegisterType(type: Float.self, javaClassname: FloatClassname, encodableClosure: { any, _ in + let value = any as! Float + let primitive = try value.javaPrimitive() + let args = [jvalue(f: primitive)] return JNI.NewObject(FloatClass, methodID: FloatConstructor, args: args)! }, decodableClosure: { value, _ in - JNI.CallFloatMethod(value, methodID: NumberFloatValueMethod) + Float(fromJavaPrimitive: JNI.CallFloatMethod(value, methodID: NumberFloatValueMethod)) }) - RegisterType(type: Double.self, javaClassname: DoubleClassname, encodableClosure: { value, _ in - let args = [jvalue(d: value as! Double)] + RegisterType(type: Double.self, javaClassname: DoubleClassname, encodableClosure: { any, _ in + let value = any as! Double + let primitive = try value.javaPrimitive() + let args = [jvalue(d: primitive)] return JNI.NewObject(DoubleClass, methodID: DoubleConstructor, args: args)! }, decodableClosure: { value, _ in - JNI.CallDoubleMethod(value, methodID: NumberDoubleValueMethod) + Double(fromJavaPrimitive: JNI.CallDoubleMethod(value, methodID: NumberDoubleValueMethod)) }) RegisterType(type: Bool.self, javaClassname: BooleanClassname, encodableClosure: { value, _ in diff --git a/Sources/JavaPrimitive.swift b/Sources/JavaPrimitive.swift index e59a3cd..047f4ad 100644 --- a/Sources/JavaPrimitive.swift +++ b/Sources/JavaPrimitive.swift @@ -83,21 +83,20 @@ extension UInt { #if arch(x86_64) || arch(arm64) self.init(UInt32(bitPattern: javaPrimitive)) #else - self.init(javaPrimitive) + self.init(bitPattern: javaPrimitive) #endif } public func javaPrimitive(codingPath: [CodingKey] = []) throws -> jint { - if self < UInt(UInt32.min) || self > Int(UInt32.max) { + 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) } - let uint32 = UInt32(self) #if arch(x86_64) || arch(arm64) - return jint(bitPattern: uint32) + return jint(bitPattern: UInt32(self)) #else - return jint(uint32) + return jint(bitPattern: self) #endif } } @@ -130,7 +129,7 @@ extension UInt32 { #if arch(x86_64) || arch(arm64) self.init(bitPattern: javaPrimitive) #else - self.init(javaPrimitive) + self.init(UInt(bitPattern: javaPrimitive)) #endif } @@ -138,7 +137,7 @@ extension UInt32 { #if arch(x86_64) || arch(arm64) return jint(bitPattern: self) #else - return jint(self) + return jint(bitPattern: UInt(self)) #endif } } From 6f8ec27054dee14086a9e56fde000ab9ff5e00d4 Mon Sep 17 00:00:00 2001 From: Andrew Druk Date: Thu, 9 Sep 2021 18:45:18 +0300 Subject: [PATCH 09/12] Dev: fix arm32 floating type issues --- Sources/JavaCoderConfig.swift | 4 +- Sources/JavaEncoder.swift | 4 +- Sources/JavaPrimitive.swift | 142 +++++++++++++++++++++++++++------- 3 files changed, 119 insertions(+), 31 deletions(-) diff --git a/Sources/JavaCoderConfig.swift b/Sources/JavaCoderConfig.swift index 3cf7561..3652001 100644 --- a/Sources/JavaCoderConfig.swift +++ b/Sources/JavaCoderConfig.swift @@ -135,7 +135,7 @@ public struct JavaCoderConfig { RegisterType(type: Float.self, javaClassname: FloatClassname, encodableClosure: { any, _ in let value = any as! Float - let primitive = try value.javaPrimitive() + let primitive = jfloat(value) let args = [jvalue(f: primitive)] return JNI.NewObject(FloatClass, methodID: FloatConstructor, args: args)! }, decodableClosure: { value, _ in @@ -144,7 +144,7 @@ public struct JavaCoderConfig { RegisterType(type: Double.self, javaClassname: DoubleClassname, encodableClosure: { any, _ in let value = any as! Double - let primitive = try value.javaPrimitive() + let primitive = jdouble(value) let args = [jvalue(d: primitive)] return JNI.NewObject(DoubleClass, methodID: DoubleConstructor, args: args)! }, decodableClosure: { value, _ in diff --git a/Sources/JavaEncoder.swift b/Sources/JavaEncoder.swift index e24e36e..27c9210 100644 --- a/Sources/JavaEncoder.swift +++ b/Sources/JavaEncoder.swift @@ -263,11 +263,11 @@ fileprivate class JavaObjectContainer : KeyedEncodingContainerPro } func encode(_ value: Double, forKey key: K) throws { - try encodeDouble(try value.javaPrimitive(), key: key.stringValue) + try encodeDouble(jdouble(value), key: key.stringValue) } func encode(_ value: Float, forKey key: K) throws { - try encodeFloat(try value.javaPrimitive(), key: key.stringValue) + try encodeFloat(jfloat(value), key: key.stringValue) } func encode(_ value: Int, forKey key: K) throws { diff --git a/Sources/JavaPrimitive.swift b/Sources/JavaPrimitive.swift index 047f4ad..d043e66 100644 --- a/Sources/JavaPrimitive.swift +++ b/Sources/JavaPrimitive.swift @@ -6,24 +6,45 @@ 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: jboolean) { + public init(fromJavaPrimitive javaPrimitive: JavaBoolean) { self.init(javaPrimitive == JNI_TRUE) } - public func javaPrimitive() throws -> jboolean { + public func javaPrimitive() throws -> JavaBoolean { return jboolean(self ? JNI_TRUE : JNI_FALSE) } } extension Int { - public init(fromJavaPrimitive javaPrimitive: jint) { + public init(fromJavaPrimitive javaPrimitive: JavaInt) { self.init(javaPrimitive) } - public func javaPrimitive(codingPath: [CodingKey] = []) throws -> jint { + 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) @@ -35,51 +56,51 @@ extension Int { extension Int8 { - public init(fromJavaPrimitive javaPrimitive: jbyte) { + public init(fromJavaPrimitive javaPrimitive: JavaByte) { self.init(javaPrimitive) } - public func javaPrimitive() throws -> jbyte { + public func javaPrimitive() throws -> JavaByte { return jbyte(self) } } extension Int16 { - public init(fromJavaPrimitive javaPrimitive: jshort) { + public init(fromJavaPrimitive javaPrimitive: JavaShort) { self.init(javaPrimitive) } - public func javaPrimitive() throws -> jshort { + public func javaPrimitive() throws -> JavaShort { return jshort(self) } } extension Int32 { - public init(fromJavaPrimitive javaPrimitive: jint) { + public init(fromJavaPrimitive javaPrimitive: JavaInt) { self.init(javaPrimitive) } - public func javaPrimitive() throws -> jint { + public func javaPrimitive() throws -> JavaInt { return jint(self) } } extension Int64 { - public init(fromJavaPrimitive javaPrimitive: jlong) { + public init(fromJavaPrimitive javaPrimitive: JavaLong) { self.init(javaPrimitive) } - public func javaPrimitive() throws -> jlong { + public func javaPrimitive() throws -> JavaLong { return jlong(self) } } extension UInt { - public init(fromJavaPrimitive javaPrimitive: jint) { + public init(fromJavaPrimitive javaPrimitive: JavaInt) { #if arch(x86_64) || arch(arm64) self.init(UInt32(bitPattern: javaPrimitive)) #else @@ -87,7 +108,7 @@ extension UInt { #endif } - public func javaPrimitive(codingPath: [CodingKey] = []) throws -> jint { + 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) @@ -103,29 +124,29 @@ extension UInt { extension UInt8 { - public init(fromJavaPrimitive javaPrimitive: jbyte) { + public init(fromJavaPrimitive javaPrimitive: JavaByte) { self.init(bitPattern: javaPrimitive) } - public func javaPrimitive() throws -> jbyte { + public func javaPrimitive() throws -> JavaByte { return jbyte(bitPattern: self) } } extension UInt16 { - public init(fromJavaPrimitive javaPrimitive: jshort) { + public init(fromJavaPrimitive javaPrimitive: JavaShort) { self.init(bitPattern: javaPrimitive) } - public func javaPrimitive() throws -> jshort { + public func javaPrimitive() throws -> JavaShort { return jshort(bitPattern: self) } } extension UInt32 { - public init(fromJavaPrimitive javaPrimitive: jint) { + public init(fromJavaPrimitive javaPrimitive: JavaInt) { #if arch(x86_64) || arch(arm64) self.init(bitPattern: javaPrimitive) #else @@ -133,7 +154,7 @@ extension UInt32 { #endif } - public func javaPrimitive() throws -> jint { + public func javaPrimitive() throws -> JavaInt { #if arch(x86_64) || arch(arm64) return jint(bitPattern: self) #else @@ -144,11 +165,11 @@ extension UInt32 { extension UInt64 { - public init(fromJavaPrimitive javaPrimitive: jlong) { + public init(fromJavaPrimitive javaPrimitive: JavaLong) { self.init(bitPattern: javaPrimitive) } - public func javaPrimitive() throws -> jlong { + public func javaPrimitive() throws -> JavaLong { return jlong(bitPattern: self) } } @@ -159,8 +180,18 @@ extension Float { self.init(javaPrimitive) } - public func javaPrimitive() throws -> jfloat { - return jfloat(self) + #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 } } @@ -170,7 +201,64 @@ extension Double { self.init(javaPrimitive) } - public func javaPrimitive() throws -> jdouble { - return jdouble(self) + #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 } -} \ No newline at end of file +} + + + +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 From f24ffdad358b14fe092bafe1b92890f311c7a36b Mon Sep 17 00:00:00 2001 From: Andrew Druk Date: Fri, 1 Oct 2021 17:40:38 +0300 Subject: [PATCH 10/12] Dev: fix local ref issues Due to more aggressive ARC in Swift 5.4 we should care more about the lifetime of local JNI references --- Sources/JavaDecoder.swift | 119 ++++++++++++++++++++++++++++++-------- Sources/JavaEncoder.swift | 106 ++++++++++++++++++++------------- 2 files changed, 160 insertions(+), 65 deletions(-) diff --git a/Sources/JavaDecoder.swift b/Sources/JavaDecoder.swift index df7888a..5107de8 100644 --- a/Sources/JavaDecoder.swift +++ b/Sources/JavaDecoder.swift @@ -94,17 +94,16 @@ fileprivate class JavaObjectContainer : KeyedDecodingContainerPro 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 - self.codingPath = jniStorage.codingPath + + codingPath = jniStorage.codingPath switch jniStorage.type { case let .object(className): - self.javaClass = className + javaClass = className default: fatalError("Wrong container type") } @@ -136,21 +135,37 @@ 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 @@ -160,16 +175,28 @@ fileprivate class JavaObjectContainer : KeyedDecodingContainerPro 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) } @@ -302,7 +329,10 @@ 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() @@ -324,6 +354,10 @@ 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 } @@ -360,18 +394,18 @@ fileprivate class JavaHashMapKeyedContainer: KeyedDecodingContain 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 - self.codingPath = jniStorage.codingPath - + 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) @@ -436,7 +470,11 @@ fileprivate class JavaHashMapKeyedContainer: KeyedDecodingContain } let javaKey = javaKeys[typeKey] - guard let object = JNI.CallObjectMethod(self.javaObject, methodID: HashMapGetMethod, args: [jvalue(l: javaKey)]) else { + 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 { @@ -480,7 +518,6 @@ fileprivate class JavaHashMapUnkeyedContainer: UnkeyedDecodingContainer { let decoder: JavaDecoder let jniStorage: JNIStorageObject - let javaObject: jobject private var javaKeys: jarray private var javaCurrentKey: jobject? @@ -488,15 +525,14 @@ fileprivate class JavaHashMapUnkeyedContainer: UnkeyedDecodingContainer { fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) throws { self.decoder = decoder self.jniStorage = jniStorage - self.javaObject = jniStorage.javaObject - self.codingPath = jniStorage.codingPath - 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 { @@ -510,7 +546,11 @@ fileprivate class JavaHashMapUnkeyedContainer: UnkeyedDecodingContainer { 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 { + 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 @@ -568,8 +608,10 @@ fileprivate class JavaArrayContainer: UnkeyedDecodingContainer { self.decoder = decoder self.jniStorage = jniStorage self.codingPath = jniStorage.codingPath - self.count = Int(JNI.CallIntMethod(jniStorage.javaObject, methodID: CollectionSizeMethod)) - self.javaIterator = JNI.CallObjectMethod(jniStorage.javaObject, methodID: CollectionIteratorMethod)! + let javaObject = jniStorage.javaObject + self.count = Int(JNI.CallIntMethod(javaObject!, methodID: CollectionSizeMethod)) + self.javaIterator = JNI.CallObjectMethod(javaObject!, methodID: CollectionIteratorMethod)! + JNI.DeleteLocalRef(javaObject) } deinit { @@ -610,17 +652,16 @@ fileprivate class JavaEnumContainer: SingleValueDecodingContainer { 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 - self.codingPath = jniStorage.codingPath + + codingPath = jniStorage.codingPath switch jniStorage.type { case let .object(className): - self.javaClass = className + javaClass = className default: fatalError("Wrong container type") } @@ -632,67 +673,91 @@ fileprivate class JavaEnumContainer: SingleValueDecodingContainer { 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(_ 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.notSupported(javaClass) } @@ -756,7 +821,11 @@ fileprivate class JavaAnyCodableContainer : KeyedDecodingContaine } } else if key.stringValue == "value" { - return try self.decoder.unbox(type: type, javaObject: self.jniStorage.javaObject, codingPath: codingPath + [key]) + 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)") diff --git a/Sources/JavaEncoder.swift b/Sources/JavaEncoder.swift index 27c9210..8f1a1dc 100644 --- a/Sources/JavaEncoder.swift +++ b/Sources/JavaEncoder.swift @@ -66,17 +66,28 @@ 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, codingPath: [CodingKey] = []) { self.type = type - self.javaObject = javaObject + self._javaObject = javaObject self.codingPath = codingPath } @@ -86,7 +97,7 @@ class JNIStorageObject { } deinit { - if let value = javaObject { + if let value = _javaObject { JNI.api.DeleteLocalRef(JNI.env, value) } } @@ -131,7 +142,7 @@ open class JavaEncoder: Encoder { do { 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 @@ -212,45 +223,55 @@ 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 @@ -402,7 +423,11 @@ fileprivate class JavaObjectContainer : KeyedEncodingContainerPro do { 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 { @@ -455,10 +480,6 @@ 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 EncodingError.invalidValue(NSNotFound, EncodingError.Context(codingPath: codingPath, debugDescription: "Nil not supported")) @@ -474,7 +495,13 @@ fileprivate class JavaHashMapKeyedContainer : KeyedEncodingContai } let valueStorage = try self.encoder.box(value, codingPath: codingPath + [key]) - let result = JNI.CallObjectMethod(javaObject, methodID: HashMapPutMethod, args: [jvalue(l: keyStorage.javaObject), jvalue(l: valueStorage.javaObject)]) + 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)") } @@ -518,10 +545,6 @@ fileprivate class JavaHashMapUnkeyedContainer : UnkeyedEncodingContainer { self.jniStorage = jniStorage } - private var javaObject: jobject { - return jniStorage.javaObject - } - // MARK: - UnkeyedEncodingContainer Methods public func encodeNil() throws { throw EncodingError.invalidValue(NSNotFound, EncodingError.Context(codingPath: codingPath, debugDescription: "Nil not supported")) @@ -531,7 +554,13 @@ fileprivate class JavaHashMapUnkeyedContainer : UnkeyedEncodingContainer { 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 } @@ -575,10 +604,6 @@ fileprivate class JavaArrayContainer : UnkeyedEncodingContainer { self.jniStorage = jniStorage } - private var javaObject: jobject { - return jniStorage.javaObject - } - // MARK: - UnkeyedEncodingContainer Methods public func encodeNil() throws { throw EncodingError.invalidValue(NSNotFound, EncodingError.Context(codingPath: codingPath, debugDescription: "Nil not supported")) @@ -587,7 +612,11 @@ fileprivate class JavaArrayContainer : UnkeyedEncodingContainer { public func encode(_ value: T) throws { let indexKey = JavaKey(index: count) let storeObject = try self.encoder.box(value, codingPath: codingPath + [indexKey]) - let rewrite = JNI.CallBooleanMethod(self.javaObject, methodID: CollectionAddMethod, args: [jvalue(l: storeObject.javaObject)]) + 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 } @@ -667,7 +696,13 @@ class JavaEnumValueEncodingContainer: SingleValueEncodingContainer { public func encode(_ value: jvalue, sig: String) throws { let clazz = try JNI.getJavaClass(javaClass) // If jniStorage.javaObject == nil its enum, else optionSet - if jniStorage.javaObject == nil { + 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, @@ -675,11 +710,6 @@ class JavaEnumValueEncodingContainer: SingleValueEncodingContainer { } jniStorage.javaObject = javaObject } - else { - let filed = try JNI.getJavaField(forClass: self.javaClass, field: "rawValue", sig: sig) - let setterFunc = setterFuncMap[sig] - setterFunc?(self.jniStorage.javaObject, filed, value) - } } private let setterFuncMap: [String: (jobject, jfieldID, jvalue) -> Void] = [ @@ -693,13 +723,13 @@ class JavaEnumValueEncodingContainer: SingleValueEncodingContainer { 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 javaObject = rawValue.javaObject defer { - JNI.RemoveFatalErrorMessage() + JNI.DeleteLocalRef(javaObject) } - guard let javaObject = JNI.CallStaticObjectMethod(clazz, methodID: valueOfMethodID, args: [jvalue(l: rawValue.javaObject)]) else { + guard let javaObject = JNI.CallStaticObjectMethod(clazz, methodID: valueOfMethodID, args: [jvalue(l: javaObject)]) else { throw JavaCodingError.cantCreateObject(javaClass) } jniStorage.javaObject = javaObject @@ -732,10 +762,6 @@ 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 EncodingError.invalidValue(NSNotFound, EncodingError.Context(codingPath: codingPath, debugDescription: "Nil not supported")) @@ -748,7 +774,7 @@ fileprivate class JavaAnyCodableContainer : KeyedEncodingContaine } do { let jniObject = try self.encoder.box(value, codingPath: codingPath + [key]) - self.jniStorage.javaObject = JNI.api.NewLocalRef(JNI.env, jniObject.javaObject) + self.jniStorage.javaObject = jniObject.javaObject } catch { if self.encoder.missingFieldsStrategy == .ignore { From 50f7bcb9ce78899a3045e86fab137e2addbc6bdc Mon Sep 17 00:00:00 2001 From: Andrew Druk Date: Fri, 1 Oct 2021 17:40:53 +0300 Subject: [PATCH 11/12] Cleanup: fix typos --- Sources/JNICore+JavaCoder.swift | 4 ++-- Sources/JNIObject+JavaCoder.swift | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/JNICore+JavaCoder.swift b/Sources/JNICore+JavaCoder.swift index db634d1..59b4004 100644 --- a/Sources/JNICore+JavaCoder.swift +++ b/Sources/JNICore+JavaCoder.swift @@ -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): @@ -136,7 +136,7 @@ public extension JNICore { public var errorDescription: String? { switch self { case .classNotFoundException(let message): - return "ClassNotFoundaException: \(message)" + return "ClassNotFoundException: \(message)" case .methodNotFoundException(let message): return "MethodNotFoundException: \(message)" case .fieldNotFoundException(let message): 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 From 0e9fcbeb6d7af1638fd8d7cb16ea75261398e257 Mon Sep 17 00:00:00 2001 From: Andrew Druk Date: Fri, 1 Oct 2021 18:22:48 +0300 Subject: [PATCH 12/12] Dev: fix local naming collision --- Sources/JavaEncoder.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/JavaEncoder.swift b/Sources/JavaEncoder.swift index 8f1a1dc..c8b7a77 100644 --- a/Sources/JavaEncoder.swift +++ b/Sources/JavaEncoder.swift @@ -725,11 +725,11 @@ class JavaEnumValueEncodingContainer: SingleValueEncodingContainer { // If jniStorage.javaObject == nil its enum, else optionSet if jniStorage.hasJavaObject == false { let valueOfMethodID = try JNI.getStaticJavaMethod(forClass: javaClass, method: "valueOf", sig: "(\(rawValue.type.sig))L\(javaClass);") - let javaObject = rawValue.javaObject + let javaRawValue = rawValue.javaObject defer { - JNI.DeleteLocalRef(javaObject) + JNI.DeleteLocalRef(javaRawValue) } - guard let javaObject = JNI.CallStaticObjectMethod(clazz, methodID: valueOfMethodID, args: [jvalue(l: javaObject)]) else { + guard let javaObject = JNI.CallStaticObjectMethod(clazz, methodID: valueOfMethodID, args: [jvalue(l: javaRawValue)]) else { throw JavaCodingError.cantCreateObject(javaClass) } jniStorage.javaObject = javaObject