- Notifications
You must be signed in to change notification settings - Fork26
SwiftJava/SwiftJava
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
I know you've been thinking.. "What I really need is a way to bridge Swift to Java"but there are a number of use cases:
Making Java technologies such as JDBC available to macOS applications.
Giving Swift applications on Linux a portable user interface using Swing.
Making business logic in written in Swift available to Android apps.
SwiftJava is a Swift code generator along with a small framework of supporting code written inthe Xcode beta6 vintage of Swift 3.0. The starting point was Boris Bügling'sCross Platform Swift talk.The code generator takes a java class, interface or package and generates Swift classesthat interface to corresponding Java methods using the Java Native Interface "JNI".These generated methods on the corresponding Swift class look something like this:
/// public java.lang.String java.lang.Object.toString()privatestaticvartoString_MethodID_7:jmethodID?openfunc toString()->String!{var__args=[jvalue]( repeating:jvalue(), count:1)let__return=JNIMethod.CallObjectMethod( object: javaObject, methodName:"toString", methodSig:"()Ljava/lang/String;", methodCache:&JavaObject.toString_MethodID_7, args:&__args, locals:nil)returnJNIType.decode( type:String(), from: __return)}
On macOS, this has been used to generate frameworks bridging the java.lang, java.util,java.sql, java.awt and javax.swing packages along with the Apple specific additionsin the com.apple package. This makes the Java apis available with auto-completion inthe Xcode source editor. The final application can be a ".app" or a command line utilitywhich should be portable to Linux using the Swift Package manager. For Android, the codegenerator can generate JNI code for a pair of interfaces from Java to Swift and fromSwift to Java saving the developer the chore of a lot of error prone manual stubbing.
To use, clone this project using the following command:
git clone https://github.com/SwiftJava/SwiftJava.git --recurse-submodules
This project contains the pre-generated java frameworks, an example macOS app usingJDBC and a command line project with assorted AWT and Swing source. Development insideXcode uses the legacy "JavaVM" framework which requires Apple's JVM downloadable from:
https://support.apple.com/kb/dl1572?locale=en_US
Development inside Xcode with the Oracle JVM is a little more complicated. It seemsthat JNI_CreateJavaVM generates a SIGSEGV internally as part of normal operationwhich is trapped using a signal handler so it can proceed on the command line.Unfortunately, this is caught by Xcode debugger lldb and it suspends and will notcontinue until you enterpr h -s false SIGSEGV
into the debug console each timeyou run the program. The alternative is to not use the debugger at all in your scheme.
Perversely, with AWT and Swing on macOS the JVM needs to be created on the main threadwhile setup code needs to be off the main thread to leave it available for AWT's ownrunloop. Use the JNI.background and JNI.run methods to achieve this in a portable manner.
When using the Swift package manager to build code from the command line, install thelatest Oracle JDK and locate the directory containing the file libjvm.so or .dylib inthe jre. Use the examples. directory to build using the following commands:
git clone https://github.com/SwiftJava/examples.gitcd examplesexport JVM_LIBRARY_PATH=$JAVA_HOME/jre/lib/server# macOSexport JVM_LIBRARY_PATH=$JAVA_HOME/jre/lib/amd64/server# Linuxulimit -n 10000# increase file descriptors for link swift build -Xlinker -L$JVM_LIBRARY_PATH -Xlinker -rpath -Xlinker$JVM_LIBRARY_PATH -Xlinker -ljvm
The swing source in"examples/Sources" shows how to receive events and subclass a Java class to have certainmethods such as java.awt.Canvas.paint() be implemented in Swift. More on this later.
For Android, consult the modified versions of the swift-android-samples and the associatedgradle build system plugin from theAndroid Toochain.This run on macOS or Ubuntu 16.04, and requires a Lollipop (api 21) or better device.More detaills and context are available in theSwift README for Androidandthis comprehensive tutorialbut hopefully the scripts in the modified gradle plugin take most of the pain out of it.
Once you have a toolchain and run its setup.sh script you should be able to type the following commands:
cd swift-android-samples/swifthello ./gradlew installDebug
For a new application, define two Java interfaces, one for messaging from Java to Swiftwith it's name ending in "Listener" and one for messaging back into Java from Swift.You then use ./genswift.sh from this project to generate the Swift binding code:
./genswift.sh your.package your.jar
This generates Swift classes and a third Java source src/org/swiftjava/your_package/YourAppProxy.javathat also needs to be included in your project. Consult the script genhello.sh and project"swift-android-samples/swifthello" for details. The source "swift-android-samples/swifthello/src/main/swift/Sources/main.swift"shows how to set this up with a native method called from the main activity.
import java_swiftvarresponder:SwiftHelloResponderForward!classListenerImpl:SwiftHelloListenerBase{overridefunc processNumber( number:Double){ responder.processedNumber( number+42.0)}overridefunc processText( text:String?){varout=String()for_in0..<100{ out+="Hello"+text!+"!"} responder.processedText( out)}}@_silgen_name("Java_net_zhuoweizhang_swifthello_SwiftHello_bind")publicfunc bind( __env:UnsafeMutablePointer<JNIEnv?>, __this:jobject?, __self:jobject?)->jobject?{ responder=SwiftHelloResponderForward( javaObject: __self)returnListenerImpl().javaObject}
For every Java interface the code generator generates a Swift Protocol alongwith a ProtocolForward class an instance of which conforms to the protocol andcan be used to message Java instances conforming to the interface/protocol.
For the Runnable interface used in threading the converse needs to be possiblewhere Java code can call through to Swift code. This is performed using a Javaproxy class which has a pointer to the associated Swift object and a "native"implementation of the "run()" method that calls through to Swift. On the Swiftside this is surfaced as the "RunnableBase" class which can be subclassed toprovide a Swift implementation of the "run()" method callable from Java.
This approach is also taken for processing events and all interfaces with namesending in "Listener", "Manager" or "Handler" also have "Base" classes generatedfor subclassing along with Java Proxy classes. On macOS and Linux these classesare compiled into a jar file ~/swiftjava.jar using the genjar.sh script for this to work.
classMyActionListener:ActionListenerBase{overridefunc actionPerformed(e event:ActionEvent?){System.exit(0);}} quitButton.addActionListener(MyActionListener());
Any interface/protocol from a Java interface can be added to a class to enable it tobe passed to Java provide it's name ends inListener
. The implementation is a littlecomplex but does not have an appreciable overhead. structs can also be made accesableto Java in this way but the object will no be live i.e. a coy of the struct will be taken.
Some event processing is also done by subclassing concrete classes that have namesending in "Adapter". Slightly modified Swift "Base" classes and Java Proxies are alsogenerated for this. Other classes have methods that are intended to be responsibilityof the subclasses such as java.awt.Canvas.paint(). A list of these methods needs to bemaintained in the code generator unfortunately. If one of these methods encountereda Base class and Proxy is generated for the concrete class that can be subclassed.
As these "Base" subclasses can't close over variables in your program you may want tohave an initialiser to capture these instead. There is a bit of a standard dancethat needs to be performed. Instantiate the "Base" superclass and assign it'sjavaObject to your classes' javaObject. Due to the use of generics you'llalso be prompted to provide a null implementation of the "required" initialiser.
classMyCanvas:CanvasBase{init(imageProducer:ImageProducer){ super.init(javaObject:nil)inherit(CanvasBase()) image=createImage(imageProducer)}requiredinit(javaObject:jobject?){fatalError("init(javaObject:) has not been implemented")}...
Consult the Swing examples code for further details.
The MIT License (MIT)Copyright (c) 2016, John Holdsworth
Permission is hereby granted, free of charge, to any person obtaining a copy of thissoftware and associated documentation files (the "Software"), to deal in the Softwarewithout restriction, including without limitation the rights to use, copy, modify, merge,publish, distribute, sublicense, and/or sell copies of the Software, and to permit personsto whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULARPURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLEFOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This License does not apply to the code generated from the Apple distribution of the Java VMwhich are provided under the provisions of "Fair Use" and your use is ultimately subjectto the original License Agreement.
About
Swift to Java Bridge
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.