├── .gitignore ├── Package.swift ├── README.md └── Sources └── JavaCelsiusConverterGUI ├── CelsiusConverterGUI+Native.swift ├── Java2Swift.config └── learn └── CelsiusConverterGUI.java /.gitignore: -------------------------------------------------------------------------------- 1 | .swiftpm 2 | .build 3 | Package.resolved 4 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import CompilerPluginSupport 5 | import PackageDescription 6 | 7 | import class Foundation.FileManager 8 | import class Foundation.ProcessInfo 9 | 10 | // Note: the JAVA_HOME environment variable must be set to point to where 11 | // Java is installed, e.g., 12 | // Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. 13 | func findJavaHome() -> String { 14 | if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { 15 | return home 16 | } 17 | 18 | // This is a workaround for envs (some IDEs) which have trouble with 19 | // picking up env variables during the build process 20 | let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" 21 | if let home = try? String(contentsOfFile: path, encoding: .utf8) { 22 | if let lastChar = home.last, lastChar.isNewline { 23 | return String(home.dropLast()) 24 | } 25 | 26 | return home 27 | } 28 | 29 | fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") 30 | } 31 | let javaHome = findJavaHome() 32 | 33 | let javaIncludePath = "\(javaHome)/include" 34 | #if os(Linux) 35 | let javaPlatformIncludePath = "\(javaIncludePath)/linux" 36 | #elseif os(macOS) 37 | let javaPlatformIncludePath = "\(javaIncludePath)/darwin" 38 | #else 39 | // TODO: Handle windows as well 40 | #error("Currently only macOS and Linux platforms are supported, this may change in the future.") 41 | #endif 42 | 43 | let package = Package( 44 | name: "JavaCelsiusConverterGUI", 45 | platforms: [ 46 | .macOS(.v13), 47 | .iOS(.v13), 48 | .tvOS(.v13), 49 | .watchOS(.v6), 50 | .macCatalyst(.v13), 51 | ], 52 | 53 | products: [ 54 | .library( 55 | name: "JavaCelsiusConverterGUI", 56 | type: .dynamic, 57 | targets: ["JavaCelsiusConverterGUI"] 58 | ), 59 | ], 60 | 61 | dependencies: [ 62 | .package(url: "https://github.com/swiftlang/swift-java", branch: "main"), 63 | ], 64 | 65 | targets: [ 66 | .target( 67 | name: "JavaCelsiusConverterGUI", 68 | dependencies: [ 69 | .product(name: "JavaKit", package: "swift-java"), 70 | .product(name: "JavaKitJar", package: "swift-java"), 71 | ], 72 | swiftSettings: [ 73 | .swiftLanguageMode(.v5), 74 | .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) 75 | ], 76 | plugins: [ 77 | .plugin(name: "JavaCompilerPlugin", package: "swift-java"), 78 | .plugin(name: "Java2SwiftPlugin", package: "swift-java"), 79 | ] 80 | ), 81 | ] 82 | ) 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift/Java Interoperability Example: Swing Celsius -> Fahrenheit converter 2 | 3 | This repository demonstrates Swift targets that mix Java and Swift 4 | source files, where Swift can be used to implement `native` methods 5 | declared in Java. It uses the 6 | [swift-java](https://github.com/swiftlang/swift-java/) package for 7 | Swift/Java interoperability. 8 | 9 | ## How to run the example 10 | 11 | First, set the `JAVA_HOME` environment variable to point to your Java installation. 12 | 13 | Then, build the example with the following command: 14 | 15 | ``` 16 | swift build 17 | ``` 18 | 19 | Finally, run the example as follows: 20 | 21 | ``` 22 | java -cp .build/plugins/outputs/celsius-converter/JavaCelsiusConverterGUI/destination/JavaCompilerPlugin/Java -Djava.library.path=.build/debug learn.CelsiusConverterGUI 23 | ``` 24 | 25 | ## Exploring the source 26 | 27 | The project only has two source files: 28 | * `Sources/JavaCelsiusConverterGUI/learn/CelsiusConverterGUI.java`: Java source code for the Java parts of this example, which originally came from [a Java tutorial](https://docs.oracle.com/javase/tutorial/uiswing/learn/creatinggui.html). 29 | * `Sources/JavaCelsiusConverterGUI/CelsiusConverterGUI+Native.swift`: Swift source code that implements the `native` Java methods for `CelsiusConverterGUI`. 30 | 31 | ## Evolution of the example from Java to (mostly) Swift 32 | 33 | The HEAD of the `main` branch contains the final version of the the program, with a mix of Java and Swift. Browse back in the git history for this project to see earlier states, starting with an all-Java implementation and then moving toward Swift implementations one commit at a time. -------------------------------------------------------------------------------- /Sources/JavaCelsiusConverterGUI/CelsiusConverterGUI+Native.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CelsiusConverterGUI+Native.swift 3 | // JavaCelsiusConverterGUI 4 | // 5 | // Created by Doug Gregor on 10/24/24. 6 | // 7 | import JavaKit 8 | 9 | @JavaImplementation("learn.CelsiusConverterGUI") 10 | extension CelsiusConverterGUI: CelsiusConverterGUINativeMethods { 11 | @JavaMethod 12 | func initComponents() { 13 | tempTextField = JTextField() 14 | celsiusLabel = JLabel() 15 | convertButton = JButton() 16 | fahrenheitLabel = JLabel() 17 | 18 | try! setDefaultCloseOperation(JavaClass().EXIT_ON_CLOSE) 19 | setTitle("Celsius Converter in Swift") 20 | 21 | celsiusLabel.setText("Celsius") 22 | convertButton.setText("Convert") 23 | fahrenheitLabel.setText("Fahrenheit") 24 | 25 | let layout = GroupLayout(getContentPane()) 26 | getContentPane().setLayout(layout.as(LayoutManager.self)) 27 | 28 | let groupAlignmentClass = try! JavaClass() 29 | let groupLayoutClass = try! JavaClass() 30 | let componentPlacementClass = try! JavaClass() 31 | let swingConstantsClass = try! JavaClass() 32 | try! layout.setHorizontalGroup( 33 | layout.createParallelGroup(groupAlignmentClass.LEADING) 34 | .addGroup(layout.createSequentialGroup() 35 | .addContainerGap() 36 | .addGroup(layout.createParallelGroup(groupAlignmentClass.LEADING) 37 | .addGroup(layout.createSequentialGroup() 38 | .addComponent(tempTextField, 39 | groupLayoutClass.PREFERRED_SIZE, 40 | groupLayoutClass.DEFAULT_SIZE, 41 | groupLayoutClass.PREFERRED_SIZE) 42 | .addPreferredGap(componentPlacementClass.RELATED) 43 | .addComponent(celsiusLabel) 44 | .as(GroupLayout.Group.self)) 45 | .addGroup(layout.createSequentialGroup() 46 | .addComponent(convertButton) 47 | .addPreferredGap(componentPlacementClass.RELATED) 48 | .addComponent(fahrenheitLabel) 49 | .as(GroupLayout.Group.self)) 50 | .as(GroupLayout.Group.self)) 51 | .addContainerGap(27, Int32(JavaClass().MAX_VALUE)) 52 | .as(GroupLayout.Group.self)) 53 | .as(GroupLayout.Group.self) 54 | ) 55 | 56 | layout.linkSize(swingConstantsClass.HORIZONTAL, [convertButton, tempTextField]) 57 | 58 | try! layout.setVerticalGroup( 59 | layout.createParallelGroup(groupAlignmentClass.LEADING) 60 | .addGroup(layout.createSequentialGroup() 61 | .addContainerGap() 62 | .addGroup(layout.createParallelGroup(groupAlignmentClass.BASELINE) 63 | .addComponent(tempTextField, groupLayoutClass.PREFERRED_SIZE, groupLayoutClass.DEFAULT_SIZE, groupLayoutClass.PREFERRED_SIZE) 64 | .addComponent(celsiusLabel) 65 | .as(GroupLayout.Group.self)) 66 | .addPreferredGap(componentPlacementClass.RELATED) 67 | .addGroup(layout.createParallelGroup(groupAlignmentClass.BASELINE) 68 | .addComponent(convertButton) 69 | .addComponent(fahrenheitLabel) 70 | .as(GroupLayout.Group.self)) 71 | .addContainerGap(21, Int32(JavaClass().MAX_VALUE)) 72 | .as(GroupLayout.Group.self) 73 | )! 74 | .as(GroupLayout.Group.self) 75 | ) 76 | 77 | pack() 78 | } 79 | 80 | @JavaMethod 81 | func convertButtonActionPerformed(_ evt: ActionEvent?) { 82 | if let celsius = Double(tempTextField.getText()) { 83 | let fahrenheit = celsius * 1.8 + 32 84 | fahrenheitLabel.setText("\(fahrenheit)°F") 85 | } else { 86 | fahrenheitLabel.setText("(not a number)") 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/JavaCelsiusConverterGUI/Java2Swift.config: -------------------------------------------------------------------------------- 1 | { 2 | "classes" : { 3 | "learn.CelsiusConverterGUI" : "CelsiusConverterGUI", 4 | "java.awt.event.ActionListener" : "ActionListener", 5 | "java.awt.Container" : "AWTContainer", 6 | "java.awt.Component" : "AWTComponent", 7 | "java.awt.event.ActionEvent" : "ActionEvent", 8 | "javax.swing.AbstractButton" : "AbstractButton", 9 | "javax.swing.JLabel" : "JLabel", 10 | "javax.swing.JButton" : "JButton", 11 | "javax.swing.JComponent" : "JComponent", 12 | "javax.swing.JLabel" : "JLabel", 13 | "javax.swing.text.JTextComponent" : "JTextComponent", 14 | "javax.swing.JTextField" : "JTextField", 15 | "javax.swing.WindowConstants" : "WindowConstants", 16 | "java.awt.Frame" : "Frame", 17 | "javax.swing.JFrame" : "JFrame", 18 | "javax.swing.GroupLayout" : "GroupLayout", 19 | "javax.swing.GroupLayout$Alignment" : "GroupLayout.Alignment", 20 | "javax.swing.GroupLayout$Group" : "GroupLayout.Group", 21 | "javax.swing.GroupLayout$ParallelGroup" : "GroupLayout.ParallelGroup", 22 | "javax.swing.GroupLayout$SequentialGroup" : "GroupLayout.SequentialGroup", 23 | "javax.swing.LayoutStyle" : "LayoutStyle", 24 | "javax.swing.LayoutStyle$ComponentPlacement" : "LayoutStyle.ComponentPlacement", 25 | "java.awt.LayoutManager" : "LayoutManager", 26 | "javax.swing.SwingConstants" : "SwingConstants", 27 | "java.awt.Window" : "Window" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/JavaCelsiusConverterGUI/learn/CelsiusConverterGUI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 11 | * - Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 15 | * - Neither the name of Oracle or the names of its 16 | * contributors may be used to endorse or promote products derived 17 | * from this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 20 | * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 21 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 23 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | /* 33 | * CelsiusConverterGUI.java 34 | * 35 | */ 36 | 37 | package learn; 38 | 39 | public class CelsiusConverterGUI extends javax.swing.JFrame { 40 | static { 41 | System.loadLibrary("JavaCelsiusConverterGUI"); 42 | } 43 | 44 | /** Creates new form CelsiusConverterGUI */ 45 | public CelsiusConverterGUI() { 46 | initComponents(); 47 | 48 | convertButton.addActionListener(new java.awt.event.ActionListener() { 49 | public void actionPerformed(java.awt.event.ActionEvent evt) { 50 | convertButtonActionPerformed(evt); 51 | } 52 | }); 53 | } 54 | 55 | native void initComponents(); 56 | 57 | native void convertButtonActionPerformed(java.awt.event.ActionEvent evt); 58 | 59 | /** 60 | * @param args the command line arguments 61 | */ 62 | public static void main(String args[]) { 63 | java.awt.EventQueue.invokeLater(new Runnable() { 64 | public void run() { 65 | new CelsiusConverterGUI().setVisible(true); 66 | } 67 | }); 68 | } 69 | 70 | public javax.swing.JLabel celsiusLabel; 71 | public javax.swing.JButton convertButton; 72 | public javax.swing.JLabel fahrenheitLabel; 73 | public javax.swing.JTextField tempTextField; 74 | } 75 | --------------------------------------------------------------------------------