init commit
This commit is contained in:
commit
8a307a9644
|
@ -0,0 +1,3 @@
|
||||||
|
Package.resolved
|
||||||
|
.build
|
||||||
|
*.xcodeproj
|
|
@ -0,0 +1,21 @@
|
||||||
|
// swift-tools-version:4.0
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "JavaCoder",
|
||||||
|
products:[
|
||||||
|
.library(
|
||||||
|
name: "JavaCoder",
|
||||||
|
type: .dynamic,
|
||||||
|
targets:["JavaCoder"]
|
||||||
|
)
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
.package(url: "https://github.com/SwiftJava/java_swift.git", from: "2.1.1"),
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
.target(name: "JavaCoder", dependencies: ["java_swift"], path: "Sources"),
|
||||||
|
],
|
||||||
|
swiftLanguageVersions: [4]
|
||||||
|
)
|
|
@ -0,0 +1,141 @@
|
||||||
|
//
|
||||||
|
// JNIHelper.swift
|
||||||
|
// jniBridge
|
||||||
|
//
|
||||||
|
// Created by Andrew on 10/18/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import java_swift
|
||||||
|
|
||||||
|
let JavaHashMapClassname = "java/util/HashMap"
|
||||||
|
let JavaStringClassname = "java/lang/String"
|
||||||
|
let JavaSetClassname = "java/util/Set"
|
||||||
|
let JavaObjectClassname = "java/lang/Object"
|
||||||
|
let JavaClassClassname = "java/lang/Class"
|
||||||
|
|
||||||
|
let JavaHashMapSig = "Ljava/util/HashMap;"
|
||||||
|
let JavaStringSig = "Ljava/lang/String;"
|
||||||
|
|
||||||
|
|
||||||
|
public enum JNIError: Error {
|
||||||
|
|
||||||
|
case classNotFoundException(String)
|
||||||
|
case methodNotFoundException(String)
|
||||||
|
case fieldNotFoundException(String)
|
||||||
|
|
||||||
|
private static let JavaExceptionClass = try! getJavaClass("java/lang/Exception")
|
||||||
|
|
||||||
|
public func `throw`() {
|
||||||
|
switch self {
|
||||||
|
case .classNotFoundException(let message):
|
||||||
|
assert(JNI.api.ThrowNew(JNI.env, JNIError.JavaExceptionClass, "ClassNotFoundaException: \(message)") == 0)
|
||||||
|
case .methodNotFoundException(let message):
|
||||||
|
assert(JNI.api.ThrowNew(JNI.env, JNIError.JavaExceptionClass, "MethodNotFoundException: \(message)") == 0)
|
||||||
|
case .fieldNotFoundException(let message):
|
||||||
|
assert(JNI.api.ThrowNew(JNI.env, JNIError.JavaExceptionClass, "FieldNotFoundException: \(message)") == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension NSLock {
|
||||||
|
|
||||||
|
func sync<T>(_ block: () throws -> T) throws -> T {
|
||||||
|
self.lock()
|
||||||
|
defer {
|
||||||
|
self.unlock()
|
||||||
|
}
|
||||||
|
return try block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate var javaClasses = [String: jclass]()
|
||||||
|
fileprivate var javaMethods = [String: jmethodID]()
|
||||||
|
fileprivate var javaFields = [String: jmethodID]()
|
||||||
|
|
||||||
|
fileprivate let javaClassesLock = NSLock()
|
||||||
|
fileprivate let javaMethodLock = NSLock()
|
||||||
|
fileprivate let javaFieldLock = NSLock()
|
||||||
|
|
||||||
|
func getJavaClass(_ className: String) throws -> jclass {
|
||||||
|
if let javaClass = javaClasses[className] {
|
||||||
|
return javaClass
|
||||||
|
}
|
||||||
|
return try javaClassesLock.sync {
|
||||||
|
if let javaClass = javaClasses[className] {
|
||||||
|
return javaClass
|
||||||
|
}
|
||||||
|
guard let javaClass = JNI.GlobalFindClass(className) else {
|
||||||
|
JNI.api.ExceptionClear(JNI.env)
|
||||||
|
throw JNIError.classNotFoundException(className)
|
||||||
|
}
|
||||||
|
javaClasses[className] = javaClass
|
||||||
|
return javaClass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getJavaEmptyConstructor(forClass className: String) throws -> jmethodID? {
|
||||||
|
return try getJavaMethod(forClass: className, method: "<init>", sig: "()V")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getJavaMethod(forClass className: String, method: String, sig: String) throws -> jmethodID {
|
||||||
|
let key = "\(className).\(method)\(sig)"
|
||||||
|
let javaClass = try getJavaClass(className)
|
||||||
|
if let methodID = javaMethods[key] {
|
||||||
|
return methodID
|
||||||
|
}
|
||||||
|
return try javaMethodLock.sync {
|
||||||
|
if let methodID = javaMethods[key] {
|
||||||
|
return methodID
|
||||||
|
}
|
||||||
|
guard let javaMethodID = JNI.api.GetMethodID(JNI.env, javaClass, method, sig) else {
|
||||||
|
JNI.api.ExceptionClear(JNI.env)
|
||||||
|
throw JNIError.methodNotFoundException(key)
|
||||||
|
}
|
||||||
|
javaMethods[key] = javaMethodID
|
||||||
|
return javaMethodID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getJavaField(forClass className: String, field: String, sig: String) throws -> jfieldID {
|
||||||
|
let key = "\(className).\(field)\(sig)"
|
||||||
|
let javaClass = try getJavaClass(className)
|
||||||
|
if let fieldID = javaFields[key] {
|
||||||
|
return fieldID
|
||||||
|
}
|
||||||
|
return try javaFieldLock.sync({
|
||||||
|
if let fieldID = javaFields[key] {
|
||||||
|
return fieldID
|
||||||
|
}
|
||||||
|
guard let fieldID = JNI.api.GetFieldID(JNI.env, javaClass, field, sig) else {
|
||||||
|
JNI.api.ExceptionClear(JNI.env)
|
||||||
|
throw JNIError.fieldNotFoundException(key)
|
||||||
|
}
|
||||||
|
javaFields[key] = fieldID
|
||||||
|
return fieldID
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JNICore {
|
||||||
|
|
||||||
|
open func GlobalFindClass( _ name: UnsafePointer<Int8>,
|
||||||
|
_ file: StaticString = #file, _ line: Int = #line ) -> jclass? {
|
||||||
|
guard let clazz: jclass = FindClass(name, file, line ) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let result = api.NewGlobalRef(env, clazz)
|
||||||
|
api.DeleteLocalRef(env, clazz)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpReferenceTables() throws {
|
||||||
|
let vm_class = try getJavaClass("dalvik/system/VMDebug")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,436 @@
|
||||||
|
//
|
||||||
|
// JavaDecoder.swift
|
||||||
|
// jniBridge
|
||||||
|
//
|
||||||
|
// Created by Andrew on 10/19/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import java_swift
|
||||||
|
|
||||||
|
public class JavaDecoder: Decoder {
|
||||||
|
|
||||||
|
public var codingPath = [CodingKey]()
|
||||||
|
|
||||||
|
public var userInfo = [CodingUserInfoKey : Any]()
|
||||||
|
|
||||||
|
fileprivate var storage = [JNIStorageObject]()
|
||||||
|
fileprivate let package: String
|
||||||
|
|
||||||
|
public init(forPackage package: String) {
|
||||||
|
self.package = package
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
|
||||||
|
let storageObject = self.popInstance()
|
||||||
|
switch storageObject.type {
|
||||||
|
case .dictionary:
|
||||||
|
let container = try JavaHashMapContainer<Key>(decoder: self, jniStorage: storageObject)
|
||||||
|
return KeyedDecodingContainer(container)
|
||||||
|
case .object:
|
||||||
|
return KeyedDecodingContainer.init(JavaObjectContainer(decoder: self, jniStorage: storageObject))
|
||||||
|
default:
|
||||||
|
fatalError("Only keyed container supported here")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
|
||||||
|
let storageObject = self.popInstance()
|
||||||
|
switch storageObject.type {
|
||||||
|
case .array:
|
||||||
|
return JavaUnkeyedDecodingContainer(decoder: self, jniStorage: storageObject)
|
||||||
|
default:
|
||||||
|
fatalError("Only unkeyed container supported here")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public func singleValueContainer() throws -> SingleValueDecodingContainer {
|
||||||
|
return JavaSingleValueDecodingContainer()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate class JavaObjectContainer<K : CodingKey> : KeyedDecodingContainerProtocol {
|
||||||
|
typealias Key = K
|
||||||
|
|
||||||
|
var codingPath = [CodingKey]()
|
||||||
|
var allKeys = [K]()
|
||||||
|
|
||||||
|
let decoder: JavaDecoder
|
||||||
|
let javaObject: jobject
|
||||||
|
let javaClass: String
|
||||||
|
|
||||||
|
fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) {
|
||||||
|
self.decoder = decoder
|
||||||
|
self.javaObject = jniStorage.javaObject
|
||||||
|
switch jniStorage.type {
|
||||||
|
case let .object(className):
|
||||||
|
self.javaClass = className
|
||||||
|
default:
|
||||||
|
fatalError("Wrong container type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
JNI.api.DeleteLocalRef(JNI.env, self.javaObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(_ key: K) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeNil(forKey key: K) throws -> Bool {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(_ type: Bool.Type, forKey key: K) throws -> Bool {
|
||||||
|
let fieldID = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "Z")
|
||||||
|
return JNI.api.GetBooleanField(JNI.env, javaObject, fieldID) == JNI_TRUE
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(_ type: Int.Type, forKey key: K) throws -> Int {
|
||||||
|
let fieldID = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "J")
|
||||||
|
return Int(JNI.api.GetLongField(JNI.env, javaObject, fieldID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(_ type: Int8.Type, forKey key: K) throws -> Int8 {
|
||||||
|
let fieldID = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "B")
|
||||||
|
return JNI.api.GetByteField(JNI.env, javaObject, fieldID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(_ type: Int16.Type, forKey key: K) throws -> Int16 {
|
||||||
|
let fieldID = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "S")
|
||||||
|
return JNI.api.GetShortField(JNI.env, javaObject, fieldID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(_ type: Int32.Type, forKey key: K) throws -> Int32 {
|
||||||
|
let fieldID = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "I")
|
||||||
|
return Int32(JNI.api.GetIntField(JNI.env, javaObject, fieldID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(_ type: Int64.Type, forKey key: K) throws -> Int64 {
|
||||||
|
let fieldID = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "J")
|
||||||
|
return JNI.api.GetLongField(JNI.env, javaObject, fieldID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(_ type: String.Type, forKey key: K) throws -> String {
|
||||||
|
guard let result = try decodeIfPresent(type, forKey: key) else {
|
||||||
|
throw JavaCodingError.nilNotSupported("\(javaClass).\(key.stringValue)")
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode<T>(_ type: T.Type, forKey key: K) throws -> T where T : Decodable {
|
||||||
|
guard let result = try decodeIfPresent(type, forKey: key) else {
|
||||||
|
throw JavaCodingError.nilNotSupported("\(javaClass).\(key.stringValue)")
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
public func decodeIfPresent(_ type: String.Type, forKey key: K) throws -> String? {
|
||||||
|
let fieldID = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "L\(JavaStringClassname);")
|
||||||
|
let object = JNI.api.GetObjectField(JNI.env, javaObject, fieldID)
|
||||||
|
let str = String(javaObject: object)
|
||||||
|
JNI.api.DeleteLocalRef(JNI.env, object)
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
public func decodeIfPresent<T>(_ type: T.Type, forKey key: K) throws -> T? where T : Decodable {
|
||||||
|
let sig = self.decoder.getSig(forType: type)
|
||||||
|
let fieldID = try getJavaField(forClass: javaClass, field: key.stringValue, sig: sig)
|
||||||
|
guard let object = JNI.api.GetObjectField(JNI.env, javaObject, fieldID) else {
|
||||||
|
throw JavaCodingError.cantFindObject("\(javaClass).\(key.stringValue)")
|
||||||
|
}
|
||||||
|
self.decoder.pushObject(object, forType: type)
|
||||||
|
return try T(from: self.decoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func superDecoder() throws -> Decoder {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func superDecoder(forKey key: K) throws -> Decoder {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate class JavaHashMapContainer<K : CodingKey>: KeyedDecodingContainerProtocol {
|
||||||
|
typealias Key = K
|
||||||
|
|
||||||
|
var codingPath = [CodingKey]()
|
||||||
|
var allKeys = [K]()
|
||||||
|
|
||||||
|
private let decoder: JavaDecoder
|
||||||
|
private let javaObject: jobject
|
||||||
|
private let javaKeys: [String: jobject]
|
||||||
|
private let getMethod: jmethodID
|
||||||
|
|
||||||
|
fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) throws {
|
||||||
|
self.decoder = decoder
|
||||||
|
self.javaObject = jniStorage.javaObject
|
||||||
|
self.getMethod = try getJavaMethod(forClass: JavaHashMapClassname, method: "get", sig: "(L\(JavaObjectClassname);)L\(JavaObjectClassname);")
|
||||||
|
|
||||||
|
// read all keys from HashMap
|
||||||
|
let keySetMethodID = try getJavaMethod(forClass: JavaHashMapClassname, method: "keySet", sig: "()L\(JavaSetClassname);")
|
||||||
|
let toArrayMethodID = try getJavaMethod(forClass: JavaSetClassname, method: "toArray", sig: "()[L\(JavaObjectClassname);")
|
||||||
|
|
||||||
|
let keySet = JNI.api.CallObjectMethodA(JNI.env, javaObject, keySetMethodID, nil)
|
||||||
|
let keyArray = JNI.api.CallObjectMethodA(JNI.env, keySet, toArrayMethodID, nil)
|
||||||
|
|
||||||
|
var javaKeys = [String: jobject]()
|
||||||
|
let size = JNI.api.GetArrayLength(JNI.env, keyArray)
|
||||||
|
|
||||||
|
for i in 0 ..< size {
|
||||||
|
guard let object = JNI.api.GetObjectArrayElement(JNI.env, keyArray, i) else {
|
||||||
|
throw JavaCodingError.wrongArrayLength
|
||||||
|
}
|
||||||
|
let key = String(javaObject: object)
|
||||||
|
javaKeys[key] = object
|
||||||
|
allKeys.append(K(stringValue: key)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
JNI.api.DeleteLocalRef(JNI.env, keySet)
|
||||||
|
JNI.api.DeleteLocalRef(JNI.env, keyArray)
|
||||||
|
|
||||||
|
self.javaKeys = javaKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
for (_, value) in self.javaKeys {
|
||||||
|
JNI.api.DeleteLocalRef(JNI.env, value)
|
||||||
|
}
|
||||||
|
JNI.api.DeleteLocalRef(JNI.env, self.javaObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(_ key: K) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeNil(forKey key: K) throws -> Bool {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(_ type: String.Type, forKey key: K) throws -> String {
|
||||||
|
var javaKeyValue = jvalue.init(l: self.javaKeys[key.stringValue])
|
||||||
|
let object = withUnsafePointer(to: &javaKeyValue, { ptr in
|
||||||
|
return JNI.api.CallObjectMethodA(JNI.env, self.javaObject, getMethod, ptr)
|
||||||
|
})
|
||||||
|
let str = String(javaObject: object)
|
||||||
|
JNI.api.DeleteLocalRef(JNI.env, object)
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode<T>(_ type: T.Type, forKey key: K) throws -> T where T : Decodable {
|
||||||
|
if type == String.self {
|
||||||
|
return try decode(String.self, forKey: key) as! T
|
||||||
|
}
|
||||||
|
var javaKeyValue = jvalue.init(l: self.javaKeys[key.stringValue])
|
||||||
|
guard let object = withUnsafePointer(to: &javaKeyValue, { ptr in
|
||||||
|
return JNI.api.CallObjectMethodA(JNI.env, self.javaObject, getMethod, ptr)
|
||||||
|
}) else {
|
||||||
|
throw JavaCodingError.cantFindObject("HashMap[\(key.stringValue)]")
|
||||||
|
}
|
||||||
|
self.decoder.pushObject(object, forType: type)
|
||||||
|
return try T(from: self.decoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func superDecoder() throws -> Decoder {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func superDecoder(forKey key: K) throws -> Decoder {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate class JavaUnkeyedDecodingContainer: UnkeyedDecodingContainer {
|
||||||
|
|
||||||
|
var codingPath = [CodingKey]()
|
||||||
|
|
||||||
|
var count: Int?
|
||||||
|
|
||||||
|
var isAtEnd: Bool {
|
||||||
|
return self.count == self.currentIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentIndex: Int = 0
|
||||||
|
|
||||||
|
let decoder: JavaDecoder
|
||||||
|
let javaObject: jobject
|
||||||
|
var unsafePointer: UnsafeMutableRawPointer? // Copied array of elements (only for primitive types)
|
||||||
|
|
||||||
|
fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) {
|
||||||
|
self.decoder = decoder
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(_ type: Int.Type) throws -> Int {
|
||||||
|
guard let arrayElements = arrayElements(JNI.api.GetLongArrayElements) else {
|
||||||
|
throw JavaCodingError.wrongArrayLength
|
||||||
|
}
|
||||||
|
let result = arrayElements.advanced(by: self.currentIndex).pointee
|
||||||
|
currentIndex += 1
|
||||||
|
return Int(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(_ type: String.Type) throws -> String {
|
||||||
|
let obj = JNI.api.GetObjectArrayElement(JNI.env, javaObject, jsize(self.currentIndex))
|
||||||
|
currentIndex += 1
|
||||||
|
defer {
|
||||||
|
JNI.api.DeleteLocalRef(JNI.env, obj)
|
||||||
|
}
|
||||||
|
return String(javaObject: obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
|
||||||
|
if type == String.self {
|
||||||
|
return try decode(String.self) as! T
|
||||||
|
}
|
||||||
|
if type == Int.self {
|
||||||
|
return try decode(Int.self) as! T
|
||||||
|
}
|
||||||
|
guard let obj = JNI.api.GetObjectArrayElement(JNI.env, javaObject, jsize(self.currentIndex)) else {
|
||||||
|
throw JavaCodingError.cantFindObject("\(type)[\(self.currentIndex)]")
|
||||||
|
}
|
||||||
|
self.decoder.pushObject(obj, forType: type)
|
||||||
|
currentIndex += 1
|
||||||
|
return try T(from: self.decoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func superDecoder() throws -> Decoder {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func arrayElements<T>(_ getArrayElementsBlock: (UnsafeMutablePointer<JNIEnv?>?, jobject?, UnsafeMutablePointer<jboolean>?) -> UnsafeMutablePointer<T>?) -> UnsafeMutablePointer<T>? {
|
||||||
|
if let unsafePointer = self.unsafePointer {
|
||||||
|
return unsafePointer.assumingMemoryBound(to: T.self)
|
||||||
|
}
|
||||||
|
let unsafePointer = getArrayElementsBlock(JNI.env, javaObject, nil)
|
||||||
|
self.unsafePointer = UnsafeMutableRawPointer(unsafePointer)
|
||||||
|
return unsafePointer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate class JavaSingleValueDecodingContainer: SingleValueDecodingContainer {
|
||||||
|
var codingPath: [CodingKey] = []
|
||||||
|
|
||||||
|
func decodeNil() -> Bool {
|
||||||
|
fatalError("Unsupported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JavaDecoder {
|
||||||
|
|
||||||
|
fileprivate func getSig<T>(forType: T.Type) -> String{
|
||||||
|
if T.self == [String].self {
|
||||||
|
return "[L\(JavaStringClassname);"
|
||||||
|
}
|
||||||
|
if T.self == [Int].self {
|
||||||
|
return "[J"
|
||||||
|
}
|
||||||
|
if "\(forType)".starts(with: "Array<") {
|
||||||
|
let subType = String("\(forType)".dropFirst(6)).dropLast(1)
|
||||||
|
return "[L\(package)/\(subType);"
|
||||||
|
}
|
||||||
|
if "\(forType)".starts(with: "Dictionary<") {
|
||||||
|
return "L\(JavaHashMapClassname);"
|
||||||
|
}
|
||||||
|
return "L\(package)/\(forType);"
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func parseType<Str: StringProtocol>(_ stringType: Str) -> JNIStorageType {
|
||||||
|
switch stringType {
|
||||||
|
case "Int":
|
||||||
|
return .primitive(name: "J")
|
||||||
|
case _ where stringType.starts(with: "Array<"):
|
||||||
|
let subType = String(stringType.dropFirst(6)).dropLast(1)
|
||||||
|
return .array(type: parseType(subType))
|
||||||
|
case _ where stringType.starts(with: "Dictionary<"):
|
||||||
|
return .dictionary
|
||||||
|
default:
|
||||||
|
return .object(className: "\(package)/\(stringType)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func pushObject<T>(_ obj: jobject, forType type: T.Type) {
|
||||||
|
self.storage.append(JNIStorageObject(type: parseType("\(type)"), javaObject: obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func popInstance() -> JNIStorageObject {
|
||||||
|
guard let javaObject = self.storage.popLast() else {
|
||||||
|
preconditionFailure("No instances in stack")
|
||||||
|
}
|
||||||
|
return javaObject
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func getJavaClassname(from obj: jobject) throws -> JNIStorageType {
|
||||||
|
let cls = JNI.api.GetObjectClass(JNI.env, obj)
|
||||||
|
let mid = try getJavaMethod(forClass: JavaClassClassname, method: "getName", sig: "()L\(JavaStringClassname);")
|
||||||
|
let javaClassName = JNI.api.CallObjectMethodA(JNI.env, cls, mid, nil)
|
||||||
|
let className = String(javaObject: javaClassName).replacingOccurrences(of: ".", with: "/")
|
||||||
|
JNI.api.DeleteLocalRef(JNI.env, javaClassName)
|
||||||
|
if className.starts(with: "[") {
|
||||||
|
let subClassname = String(className.dropFirst(2)).dropLast(1)
|
||||||
|
// TODO: write parseSig func
|
||||||
|
return JNIStorageType.array(type: JNIStorageType.object(className: String(subClassname)))
|
||||||
|
}
|
||||||
|
return JNIStorageType.object(className: className)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,451 @@
|
||||||
|
//
|
||||||
|
// JavaEncoder.swift
|
||||||
|
// jniBridge
|
||||||
|
//
|
||||||
|
// Created by Andrew on 10/14/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreFoundation
|
||||||
|
import java_swift
|
||||||
|
|
||||||
|
public enum JavaCodingError: Error {
|
||||||
|
case notSupported
|
||||||
|
case cantCreateObject(String)
|
||||||
|
case cantFindObject(String)
|
||||||
|
case nilNotSupported(String)
|
||||||
|
case wrongArrayLength
|
||||||
|
}
|
||||||
|
|
||||||
|
indirect enum JNIStorageType {
|
||||||
|
case primitive(name: String)
|
||||||
|
case object(className: String)
|
||||||
|
case array(type: JNIStorageType)
|
||||||
|
case dictionary
|
||||||
|
|
||||||
|
var sig: String {
|
||||||
|
switch self {
|
||||||
|
case .primitive(let name):
|
||||||
|
return name
|
||||||
|
case .array(let type):
|
||||||
|
return "[\(type.sig)"
|
||||||
|
case .object(let className):
|
||||||
|
return "L\(className);"
|
||||||
|
case .dictionary:
|
||||||
|
return "L\(JavaHashMapClassname);"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct JNIStorageObject {
|
||||||
|
let type: JNIStorageType
|
||||||
|
let javaObject: jobject
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `JavaEncoder` facilitates the encoding of `Encodable` values into JSON.
|
||||||
|
open class JavaEncoder: Encoder {
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
|
/// The path to the current point in encoding.
|
||||||
|
public var codingPath: [CodingKey]
|
||||||
|
|
||||||
|
/// Contextual user-provided information for use during encoding.
|
||||||
|
public var userInfo: [CodingUserInfoKey : Any] {
|
||||||
|
return [:]
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate let package: String
|
||||||
|
fileprivate var javaObjects: [JNIStorageObject]
|
||||||
|
|
||||||
|
// MARK: - Constructing a JSON Encoder
|
||||||
|
/// Initializes `self` with default strategies.
|
||||||
|
public init(forPackage: String) {
|
||||||
|
self.codingPath = [CodingKey]()
|
||||||
|
self.package = forPackage
|
||||||
|
self.javaObjects = [JNIStorageObject]()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Encoding Values
|
||||||
|
/// Encodes the given top-level value and returns its JSON representation.
|
||||||
|
///
|
||||||
|
/// - parameter value: The value to encode.
|
||||||
|
/// - returns: A new `Data` value containing the encoded JSON data.
|
||||||
|
/// - 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)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
public func container<Key>(keyedBy: Key.Type) -> KeyedEncodingContainer<Key> {
|
||||||
|
let storage = self.popInstance()
|
||||||
|
switch storage.type {
|
||||||
|
case .dictionary:
|
||||||
|
let container = JavaHashMapContainer<Key>(referencing: self, codingPath: self.codingPath, javaObject: storage.javaObject)
|
||||||
|
return KeyedEncodingContainer(container)
|
||||||
|
case let .object(className):
|
||||||
|
let container = JavaObjectContainer<Key>(referencing: self, codingPath: self.codingPath, javaClass: className, javaObject: storage.javaObject)
|
||||||
|
return KeyedEncodingContainer(container)
|
||||||
|
default:
|
||||||
|
fatalError("Only keyed containers")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func unkeyedContainer() -> UnkeyedEncodingContainer {
|
||||||
|
let storage = self.popInstance()
|
||||||
|
return JavaArrayContainer(referencing: self, codingPath: self.codingPath, arrayObject: storage.javaObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func singleValueContainer() -> SingleValueEncodingContainer {
|
||||||
|
return JavaSingleValueEncodingContainer(encoder: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Encoding Containers
|
||||||
|
fileprivate class JavaObjectContainer<K : CodingKey> : KeyedEncodingContainerProtocol {
|
||||||
|
|
||||||
|
typealias Key = K
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
/// A reference to the encoder we're writing to.
|
||||||
|
private let encoder: JavaEncoder
|
||||||
|
|
||||||
|
private var javaClass: String
|
||||||
|
private var javaObject: jobject
|
||||||
|
|
||||||
|
/// 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) {
|
||||||
|
self.encoder = encoder
|
||||||
|
self.codingPath = codingPath
|
||||||
|
self.javaClass = javaClass
|
||||||
|
self.javaObject = javaObject
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
JNI.api.DeleteLocalRef(JNI.env, javaObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - KeyedEncodingContainerProtocol Methods
|
||||||
|
public func encodeNil(forKey key: Key) throws {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ value: Bool, forKey key: Key) throws {
|
||||||
|
let filed = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "Z")
|
||||||
|
JNI.api.SetBooleanField(JNI.env, javaObject, filed, jboolean(value ? JNI_TRUE : JNI_FALSE))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ value: Int, forKey key: Key) throws {
|
||||||
|
let filed = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "J")
|
||||||
|
JNI.api.SetLongField(JNI.env, javaObject, filed, Int64(value))
|
||||||
|
}
|
||||||
|
public func encode(_ value: Int8, forKey key: Key) throws {
|
||||||
|
let filed = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "B")
|
||||||
|
JNI.api.SetByteField(JNI.env, javaObject, filed, value)
|
||||||
|
}
|
||||||
|
public func encode(_ value: Int16, forKey key: Key) throws {
|
||||||
|
let filed = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "S")
|
||||||
|
JNI.api.SetShortField(JNI.env, javaObject, filed, value)
|
||||||
|
}
|
||||||
|
public func encode(_ value: Int32, forKey key: Key) throws {
|
||||||
|
let filed = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "I")
|
||||||
|
JNI.api.SetIntField(JNI.env, javaObject, filed, jint(value))
|
||||||
|
}
|
||||||
|
public func encode(_ value: Int64, forKey key: Key) throws {
|
||||||
|
let filed = try getJavaField(forClass: javaClass, field: key.stringValue, sig: "J")
|
||||||
|
JNI.api.SetLongField(JNI.env, javaObject, filed, 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]()
|
||||||
|
JNI.check(JNI.api.SetObjectField(JNI.env, javaObject, filed, value.localJavaObject(&locals)), &locals)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
|
||||||
|
preconditionFailure("Not implemented: nestedContainer")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
|
||||||
|
preconditionFailure("Not implemented: nestedUnkeyedContainer")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func superEncoder() -> Encoder {
|
||||||
|
preconditionFailure("Not implemented: superEncoder")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func superEncoder(forKey key: Key) -> Encoder {
|
||||||
|
preconditionFailure("Not implemented: superEncoder")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Encoding Containers
|
||||||
|
fileprivate class JavaHashMapContainer<K : CodingKey> : KeyedEncodingContainerProtocol {
|
||||||
|
|
||||||
|
typealias Key = K
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
/// A reference to the encoder we're writing to.
|
||||||
|
private let encoder: JavaEncoder
|
||||||
|
|
||||||
|
private var javaObject: jobject
|
||||||
|
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.
|
||||||
|
private(set) public var codingPath: [CodingKey]
|
||||||
|
|
||||||
|
// MARK: - Initialization
|
||||||
|
/// Initializes `self` with the given references.
|
||||||
|
fileprivate init(referencing encoder: JavaEncoder, codingPath: [CodingKey], javaObject: jobject) {
|
||||||
|
self.encoder = encoder
|
||||||
|
self.codingPath = codingPath
|
||||||
|
self.javaObject = javaObject
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
JNI.api.DeleteLocalRef(JNI.env, javaObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - KeyedEncodingContainerProtocol Methods
|
||||||
|
public func encodeNil(forKey key: Key) throws {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ value: String, forKey key: Key) throws {
|
||||||
|
var locals = [jobject]()
|
||||||
|
var args = [jvalue]()
|
||||||
|
args.append(jvalue(l: key.stringValue.localJavaObject(&locals)))
|
||||||
|
args.append(jvalue(l: value.localJavaObject(&locals)))
|
||||||
|
withUnsafePointer(to: &args[0]) { argsPtr in
|
||||||
|
let result = JNI.check(JNI.api.CallObjectMethodA(JNI.env, javaObject, javaPutMethod, argsPtr), &locals)
|
||||||
|
assert(result == nil, "Rewrite for key \(key.stringValue)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode<T : Encodable>(_ value: T, forKey key: Key) throws {
|
||||||
|
if value is String {
|
||||||
|
try self.encode(value as! String, forKey: key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let object = try self.encoder.pushInstance(value)
|
||||||
|
var locals = [jobject]()
|
||||||
|
var args = [jvalue]()
|
||||||
|
args.append(jvalue(l: key.stringValue.localJavaObject(&locals)))
|
||||||
|
args.append(jvalue(l: object.javaObject))
|
||||||
|
|
||||||
|
withUnsafePointer(to: &args[0]) { argsPtr in
|
||||||
|
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> {
|
||||||
|
preconditionFailure("Not implemented: nestedContainer")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
|
||||||
|
preconditionFailure("Not implemented: nestedUnkeyedContainer")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func superEncoder() -> Encoder {
|
||||||
|
preconditionFailure("Not implemented: superEncoder")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func superEncoder(forKey key: Key) -> Encoder {
|
||||||
|
preconditionFailure("Not implemented: superEncoder")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate class JavaArrayContainer : UnkeyedEncodingContainer {
|
||||||
|
// MARK: Properties
|
||||||
|
/// A reference to the encoder we're writing to.
|
||||||
|
private let encoder: JavaEncoder
|
||||||
|
|
||||||
|
/// The path of coding keys taken to get to this point in encoding.
|
||||||
|
private(set) public var codingPath: [CodingKey]
|
||||||
|
|
||||||
|
/// The number of elements encoded into the container.
|
||||||
|
public private(set) var count: Int = 0
|
||||||
|
|
||||||
|
private let javaObject: jobject
|
||||||
|
|
||||||
|
// MARK: - Initialization
|
||||||
|
/// Initializes `self` with the given references.
|
||||||
|
fileprivate init(referencing encoder: JavaEncoder, codingPath: [CodingKey], arrayObject: jobject) {
|
||||||
|
self.encoder = encoder
|
||||||
|
self.codingPath = codingPath
|
||||||
|
self.javaObject = arrayObject
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
JNI.api.DeleteLocalRef(JNI.env, javaObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - UnkeyedEncodingContainer Methods
|
||||||
|
public func encodeNil() throws {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ value: Int) throws {
|
||||||
|
var value = jlong(value)
|
||||||
|
JNI.api.SetLongArrayRegion(JNI.env, self.javaObject, jsize(count), 1, &value)
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ value: String) throws {
|
||||||
|
var locals = [jobject]()
|
||||||
|
JNI.check(JNI.api.SetObjectArrayElement(JNI.env,
|
||||||
|
self.javaObject,
|
||||||
|
jsize(self.count),
|
||||||
|
value.localJavaObject(&locals)), &locals)
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode<T : Encodable>(_ value: T) throws {
|
||||||
|
if value is String {
|
||||||
|
try self.encode(value as! String)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if value is Int {
|
||||||
|
try self.encode(value as! Int)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let storeObject = try self.encoder.pushInstance(value)
|
||||||
|
JNI.api.SetObjectArrayElement(JNI.env,
|
||||||
|
self.javaObject,
|
||||||
|
jsize(self.count),
|
||||||
|
storeObject.javaObject)
|
||||||
|
|
||||||
|
try value.encode(to: self.encoder)
|
||||||
|
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> {
|
||||||
|
preconditionFailure("Not implemented: nestedContainer")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
|
||||||
|
preconditionFailure("Not implemented: nestedUnkeyedContainer")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func superEncoder() -> Encoder {
|
||||||
|
preconditionFailure("Not implemented: superEncoder")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JavaSingleValueEncodingContainer: SingleValueEncodingContainer {
|
||||||
|
|
||||||
|
var codingPath: [CodingKey]
|
||||||
|
let encoder: JavaEncoder
|
||||||
|
|
||||||
|
init(encoder: JavaEncoder) {
|
||||||
|
self.codingPath = [CodingKey]()
|
||||||
|
self.encoder = encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encodeNil() throws {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode<T : Encodable>(_ value: T) throws {
|
||||||
|
throw JavaCodingError.notSupported
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JavaEncoder {
|
||||||
|
|
||||||
|
fileprivate func getFullClassName<T>(_ value: T) -> String{
|
||||||
|
if value is [String: Encodable] {
|
||||||
|
return JavaHashMapClassname
|
||||||
|
}
|
||||||
|
return package + "/" + String(describing: type(of: value))
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
fileprivate func pushInstance<T: Encodable>(_ value: T) throws -> JNIStorageObject {
|
||||||
|
let storage: JNIStorageObject
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
else if T.self == [Int].self {
|
||||||
|
let value = value as! [Int]
|
||||||
|
guard let javaObject = JNI.api.NewLongArray(JNI.env, jsize(value.count)) else {
|
||||||
|
throw JavaCodingError.cantCreateObject("long[]")
|
||||||
|
}
|
||||||
|
storage = JNIStorageObject(type: .array(type: .primitive(name: "J")), javaObject: javaObject)
|
||||||
|
}
|
||||||
|
else if let value = 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 {
|
||||||
|
throw JavaCodingError.cantCreateObject("\(fullClassName)[]")
|
||||||
|
}
|
||||||
|
storage = JNIStorageObject(type: .array(type: .object(className: fullClassName)), javaObject: javaObject)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let storageType: JNIStorageType
|
||||||
|
let fullClassName: String
|
||||||
|
if value is [String: Encodable] {
|
||||||
|
fullClassName = JavaHashMapClassname
|
||||||
|
storageType = .dictionary
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fullClassName = package + "/" + String(describing: type(of: value))
|
||||||
|
storageType = .object(className: fullClassName)
|
||||||
|
}
|
||||||
|
let javaClass = try getJavaClass(fullClassName)
|
||||||
|
let emptyContructor = try getJavaEmptyConstructor(forClass: fullClassName)
|
||||||
|
guard let javaObject = JNI.api.NewObjectA(JNI.env, javaClass, emptyContructor, nil) else {
|
||||||
|
throw JavaCodingError.cantCreateObject(fullClassName)
|
||||||
|
}
|
||||||
|
storage = JNIStorageObject(type: storageType, javaObject: javaObject)
|
||||||
|
}
|
||||||
|
javaObjects.append(storage)
|
||||||
|
return storage
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func popInstance() -> JNIStorageObject {
|
||||||
|
guard let javaObject = self.javaObjects.popLast() else {
|
||||||
|
preconditionFailure("No instances in stack")
|
||||||
|
}
|
||||||
|
return javaObject
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue