Skip to content

Commit

Permalink
Bridge 'Any' types
Browse files Browse the repository at this point in the history
  • Loading branch information
aabewhite committed Dec 11, 2024
1 parent 419c7ea commit 9200251
Show file tree
Hide file tree
Showing 10 changed files with 371 additions and 57 deletions.
10 changes: 4 additions & 6 deletions Sources/SkipBridge/BridgeSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,15 @@ extension SwiftObjectPointer {
return try! SwiftObjectPointer.call(Java_PeerBridged_peer_methodID, on: bridged, options: options, args: [])
}

/// Return a pointer to the Swift instance for a given Java object.
public static func projection(of object: JavaObjectPointer, options: JConvertibleOptions, peerOnly: Bool = false) -> SwiftObjectPointer? {
/// Return the `Swift_peer` of the given Kotlin object if it is `SwiftPeerBridged`.
public static func tryPeer(of object: JavaObjectPointer, options: JConvertibleOptions) -> SwiftObjectPointer? {
let object_java = object.toJavaParameter(options: options)
let options_java = options.rawValue.toJavaParameter(options: options)
let peerOnly_java = peerOnly.toJavaParameter(options: options)
let ptr: SwiftObjectPointer = try! Java_fileClass.callStatic(method: Java_projection_methodID, options: options, args: [object_java, options_java, peerOnly_java])
let ptr: SwiftObjectPointer = try! Java_fileClass.callStatic(method: Java_tryPeer_methodID, options: options, args: [object_java])
return ptr == SwiftObjectNil ? nil : ptr
}
}
private let Java_fileClass = try! JClass(name: "skip/bridge/kt/BridgeSupportKt")
private let Java_projection_methodID = Java_fileClass.getStaticMethodID(name: "Swift_projection", sig: "(Ljava/lang/Object;IZ)J")!
private let Java_tryPeer_methodID = Java_fileClass.getStaticMethodID(name: "Swift_peer", sig: "(Ljava/lang/Object;)J")!
private let Java_PeerBridged_class = try! JClass(name: "skip/bridge/kt/SwiftPeerBridged")
private let Java_PeerBridged_peer_methodID = Java_PeerBridged_class.getMethodID(name: "Swift_peer", sig: "()J")!

Expand Down
164 changes: 157 additions & 7 deletions Sources/SkipBridge/BridgedTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,131 @@

import Foundation

//
// NOTE:
// Keep this in sync with `SkipBridgeKt.BridgedTypes`
//

/// Supported bridged type constants.
public enum BridgedTypes: String {
case boolean_
case byte_
case char_
case double_
case float_
case int_
case long_
case short_
case string_

case byteArray
case date
case list
case map
case set
case uuid
case uri

case swiftArray
case swiftData
case swiftDate
case swiftDictionary
case swiftSet
case swiftUUID
case swiftURL

case other
}

/// Utilities to convert unknown bridged objects.
public struct AnyBridging {
/// Convert an unknown Kotlin/Java instance to its Swift projection.
public static func fromJavaObject(_ ptr: JavaObjectPointer?, options: JConvertibleOptions, fallback: (() -> Any)? = nil) -> Any? {
guard let ptr else {
return nil
}
if let projection = tryProjection(of: ptr, options: options) {
return projection
}

let bridgedTypeString = bridgedTypeString(of: ptr, options: options)
let bridgedType = BridgedTypes(rawValue: bridgedTypeString) ?? .other
switch bridgedType {
case .boolean_:
return Bool.fromJavaObject(ptr, options: options)
case .byte_:
return Int8.fromJavaObject(ptr, options: options)
case .char_:
// TODO
// return Character.fromJavaObject(ptr, options: options)
fatalError("Character is not yet bridgable")
case .double_:
return Double.fromJavaObject(ptr, options: options)
case .float_:
return Float.fromJavaObject(ptr, options: options)
case .int_:
return Int.fromJavaObject(ptr, options: options)
case .long_:
return Int64.fromJavaObject(ptr, options: options)
case .short_:
return Int16.fromJavaObject(ptr, options: options)
case .string_:
return String.fromJavaObject(ptr, options: options)
case .byteArray:
return Data.fromJavaObject(ptr, options: options)
case .date:
return Date.fromJavaObject(ptr, options: options)
case .list:
return Array<Any>.fromJavaObject(ptr, options: options)
case .map:
return Dictionary<AnyHashable, Any>.fromJavaObject(ptr, options: options)
case .set:
return Array<AnyHashable>.fromJavaObject(ptr, options: options)
case .uuid:
return UUID.fromJavaObject(ptr, options: options)
case .uri:
return URL.fromJavaObject(ptr, options: options)
case .swiftArray:
return Array<Any>.fromJavaObject(ptr, options: options)
case .swiftData:
return Data.fromJavaObject(ptr, options: options)
case .swiftDate:
return Date.fromJavaObject(ptr, options: options)
case .swiftDictionary:
return Dictionary<AnyHashable, Any>.fromJavaObject(ptr, options: options)
case .swiftSet:
return Array<AnyHashable>.fromJavaObject(ptr, options: options)
case .swiftUUID:
return UUID.fromJavaObject(ptr, options: options)
case .swiftURL:
return URL.fromJavaObject(ptr, options: options)
case .other:
if let fallback {
return fallback()
} else {
fatalError("Unable to bridge Kotlin/Java instance \(ptr)")
}
}
}

private static func tryProjection(of ptr: JavaObjectPointer, options: JConvertibleOptions) -> Any? {
let ptr_java = ptr.toJavaParameter(options: options)
let options_java = options.rawValue.toJavaParameter(options: options)
let closure_java: JavaObjectPointer? = try! Java_fileClass.callStatic(method: Java_tryProjection_methodID, options: options, args: [ptr_java, options_java])
let closure: (() -> Any)? = SwiftClosure0.closure(forJavaObject: closure_java, options: options)
return closure?()
}

private static func bridgedTypeString(of ptr: JavaObjectPointer, options: JConvertibleOptions) -> String {
let ptr_java = ptr.toJavaParameter(options: options)
return try! Java_fileClass.callStatic(method: Java_bridgedTypeString_methodID, options: options, args: [ptr_java])
}
}

