Dev: move java boxing to dedicated method

This commit is contained in:
Andrew Druk 2017-11-09 18:18:08 +02:00
parent e6cbf47660
commit 8880665e42
3 changed files with 120 additions and 64 deletions

View File

@ -68,6 +68,7 @@ func getJavaClass(_ className: String) throws -> jclass {
}
guard let javaClass = JNI.GlobalFindClass(className) else {
JNI.api.ExceptionClear(JNI.env)
JNI.ExceptionReset()
throw JNIError.classNotFoundException(className)
}
javaClasses[className] = javaClass
@ -91,6 +92,7 @@ func getJavaMethod(forClass className: String, method: String, sig: String) thro
}
guard let javaMethodID = JNI.api.GetMethodID(JNI.env, javaClass, method, sig) else {
JNI.api.ExceptionClear(JNI.env)
JNI.ExceptionReset()
throw JNIError.methodNotFoundException(key)
}
javaMethods[key] = javaMethodID
@ -110,6 +112,7 @@ func getJavaField(forClass className: String, field: String, sig: String) throws
}
guard let fieldID = JNI.api.GetFieldID(JNI.env, javaClass, field, sig) else {
JNI.api.ExceptionClear(JNI.env)
JNI.ExceptionReset()
throw JNIError.fieldNotFoundException(key)
}
javaFields[key] = fieldID
@ -135,6 +138,7 @@ extension JNICore {
let dump_mid = JNI.api.GetStaticMethodID(JNI.env, vm_class, "dumpReferenceTables", "()V")
JNI.api.CallStaticVoidMethodA(JNI.env, vm_class, dump_mid, nil)
JNI.api.ExceptionClear(JNI.env)
JNI.ExceptionReset()
}
}

View File

