Dev: add support of AnyCodable and super Encoding/Decoding

This commit is contained in:
Andrew Druk 2017-12-01 12:10:56 +02:00
parent fa93e4ed20
commit 31ac6c999e
4 changed files with 241 additions and 8 deletions

View File

@ -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]
)

View File

@ -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")

View File

@ -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

View File

@ -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)