Skip to content

Commit

Permalink
Merge pull request #33 from skiptools/anybridge
Browse files Browse the repository at this point in the history
Bridge 'Any' types
  • Loading branch information
aabewhite authored Dec 11, 2024
2 parents 096a18b + 9200251 commit 960572b
Show file tree
Hide file tree
Showing 13 changed files with 447 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
// under the terms of the GNU Lesser General Public License 3.0
// as published by the Free Software Foundation https://fsf.org

/// Protocol added to `@BridgeToKotlin` types.
/// Protocol added to compiled Swift types that are bridged to Kotlin.
public protocol BridgedToKotlin: JObjectProtocol, JConvertible {
}

/// Protocol added to compiled Swift projections generated from bridged Kotlin types.
public protocol BridgedFromKotlin: JObjectProtocol, JConvertible {
}

/// An opaque reference to a Swift object.
public typealias SwiftObjectPointer = Int64
public let SwiftObjectNil = Int64(0)
Expand Down Expand Up @@ -53,21 +57,22 @@ extension SwiftObjectPointer {
}

extension SwiftObjectPointer {
/// Return the `Swift_peer` of the given `SwiftPeerBridged` object.
/// Return the `Swift_peer` of the given `SwiftPeerBridged` Kotlin object.
public static func peer(of bridged: JavaObjectPointer, options: JConvertibleOptions) -> SwiftObjectPointer {
return try! SwiftObjectPointer.call(Java_SwiftPeerBridged_peer_methodID, on: bridged, options: options, args: [])
return try! SwiftObjectPointer.call(Java_PeerBridged_peer_methodID, on: bridged, options: options, args: [])
}

/// Check whether the given object is `SwiftPeerBridged` and if so, return its `Swift_peer`.
public static func filterPeer(of bridged: JavaObjectPointer, options: JConvertibleOptions) -> SwiftObjectPointer? {
let ptr: SwiftObjectPointer = try! Java_fileClass.callStatic(method: Java_peer_methodID, options: options, args: [bridged.toJavaParameter(options: options)])
/// 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 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/BridgeToKotlinSupportKt")
private let Java_peer_methodID = Java_fileClass.getStaticMethodID(name: "Swift_bridgedPeer", sig: "(Ljava/lang/Object;)J")!
private let Java_SwiftPeerBridged_class = try! JClass(name: "skip/bridge/kt/SwiftPeerBridged")
private let Java_SwiftPeerBridged_peer_methodID = Java_SwiftPeerBridged_class.getMethodID(name: "Swift_bridgedPeer", sig: "()J")!
private let Java_fileClass = try! JClass(name: "skip/bridge/kt/BridgeSupportKt")
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")!

/// Reference type to hold a value type.
public final class SwiftValueTypeBox<T> {
Expand Down
9 changes: 0 additions & 9 deletions Sources/SkipBridge/BridgeToSwiftSupport.swift

This file was deleted.

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.filterPeer(of: function, options: options) {
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.filterPeer(of: function, options: options) {
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.filterPeer(of: function, options: options) {
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.filterPeer(of: function, options: options) {
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.filterPeer(of: function, options: options) {
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.filterPeer(of: function, options: options) {
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
43 changes: 43 additions & 0 deletions Sources/SkipBridgeKt/BridgeSupport.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2024 Skip
//
// This is free software: you can redistribute and/or modify it
// under the terms of the GNU Lesser General Public License 3.0
// as published by the Free Software Foundation https://fsf.org

#if SKIP

/// An opaque reference to a Swift object.
public typealias SwiftObjectPointer = Int64
public let SwiftObjectNil = Int64(0)

/// Return a Swift object pointer to a Kotlin instance, else `SwiftObjectNil`.
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
}

/// 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
27 changes: 0 additions & 27 deletions Sources/SkipBridgeKt/BridgeToKotlinSupport.swift

This file was deleted.

Loading

0 comments on commit 960572b

Please sign in to comment.