private let Java_fileClass = try! JClass(name: "skip/bridge/kt/BridgeSupportKt")
private let Java_tryProjection_methodID = Java_fileClass.getStaticMethodID(name: "Swift_projection", sig: "(Ljava/lang/Object;I)Lkotlin/jvm/functions/Function0;")!
private let Java_bridgedTypeString_methodID = Java_fileClass.getStaticMethodID(name: "bridgedTypeStringOf", sig: "(Ljava/lang/Object;)Ljava/lang/String;")!

//
// NOTE:
// The Kotlin version of custom converting types should conform to `SwiftCustomBridged`.
Expand All @@ -29,11 +154,17 @@ extension Array: JObjectProtocol, JConvertible {
for i in 0..<count {
// arr.append(list.get(i))
let element_java = try! JavaObjectPointer?.call(Java_List_get_methodID, on: list_java, options: options, args: [i.toJavaParameter(options: options)])
let element = (Element.self as! JConvertible.Type).fromJavaObject(element_java, options: options)
// Convert non-polymorphic JConvertibles directly, else fall back to AnyBridging
let element: Element
if let convertibleElement = Element.self as? JConvertible.Type, !(Element.self is AnyObject.Type) {
element = convertibleElement.fromJavaObject(element_java, options: options) as! Element
} else {
element = AnyBridging.fromJavaObject(element_java, options: options) as! Element
}
arr.append(element)
if let element_java {
jni.deleteLocalRef(element_java)
}
arr.append(element as! Element)
}
return arr
}
Expand Down Expand Up @@ -155,15 +286,27 @@ extension Dictionary: JObjectProtocol, JConvertible {
// let key = itr.next(); let value = map.get(key)
let key_java = try! JavaObjectPointer?.call(Java_Iterator_next_methodID, on: iterator_java, options: options, args: [])
let value_java = try! JavaObjectPointer?.call(Java_Map_get_methodID, on: map_java, options: options, args: [key_java.toJavaParameter(options: options)])
let key = (Key.self as! JConvertible.Type).fromJavaObject(key_java, options: options)
let value = (Value.self as! JConvertible.Type).fromJavaObject(value_java, options: options)

// Convert non-polymorphic JConvertibles directly, else fall back to AnyBridging
let key: Key
let value: Value
if let convertibleKey = Key.self as? JConvertible.Type, !(Key.self is AnyObject.Type) {
key = convertibleKey.fromJavaObject(key_java, options: options) as! Key
} else {
key = AnyBridging.fromJavaObject(key_java, options: options) as! Key
}
if let convertibleValue = Value.self as? JConvertible.Type, !(Value.self is AnyObject.Type) {
value = convertibleValue.fromJavaObject(value_java, options: options) as! Value
} else {
value = AnyBridging.fromJavaObject(value_java, options: options) as! Value
}
dict[key] = value
if let key_java {
jni.deleteLocalRef(key_java)
}
if let value_java {
jni.deleteLocalRef(value_java)
}
dict[key as! Key] = value as? Value
}
return dict
}
Expand Down Expand Up @@ -225,11 +368,18 @@ extension Set: JObjectProtocol, JConvertible {
for _ in 0..<size {
// set.insert(itr.next())
let element_java = try! JavaObjectPointer?.call(Java_Iterator_next_methodID, on: iterator_java, options: options, args: [])
let element = (Element.self as! JConvertible.Type).fromJavaObject(element_java, options: options)
// Convert non-polymorphic JConvertibles directly, else fall back to AnyBridging
let element: Element
if let convertibleElement = Element.self as? JConvertible.Type, !(Element.self is AnyObject.Type) {
element = convertibleElement.fromJavaObject(element_java, options: options) as! Element
} else {
element = AnyBridging.fromJavaObject(element_java, options: options) as! Element
}
set.insert(element)
if let element_java {
jni.deleteLocalRef(element_java)
}
set.insert(element as! Element)

}
return set
}
Expand Down
12 changes: 6 additions & 6 deletions Sources/SkipBridge/Closures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public final class SwiftClosure0 {
guard let function else {
return nil
}
if let ptr = SwiftObjectPointer.projection(of: function, options: options, peerOnly: true) {
if let ptr = SwiftObjectPointer.tryPeer(of: function, options: options) {
let closure: SwiftClosure0 = ptr.pointee()!
return { closure.closure() as! R }
} else {
Expand Down Expand Up @@ -155,7 +155,7 @@ public final class SwiftClosure1 {
guard let function else {
return nil
}
if let ptr = SwiftObjectPointer.projection(of: function, options: options, peerOnly: true) {
if let ptr = SwiftObjectPointer.tryPeer(of: function, options: options) {
let closure: SwiftClosure1 = ptr.pointee()!
return { p0 in closure.closure(p0) as! R }
} else {
Expand Down Expand Up @@ -206,7 +206,7 @@ public final class SwiftClosure2 {
guard let function else {
return nil
}
if let ptr = SwiftObjectPointer.projection(of: function, options: options, peerOnly: true) {
if let ptr = SwiftObjectPointer.tryPeer(of: function, options: options) {
let closure: SwiftClosure2 = ptr.pointee()!
return { p0, p1 in closure.closure(p0, p1) as! R }
} else {
Expand Down Expand Up @@ -260,7 +260,7 @@ public final class SwiftClosure3 {
guard let function else {
return nil
}
if let ptr = SwiftObjectPointer.projection(of: function, options: options, peerOnly: true) {
if let ptr = SwiftObjectPointer.tryPeer(of: function, options: options) {
let closure: SwiftClosure3 = ptr.pointee()!
return { p0, p1, p2 in closure.closure(p0, p1, p2) as! R }
} else {
Expand Down Expand Up @@ -317,7 +317,7 @@ public final class SwiftClosure4 {
guard let function else {
return nil
}
if let ptr = SwiftObjectPointer.projection(of: function, options: options, peerOnly: true) {
if let ptr = SwiftObjectPointer.tryPeer(of: function, options: options) {
let closure: SwiftClosure4 = ptr.pointee()!
return { p0, p1, p2, p3 in closure.closure(p0, p1, p2, p3) as! R }
} else {
Expand Down Expand Up @@ -377,7 +377,7 @@ public final class SwiftClosure5 {
guard let function else {
return nil
}
if let ptr = SwiftObjectPointer.projection(of: function, options: options, peerOnly: true) {
if let ptr = SwiftObjectPointer.tryPeer(of: function, options: options) {
let closure: SwiftClosure5 = ptr.pointee()!
return { p0, p1, p2, p3, p4 in closure.closure(p0, p1, p2, p3, p4) as! R }
} else {
Expand Down
37 changes: 18 additions & 19 deletions Sources/SkipBridgeKt/BridgeSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,33 @@ public typealias SwiftObjectPointer = Int64
public let SwiftObjectNil = Int64(0)

/// Return a Swift object pointer to a Kotlin instance, else `SwiftObjectNil`.
public func Swift_projection(of object: Any, options: Int, peerOnly: Bool) -> SwiftObjectPointer {
if peerOnly {
let peer = (object as? SwiftPeerBridged)?.Swift_peer()
return peer ?? SwiftObjectNil
} else {
let projection = (object as? SwiftProjectable)?.Swift_projection(options: options)
return projection ?? SwiftObjectNil
}
}

/// Protocol added to a Kotlin type that can project to Swift.
public protocol SwiftProjectable {
func Swift_projection(options: Int) -> SwiftObjectPointer
public func Swift_peer(of object: Any) -> SwiftObjectPointer {
let peer = (object as? SwiftPeerBridged)?.Swift_peer()
return peer ?? SwiftObjectNil
}

/// Protocol added to a Kotlin projection type backed by a Swift peer object.
public protocol SwiftPeerBridged {
func Swift_peer() -> SwiftObjectPointer
}

extension SwiftPeerBridged: SwiftProjectable {
public func Swift_projection(options: Int) -> SwiftObjectPointer {
return Swift_peer()
}
}

/// Marker type used to guarantee uniqueness of our `Swift_peer` constructor.
public final class SwiftPeerMarker {
}

/// Return a closure that creates a Swift projection of this Kotlin instance, else nil.
public func Swift_projection(of object: Any, options: Int) -> (() -> Any)? {
return (object as? SwiftProjectable)?.Swift_projection(options: options)
}

/// Protocol added to a Kotlin type that can project to Swift.
public protocol SwiftProjectable {
func Swift_projection(options: Int) -> () -> Any
}

/// Return the `BridgedTypes` enum case name for the given Kotlin/Java object's type.
public func bridgedTypeStringOf(_ object: Any) -> String {
return bridgedTypeOf(object).name
}

#endif
Loading

0 comments on commit 9200251

Please sign in to comment.