Dev: add support of AnyCodable and super Encoding/Decoding
This commit is contained in:
parent
fa93e4ed20
commit
31ac6c999e
|
@ -12,9 +12,10 @@ let package = Package(
|
|||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/andriydruk/java_swift.git", .branch("master")),
|
||||
.package(url: "https://github.com/andriydruk/swift-anycodable.git", .branch("master")),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "JavaCoder", dependencies: ["java_swift"], path: "Sources"),
|
||||
.target(name: "JavaCoder", dependencies: ["java_swift", "AnyCodable"], path: "Sources"),
|
||||
],
|
||||
swiftLanguageVersions: [4]
|
||||
)
|
||||
|
|
|
@ -49,6 +49,8 @@ let BooleanConstructor = try! JNI.getJavaMethod(forClass: BooleanClassname, meth
|
|||
|
||||
let ObjectToStringMethod = try! JNI.getJavaMethod(forClass: "java/lang/Object", method: "toString", sig: "()Ljava/lang/String;")
|
||||
let ClassGetNameMethod = try! JNI.getJavaMethod(forClass: ClassClassname, method: "getName", sig: "()L\(StringClassname);")
|
||||
let ClassGetFieldMethod = try! JNI.getJavaMethod(forClass: ClassClassname, method: "getField", sig: "(Ljava/lang/String;)Ljava/lang/reflect/Field;")
|
||||
let FieldGetTypedMethod = try! JNI.getJavaMethod(forClass: "java/lang/reflect/Field", method: "getType", sig: "()L\(ClassClassname);")
|
||||
let NumberByteValueMethod = try! JNI.getJavaMethod(forClass: "java/lang/Number", method: "byteValue", sig: "()B")
|
||||
let NumberShortValueMethod = try! JNI.getJavaMethod(forClass: "java/lang/Number", method: "shortValue", sig: "()S")
|
||||
let NumberIntValueMethod = try! JNI.getJavaMethod(forClass: "java/lang/Number", method: "intValue", sig: "()I")
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import java_swift
|
||||
import AnyCodable
|
||||
|
||||
public class JavaDecoder: Decoder {
|
||||
|
||||
|
@ -47,7 +48,9 @@ public class JavaDecoder: Decoder {
|
|||
let container = try JavaHashMapKeyedContainer<Key>(decoder: self, jniStorage: storageObject)
|
||||
return KeyedDecodingContainer(container)
|
||||
case .object:
|
||||
return KeyedDecodingContainer.init(JavaObjectContainer(decoder: self, jniStorage: storageObject))
|
||||
return KeyedDecodingContainer(JavaObjectContainer(decoder: self, jniStorage: storageObject))
|
||||
case .anyCodable:
|
||||
return KeyedDecodingContainer(JavaAnyCodableContainer(decoder: self, jniStorage: storageObject))
|
||||
default:
|
||||
fatalError("Only keyed container supported here")
|
||||
}
|
||||
|
@ -119,6 +122,7 @@ fileprivate class JavaObjectContainer<K : CodingKey> : KeyedDecodingContainerPro
|
|||
}
|
||||
catch {
|
||||
if self.decoder.missingFieldsStrategy == .ignore {
|
||||
NSLog("Ignore error: \(error)")
|
||||
return defaultValue
|
||||
}
|
||||
else {
|
||||
|
@ -195,7 +199,25 @@ fileprivate class JavaObjectContainer<K : CodingKey> : KeyedDecodingContainerPro
|
|||
|
||||
private func decodeJava<T>(_ type: T.Type, forKey key: K) throws -> T? where T : Decodable {
|
||||
return try decodeWithMissingStrategy(defaultValue: nil) {
|
||||
let classname = self.decoder.getJavaClassname(forType: type)
|
||||
let classname: String
|
||||
if type == AnyCodable.self {
|
||||
var locals = [jobject]()
|
||||
let cls = JNI.api.GetObjectClass(JNI.env, javaObject)!
|
||||
let javaTypename = key.stringValue.localJavaObject(&locals)
|
||||
let field = JNI.CallObjectMethod(cls, methodID: ClassGetFieldMethod, args: [jvalue(l: javaTypename)])!
|
||||
let fieldClass = JNI.CallObjectMethod(field, methodID: FieldGetTypedMethod, args: [])!
|
||||
let javaClassName = JNI.api.CallObjectMethodA(JNI.env, fieldClass, ClassGetNameMethod, nil)!
|
||||
classname = String(javaObject: javaClassName).replacingOccurrences(of: ".", with: "/")
|
||||
JNI.DeleteLocalRef(cls)
|
||||
JNI.DeleteLocalRef(field)
|
||||
JNI.DeleteLocalRef(fieldClass)
|
||||
JNI.DeleteLocalRef(javaClassName)
|
||||
_ = JNI.check(Void.self, &locals)
|
||||
}
|
||||
else {
|
||||
classname = self.decoder.getJavaClassname(forType: type)
|
||||
}
|
||||
|
||||
let fieldID = try JNI.getJavaField(forClass: javaClass, field: key.stringValue, sig: "L\(classname);")
|
||||
guard let object = JNI.api.GetObjectField(JNI.env, javaObject, fieldID) else {
|
||||
return nil
|
||||
|
@ -216,7 +238,8 @@ fileprivate class JavaObjectContainer<K : CodingKey> : KeyedDecodingContainerPro
|
|||
}
|
||||
|
||||
func superDecoder() throws -> Decoder {
|
||||
throw JavaCodingError.notSupported("JavaObjectContainer.superDecoder")
|
||||
self.decoder.storage.append(self.jniStorage)
|
||||
return self.decoder
|
||||
}
|
||||
|
||||
func superDecoder(forKey key: K) throws -> Decoder {
|
||||
|
@ -502,6 +525,81 @@ fileprivate class JavaEnumContainer: SingleValueDecodingContainer {
|
|||
}
|
||||
}
|
||||
|
||||
fileprivate class JavaAnyCodableContainer<K : CodingKey> : KeyedDecodingContainerProtocol {
|
||||
typealias Key = K
|
||||
|
||||
var codingPath = [CodingKey]()
|
||||
var allKeys = [K]()
|
||||
|
||||
let decoder: JavaDecoder
|
||||
let jniStorage: JNIStorageObject
|
||||
let jniCodableType: JNIStorageType
|
||||
|
||||
fileprivate init(decoder: JavaDecoder, jniStorage: JNIStorageObject) {
|
||||
self.decoder = decoder
|
||||
self.jniStorage = jniStorage
|
||||
switch jniStorage.type {
|
||||
case let .anyCodable(codable):
|
||||
self.jniCodableType = codable
|
||||
default:
|
||||
fatalError("Only .anyCodable supported here")
|
||||
}
|
||||
}
|
||||
|
||||
func contains(_ key: K) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func decodeNil(forKey key: K) throws -> Bool {
|
||||
throw JavaCodingError.notSupported("JavaObjectContainer.decodeNil(forKey: \(key)")
|
||||
}
|
||||
|
||||
func decode<T>(_ type: T.Type, forKey key: K) throws -> T where T : Decodable {
|
||||
if key.stringValue == "typeName" {
|
||||
switch jniCodableType {
|
||||
case let .object(className):
|
||||
let subString = className.split(separator: "/").last!
|
||||
return String(subString) as! T
|
||||
case .array:
|
||||
return AnyCodable.ArrayTypeName as! T
|
||||
case .dictionary:
|
||||
return AnyCodable.DictionaryTypeName as! T
|
||||
default:
|
||||
fatalError("Unsupported type here")
|
||||
}
|
||||
}
|
||||
else if key.stringValue == "value" {
|
||||
return try self.decoder.unbox(type: type, javaObject: self.jniStorage.javaObject)
|
||||
}
|
||||
else {
|
||||
fatalError("Unknown key: \(key.stringValue)")
|
||||
}
|
||||
}
|
||||
|
||||
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
|
||||
throw JavaCodingError.notSupported("JavaAnyCodableContainer.nestedContainer(keyedBy: \(type), forKey: \(key))")
|
||||
}
|
||||
|
||||
func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer {
|
||||
switch jniCodableType {
|
||||
case .array:
|
||||
return JavaArrayContainer(decoder: self.decoder, jniStorage: self.jniStorage)
|
||||
case .dictionary:
|
||||
return try JavaHashMapUnkeyedContainer(decoder: self.decoder, jniStorage: self.jniStorage)
|
||||
default:
|
||||
fatalError("Unsupported type here")
|
||||
}
|
||||
}
|
||||
|
||||
func superDecoder() throws -> Decoder {
|
||||
throw JavaCodingError.notSupported("JavaAnyCodableContainer.superDecoder")
|
||||
}
|
||||
|
||||
func superDecoder(forKey key: K) throws -> Decoder {
|
||||
throw JavaCodingError.notSupported("JavaAnyCodableContainer.superDecoder(forKey: \(key)")
|
||||
}
|
||||
}
|
||||
|
||||
extension JavaDecoder {
|
||||
|
||||
fileprivate func unbox<T: Decodable>(type: T.Type, javaObject: jobject) throws -> T {
|
||||
|
@ -555,6 +653,27 @@ extension JavaDecoder {
|
|||
let pathString = JNI.api.CallObjectMethodA(JNI.env, javaObject, ObjectToStringMethod, nil)
|
||||
return URL(string: String(javaObject: pathString)) as! T
|
||||
}
|
||||
else if type == AnyCodable.self {
|
||||
let cls = JNI.api.GetObjectClass(JNI.env, javaObject)
|
||||
let javaClassName = JNI.api.CallObjectMethodA(JNI.env, cls, ClassGetNameMethod, nil)
|
||||
let className = String(javaObject: javaClassName).replacingOccurrences(of: ".", with: "/")
|
||||
JNI.DeleteLocalRef(cls)
|
||||
JNI.DeleteLocalRef(javaClassName)
|
||||
let codableType: JNIStorageType
|
||||
if className == ArrayListClassname {
|
||||
codableType = .array
|
||||
}
|
||||
else if className == HashMapClassname {
|
||||
codableType = .dictionary
|
||||
}
|
||||
else {
|
||||
codableType = .object(className: className)
|
||||
}
|
||||
let obj = JNI.api.NewLocalRef(JNI.env, javaObject)!
|
||||
let storageObject = JNIStorageObject(type: .anyCodable(codable: codableType), javaObject: obj)
|
||||
self.storage.append(storageObject)
|
||||
return try T.init(from: self)
|
||||
}
|
||||
else {
|
||||
let stringType = "\(type)"
|
||||
let storageObject: JNIStorageObject
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import Foundation
|
||||
import CoreFoundation
|
||||
import java_swift
|
||||
import AnyCodable
|
||||
|
||||
public enum MissingFieldsStrategy: Error {
|
||||
case `throw`
|
||||
|
@ -26,6 +27,7 @@ indirect enum JNIStorageType {
|
|||
case object(className: String)
|
||||
case array
|
||||
case dictionary
|
||||
case anyCodable(codable: JNIStorageType)
|
||||
|
||||
var sig: String {
|
||||
switch self {
|
||||
|
@ -35,6 +37,8 @@ indirect enum JNIStorageType {
|
|||
return "L\(ArrayListClassname);"
|
||||
case .dictionary:
|
||||
return "L\(HashMapClassname);"
|
||||
case .anyCodable(let codable):
|
||||
return codable.sig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +120,9 @@ open class JavaEncoder: Encoder {
|
|||
case let .object(className):
|
||||
let container = JavaObjectContainer<Key>(referencing: self, codingPath: self.codingPath, javaClass: className, jniStorage: storage)
|
||||
return KeyedEncodingContainer(container)
|
||||
case .anyCodable:
|
||||
let container = JavaAnyCodableContainer<Key>(referencing: self, codingPath: self.codingPath, jniStorage: storage)
|
||||
return KeyedEncodingContainer(container)
|
||||
default:
|
||||
fatalError("Only keyed containers")
|
||||
}
|
||||
|
@ -190,7 +197,7 @@ fileprivate class JavaObjectContainer<K : CodingKey> : KeyedEncodingContainerPro
|
|||
}
|
||||
catch {
|
||||
if self.encoder.missingFieldsStrategy == .ignore {
|
||||
// Ignore
|
||||
NSLog("Ignore error: \(error)")
|
||||
}
|
||||
else {
|
||||
throw error
|
||||
|
@ -207,7 +214,8 @@ fileprivate class JavaObjectContainer<K : CodingKey> : KeyedEncodingContainerPro
|
|||
}
|
||||
|
||||
public func superEncoder() -> Encoder {
|
||||
preconditionFailure("Not implemented: superEncoder")
|
||||
self.encoder.javaObjects.append(self.jniStorage)
|
||||
return self.encoder
|
||||
}
|
||||
|
||||
public func superEncoder(forKey key: Key) -> Encoder {
|
||||
|
@ -423,6 +431,84 @@ class JavaEnumValueEncodingContainer: SingleValueEncodingContainer {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - AnyCodable Containers
|
||||
fileprivate class JavaAnyCodableContainer<K : CodingKey> : KeyedEncodingContainerProtocol {
|
||||
|
||||
typealias Key = K
|
||||
|
||||
// MARK: Properties
|
||||
/// A reference to the encoder we're writing to.
|
||||
private let encoder: JavaEncoder
|
||||
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], jniStorage: JNIStorageObject) {
|
||||
self.encoder = encoder
|
||||
self.codingPath = codingPath
|
||||
self.jniStorage = jniStorage
|
||||
}
|
||||
|
||||
private var javaObject: jobject {
|
||||
return jniStorage.javaObject
|
||||
}
|
||||
|
||||
// MARK: - KeyedEncodingContainerProtocol Methods
|
||||
public func encodeNil(forKey key: Key) throws {
|
||||
throw JavaCodingError.notSupported("JavaObjectContainer.encodeNil(forKey: \(key)")
|
||||
}
|
||||
|
||||
public func encode<T : Encodable>(_ value: T, forKey key: Key) throws {
|
||||
if key.stringValue == "typeName" {
|
||||
// ignore typeName
|
||||
return
|
||||
}
|
||||
do {
|
||||
let jniObject = try self.encoder.box(value)
|
||||
self.jniStorage.javaObject = JNI.api.NewLocalRef(JNI.env, jniObject.javaObject)
|
||||
}
|
||||
catch {
|
||||
if self.encoder.missingFieldsStrategy == .ignore {
|
||||
NSLog("Ignore error: \(error)")
|
||||
}
|
||||
else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
|
||||
preconditionFailure("Not implemented: nestedContainer")
|
||||
}
|
||||
|
||||
public func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
|
||||
switch self.jniStorage.type {
|
||||
case let .anyCodable(codable):
|
||||
switch codable {
|
||||
case .dictionary:
|
||||
return JavaHashMapUnkeyedContainer(referencing: self.encoder, codingPath: self.codingPath, jniStorage: self.jniStorage)
|
||||
case .array:
|
||||
return JavaArrayContainer(referencing: self.encoder, codingPath: self.codingPath, jniStorage: self.jniStorage)
|
||||
default:
|
||||
fatalError("Only single containers")
|
||||
}
|
||||
default:
|
||||
fatalError("Only single containers")
|
||||
}
|
||||
}
|
||||
|
||||
public func superEncoder() -> Encoder {
|
||||
preconditionFailure("Not implemented: superEncoder")
|
||||
}
|
||||
|
||||
public func superEncoder(forKey key: Key) -> Encoder {
|
||||
preconditionFailure("Not implemented: superEncoder")
|
||||
}
|
||||
}
|
||||
|
||||
extension JavaEncoder {
|
||||
|
||||
fileprivate func box<T: Encodable>(_ value: T) throws -> JNIStorageObject {
|
||||
|
@ -516,6 +602,31 @@ extension JavaEncoder {
|
|||
let uriObject = JNI.check(JNI.CallStaticObjectMethod(UriClass, methodID: UriConstructor!, args: args), &locals)
|
||||
storage = JNIStorageObject.init(type: .object(className: UriClassname), javaObject: uriObject!)
|
||||
}
|
||||
else if T.self == AnyCodable.self {
|
||||
let anyCodableValue = value as! AnyCodable
|
||||
let storageType: JNIStorageType
|
||||
let fullClassName: String
|
||||
if anyCodableValue.typeName == AnyCodable.DictionaryTypeName {
|
||||
fullClassName = HashMapClassname
|
||||
storageType = .anyCodable(codable: .dictionary)
|
||||
}
|
||||
else if anyCodableValue.typeName == AnyCodable.ArrayTypeName {
|
||||
fullClassName = ArrayListClassname
|
||||
storageType = .anyCodable(codable: .array)
|
||||
}
|
||||
else {
|
||||
fullClassName = package + "/" + anyCodableValue.typeName
|
||||
storageType = .anyCodable(codable: .object(className: fullClassName))
|
||||
}
|
||||
let javaClass = try JNI.getJavaClass(fullClassName)
|
||||
let emptyConstructor = try JNI.getJavaEmptyConstructor(forClass: fullClassName)
|
||||
guard let javaObject = JNI.api.NewObjectA(JNI.env, javaClass, emptyConstructor, nil) else {
|
||||
throw JavaCodingError.cantCreateObject(fullClassName)
|
||||
}
|
||||
storage = JNIStorageObject(type: storageType, javaObject: javaObject)
|
||||
javaObjects.append(storage)
|
||||
try anyCodableValue.encode(to: self)
|
||||
}
|
||||
else if Mirror(reflecting: value).displayStyle == .enum {
|
||||
let fullClassName = package + "/" + String(describing: type(of: value))
|
||||
// We don't create object for enum. Should be created at JavaEnumValueEncodingContainer
|
||||
|
@ -539,8 +650,8 @@ extension JavaEncoder {
|
|||
storageType = .object(className: fullClassName)
|
||||
}
|
||||
let javaClass = try JNI.getJavaClass(fullClassName)
|
||||
let emptyContructor = try JNI.getJavaEmptyConstructor(forClass: fullClassName)
|
||||
guard let javaObject = JNI.api.NewObjectA(JNI.env, javaClass, emptyContructor, nil) else {
|
||||
let emptyConstructor = try JNI.getJavaEmptyConstructor(forClass: fullClassName)
|
||||
guard let javaObject = JNI.api.NewObjectA(JNI.env, javaClass, emptyConstructor, nil) else {
|
||||
throw JavaCodingError.cantCreateObject(fullClassName)
|
||||
}
|
||||
storage = JNIStorageObject(type: storageType, javaObject: javaObject)
|
||||
|
|
Loading…
Reference in New Issue