@ -31,16 +31,13 @@ public class JavaDecoder: Decoder {
public func decode<T : Decodable>(_ type: T.Type, from javaObject: jobject) throws -> T {
do {
let rootStorageType = try getJavaClassname(from: javaObject)
self.storage.append(JNIStorageObject(type: rootStorageType, javaObject: javaObject))
self.storage.append(JNIStorageObject(type: rootStorageType, javaObject: JNI.api.NewLocalRef(JNI.env, javaObject)!))
let value = try T(from: self)
assert(self.storage.count == 0, "Missing decoding for \(self.storage.count) objects")
return value
}
catch {
// clean all reference if failed
for storageObject in self.storage {
JNI.api.DeleteLocalRef(JNI.env, storageObject.javaObject)
}
self.storage.removeAll()
throw error
}
@ -89,11 +86,13 @@ fileprivate class JavaObjectContainer<K : CodingKey> : KeyedDecodingContainerPro
var allKeys = [K]()
let decoder: JavaDecoder
let jniStorage: JNIStorageObject
let javaObject: jobject
let javaClass: String
fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) {
self.decoder = decoder
self.jniStorage = jniStorage
self.javaObject = jniStorage.javaObject
switch jniStorage.type {
case let .object(className):
@ -103,10 +102,6 @@ fileprivate class JavaObjectContainer<K : CodingKey> : KeyedDecodingContainerPro
}
}
deinit {
JNI.api.DeleteLocalRef(JNI.env, self.javaObject)
}
func contains(_ key: K) -> Bool {
return true
}
@ -256,12 +251,14 @@ fileprivate class JavaHashMapContainer<K : CodingKey>: KeyedDecodingContainerPro
var allKeys = [K]()
private let decoder: JavaDecoder
private let jniStorage: JNIStorageObject
private let javaObject: jobject
private let javaKeys: [String: jobject]
private let getMethod: jmethodID
fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) throws {
self.decoder = decoder
self.jniStorage = jniStorage
self.javaObject = jniStorage.javaObject
self.getMethod = try getJavaMethod(forClass: JavaHashMapClassname, method: "get", sig: "(L\(JavaObjectClassname);)L\(JavaObjectClassname);")
@ -294,7 +291,6 @@ fileprivate class JavaHashMapContainer<K : CodingKey>: KeyedDecodingContainerPro
for (_, value) in self.javaKeys {
JNI.api.DeleteLocalRef(JNI.env, value)
}
JNI.api.DeleteLocalRef(JNI.env, self.javaObject)
}
func contains(_ key: K) -> Bool {
@ -359,19 +355,17 @@ fileprivate class JavaUnkeyedDecodingContainer: UnkeyedDecodingContainer {
var currentIndex: Int = 0
let decoder: JavaDecoder
let jniStorage: JNIStorageObject
let javaObject: jobject
var unsafePointer: UnsafeMutableRawPointer? // Copied array of elements (only for primitive types)
fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) {
self.decoder = decoder
self.jniStorage = jniStorage
self.javaObject = jniStorage.javaObject
self.count = Int(JNI.api.GetArrayLength(JNI.env, jniStorage.javaObject))
}
deinit {
JNI.api.DeleteLocalRef(JNI.env, self.javaObject)
}
func decodeNil() throws -> Bool {
throw JavaCodingError.notSupported("JavaUnkeyedDecodingContainer.decodeNil")
}
@ -435,11 +429,13 @@ fileprivate class JavaEnumDecodingContainer: SingleValueDecodingContainer {
var codingPath: [CodingKey] = []
let decoder: JavaDecoder
let jniStorage: JNIStorageObject
let javaObject: jobject
let javaClass: String
fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) {
self.decoder = decoder
self.jniStorage = jniStorage
self.javaObject = jniStorage.javaObject
switch jniStorage.type {
case let .object(className):

View File

@ -42,9 +42,18 @@ indirect enum JNIStorageType {
}
}
struct JNIStorageObject {
class JNIStorageObject {
let type: JNIStorageType
let javaObject: jobject
init(type: JNIStorageType, javaObject: jobject) {
self.type = type
self.javaObject = javaObject
}
deinit {
JNI.api.DeleteLocalRef(JNI.env, javaObject)
}
}
/// `JavaEncoder` facilitates the encoding of `Encodable` values into JSON.
@ -62,13 +71,15 @@ open class JavaEncoder: Encoder {
fileprivate let package: String
fileprivate var javaObjects: [JNIStorageObject]
fileprivate let missingFieldsStrategy: MissingFieldsStrategy
// MARK: - Constructing a JSON Encoder
/// Initializes `self` with default strategies.
public init(forPackage: String) {
public init(forPackage: String, missingFieldsStrategy: MissingFieldsStrategy = .throw) {
self.codingPath = [CodingKey]()
self.package = forPackage
self.javaObjects = [JNIStorageObject]()
self.missingFieldsStrategy = missingFieldsStrategy
}
// MARK: - Encoding Values
@ -79,22 +90,17 @@ open class JavaEncoder: Encoder {
/// - throws: `EncodingError.invalidValue` if a non-conforming floating-point value is encountered during encoding, and the encoding strategy is `.throw`.
/// - throws: An error if any value throws an error during encoding.
open func encode<T : Encodable>(_ value: T) throws -> jobject {
let storage = try self.pushInstance(value)
let javaObject = JNI.api.NewLocalRef(JNI.env, storage.javaObject)!
do {
try value.encode(to: self)
let storage = try self.box(value)
assert(self.javaObjects.count == 0, "Missing encoding for \(self.javaObjects.count) objects")
return JNI.api.NewLocalRef(JNI.env, storage.javaObject)!
}
catch {
// clean all reference if failed
JNI.api.DeleteLocalRef(JNI.env, javaObject)
for storage in self.javaObjects {
JNI.api.DeleteLocalRef(JNI.env, storage.javaObject)
}
self.javaObjects.removeAll()
throw error
}
assert(self.javaObjects.count == 0, "Missing encoding for \(self.javaObjects.count) objects")
return javaObject
}
// MARK: - Encoder Methods
@ -102,10 +108,10 @@ open class JavaEncoder: Encoder {
let storage = self.popInstance()
switch storage.type {
case .dictionary:
let container = JavaHashMapContainer<Key>(referencing: self, codingPath: self.codingPath, javaObject: storage.javaObject)
let container = JavaHashMapContainer<Key>(referencing: self, codingPath: self.codingPath, jniStorage: storage)
return KeyedEncodingContainer(container)
case let .object(className):
let container = JavaObjectContainer<Key>(referencing: self, codingPath: self.codingPath, javaClass: className, javaObject: storage.javaObject)
let container = JavaObjectContainer<Key>(referencing: self, codingPath: self.codingPath, javaClass: className, jniStorage: storage)
return KeyedEncodingContainer(container)
default:
fatalError("Only keyed containers")
@ -114,14 +120,14 @@ open class JavaEncoder: Encoder {
public func unkeyedContainer() -> UnkeyedEncodingContainer {
let storage = self.popInstance()
return JavaArrayContainer(referencing: self, codingPath: self.codingPath, arrayObject: storage.javaObject)
return JavaArrayContainer(referencing: self, codingPath: self.codingPath, jniStorage: storage)
}
public func singleValueContainer() -> SingleValueEncodingContainer {
let storage = self.popInstance()
switch storage.type {
case let .object(className):
return JavaEnumValueEncodingContainer(encoder: self, javaClass: className, javaObject: storage.javaObject)
return JavaEnumValueEncodingContainer(encoder: self, javaClass: className, jniStorage: storage)
default:
fatalError("Only object type supported here")
}
@ -138,23 +144,23 @@ fileprivate class JavaObjectContainer<K : CodingKey> : KeyedEncodingContainerPro
/// A reference to the encoder we're writing to.
private let encoder: JavaEncoder
private var javaClass: String
private var javaObject: jobject
private let javaClass: String
private let jniStorage: JNIStorageObject
/// The path of coding keys taken to get to this point in encoding.
private(set) public var codingPath: [CodingKey]
// MARK: - Initialization
/// Initializes `self` with the given references.
fileprivate init(referencing encoder: JavaEncoder, codingPath: [CodingKey], javaClass: String, javaObject: jobject) {
fileprivate init(referencing encoder: JavaEncoder, codingPath: [CodingKey], javaClass: String, jniStorage: JNIStorageObject) {
self.encoder = encoder
self.codingPath = codingPath
self.javaClass = javaClass
self.javaObject = javaObject
self.jniStorage = jniStorage
}
deinit {
JNI.api.DeleteLocalRef(JNI.env, javaObject)
private var javaObject: jobject {
return jniStorage.javaObject
}
// MARK: - KeyedEncodingContainerProtocol Methods
@ -188,6 +194,11 @@ fileprivate class JavaObjectContainer<K : CodingKey> : KeyedEncodingContainerPro
JNI.api.SetLongField(JNI.env, javaObject, filed, value)
}
public func encode(_ value: UInt, forKey key: Key) throws {
let filed = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "J")
JNI.api.SetLongField(JNI.env, javaObject, filed, jlong(bitPattern: UInt64(value)))
}
public func encode(_ value: String, forKey key: Key) throws {
let filed = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "Ljava/lang/String;")
var locals = [jobject]()
@ -195,10 +206,19 @@ fileprivate class JavaObjectContainer<K : CodingKey> : KeyedEncodingContainerPro
}
public func encode<T : Encodable>(_ value: T, forKey key: Key) throws {
let object = try self.encoder.pushInstance(value)
let filed = try getJavaField(forClass: self.javaClass, field: key.stringValue, sig: object.type.sig)
JNI.api.SetObjectField(JNI.env, self.javaObject, filed, object.javaObject)
try value.encode(to: self.encoder)
do {
let object = try self.encoder.box(value)
let filed = try getJavaField(forClass: self.javaClass, field: key.stringValue, sig: object.type.sig)
JNI.api.SetObjectField(JNI.env, self.javaObject, filed, object.javaObject)
}
catch {
if self.encoder.missingFieldsStrategy == .ignore {
// Ignore
}
else {
throw error
}
}
}
public func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
@ -227,7 +247,7 @@ fileprivate class JavaHashMapContainer<K : CodingKey> : KeyedEncodingContainerPr
/// A reference to the encoder we're writing to.
private let encoder: JavaEncoder
private var javaObject: jobject
private let jniStorage: JNIStorageObject
private var javaPutMethod = try! getJavaMethod(forClass: JavaHashMapClassname, method: "put", sig: "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;")
/// The path of coding keys taken to get to this point in encoding.
@ -235,14 +255,14 @@ fileprivate class JavaHashMapContainer<K : CodingKey> : KeyedEncodingContainerPr
// MARK: - Initialization
/// Initializes `self` with the given references.
fileprivate init(referencing encoder: JavaEncoder, codingPath: [CodingKey], javaObject: jobject) {
fileprivate init(referencing encoder: JavaEncoder, codingPath: [CodingKey], jniStorage: JNIStorageObject) {
self.encoder = encoder
self.codingPath = codingPath
self.javaObject = javaObject
self.jniStorage = jniStorage
}
deinit {
JNI.api.DeleteLocalRef(JNI.env, javaObject)
private var javaObject: jobject {
return jniStorage.javaObject
}
// MARK: - KeyedEncodingContainerProtocol Methods
@ -267,7 +287,7 @@ fileprivate class JavaHashMapContainer<K : CodingKey> : KeyedEncodingContainerPr
return
}
let object = try self.encoder.pushInstance(value)
let object = try self.encoder.box(value)
var locals = [jobject]()
var args = [jvalue]()
args.append(jvalue(l: key.stringValue.localJavaObject(&locals)))
@ -277,8 +297,6 @@ fileprivate class JavaHashMapContainer<K : CodingKey> : KeyedEncodingContainerPr
let result = JNI.check(JNI.api.CallObjectMethodA(JNI.env, javaObject, javaPutMethod, argsPtr), &locals)
assert(result == nil, "Rewrite for key \(key.stringValue)")
}
try value.encode(to: self.encoder)
}
public func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
@ -309,18 +327,18 @@ fileprivate class JavaArrayContainer : UnkeyedEncodingContainer {
/// The number of elements encoded into the container.
public private(set) var count: Int = 0
private let javaObject: jobject
private let jniStorage: JNIStorageObject
// MARK: - Initialization
/// Initializes `self` with the given references.
fileprivate init(referencing encoder: JavaEncoder, codingPath: [CodingKey], arrayObject: jobject) {
fileprivate init(referencing encoder: JavaEncoder, codingPath: [CodingKey], jniStorage: JNIStorageObject) {
self.encoder = encoder
self.codingPath = codingPath
self.javaObject = arrayObject
self.jniStorage = jniStorage
}
deinit {
JNI.api.DeleteLocalRef(JNI.env, javaObject)
private var javaObject: jobject {
return jniStorage.javaObject
}
// MARK: - UnkeyedEncodingContainer Methods
@ -353,14 +371,12 @@ fileprivate class JavaArrayContainer : UnkeyedEncodingContainer {
return
}
let storeObject = try self.encoder.pushInstance(value)
let storeObject = try self.encoder.box(value)
JNI.api.SetObjectArrayElement(JNI.env,
self.javaObject,
jsize(self.count),
storeObject.javaObject)
try value.encode(to: self.encoder)
count += 1
}
@ -383,14 +399,17 @@ class JavaEnumValueEncodingContainer: SingleValueEncodingContainer {
let encoder: JavaEncoder
private var javaClass: String
private var javaObject: jobject
private var jniStorage: JNIStorageObject
init(encoder: JavaEncoder, javaClass: String, javaObject: jobject) {
init(encoder: JavaEncoder, javaClass: String, jniStorage: JNIStorageObject) {
self.codingPath = [CodingKey]()
self.encoder = encoder
self.javaClass = javaClass
self.javaObject = javaObject
NSLog("Created \(javaClass) enum value encoding container")
self.jniStorage = jniStorage
}
private var javaObject: jobject {
return jniStorage.javaObject
}
public func encodeNil() throws {
@ -403,10 +422,23 @@ class JavaEnumValueEncodingContainer: SingleValueEncodingContainer {
JNI.api.SetLongField(JNI.env, javaObject, fieldID, jlong(value as! Int))
return
}
throw JavaCodingError.notSupported("JavaSingleValueEncodingContainer.encode(value: \(value)")
if value is UInt {
let filed = try getJavaField(forClass: javaClass, field: "rawValue", sig: "J")
JNI.api.SetLongField(JNI.env, javaObject, filed, jlong(bitPattern: UInt64(value as! UInt)))
return
}
if value is UInt32 {
let filed = try getJavaField(forClass: javaClass, field: "rawValue", sig: "J")
JNI.api.SetLongField(JNI.env, javaObject, filed, jlong(value as! UInt32))
return
}
throw JavaCodingError.notSupported("JavaSingleValueEncodingContainer.encode(value: \(value) \(type(of:value))")
}
}
private var UriClass = JNI.GlobalFindClass("android/net/Uri")
private var UriConstructor = JNI.api.GetStaticMethodID(JNI.env, JNI.GlobalFindClass("android/net/Uri"), "parse", "(Ljava/lang/String;)Landroid/net/Uri;")
extension JavaEncoder {
fileprivate func getFullClassName<T>(_ value: T) -> String{
@ -417,15 +449,34 @@ extension JavaEncoder {
}
@discardableResult
fileprivate func pushInstance<T: Encodable>(_ value: T) throws -> JNIStorageObject {
fileprivate func box<T: Encodable>(_ value: T) throws -> JNIStorageObject {
let storage: JNIStorageObject
if T.self == [String].self {
if T.self == URL.self {
var locals = [jobject]()
let javaString = (value as! URL).absoluteString.localJavaObject(&locals)
var args = [jvalue]()
args.append(jvalue(l: javaString))
let uriObject = JNIMethod.CallStaticObjectMethod(className: "android/net/Uri",
classCache: &UriClass,
methodName: "parse",
methodSig: "(Ljava/lang/String;)Landroid/net/Uri;",
methodCache: &UriConstructor,
args: &args,
locals: &locals)
storage = JNIStorageObject.init(type: .object(className: "android/net/Uri"), javaObject: uriObject!)
}
else if T.self == [String].self {
let value = value as! [String]
let javaClass = try getJavaClass(JavaStringClassname)
guard let javaObject = JNI.api.NewObjectArray(JNI.env, jsize(value.count), javaClass, nil) else {
throw JavaCodingError.cantCreateObject("\(JavaStringClassname)[]")
}
storage = JNIStorageObject(type: .array(type: .object(className: JavaStringClassname)), javaObject: javaObject)
javaObjects.append(storage)
try value.encode(to: self)
}
else if T.self == [Int].self {
let value = value as! [Int]
@ -433,15 +484,19 @@ extension JavaEncoder {
throw JavaCodingError.cantCreateObject("long[]")
}
storage = JNIStorageObject(type: .array(type: .primitive(name: "J")), javaObject: javaObject)
javaObjects.append(storage)
try value.encode(to: self)
}
else if let value = value as? [Encodable] {
else if let valueEncodableArray = value as? [Encodable] {
let subType = String("\(T.self)".dropFirst(6)).dropLast(1)
let fullClassName = package + "/" + subType
let javaClass = try getJavaClass(fullClassName)
guard let javaObject = JNI.api.NewObjectArray(JNI.env, jsize(value.count), javaClass, nil) else {
guard let javaObject = JNI.api.NewObjectArray(JNI.env, jsize(valueEncodableArray.count), javaClass, nil) else {
throw JavaCodingError.cantCreateObject("\(fullClassName)[]")
}
storage = JNIStorageObject(type: .array(type: .object(className: fullClassName)), javaObject: javaObject)
javaObjects.append(storage)
try value.encode(to: self)
}
else {
let storageType: JNIStorageType
@ -460,8 +515,9 @@ extension JavaEncoder {
throw JavaCodingError.cantCreateObject(fullClassName)
}
storage = JNIStorageObject(type: storageType, javaObject: javaObject)
javaObjects.append(storage)
try value.encode(to: self)
}
javaObjects.append(storage)
return storage
}