Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ let package = Package(
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
.package(url: "https://github.com/apple/swift-system", from: "1.4.0"),
.package(url: "https://github.com/apple/swift-log", from: "1.2.0"),
.package(url: "https://github.com/apple/swift-collections", .upToNextMinor(from: "1.3.0")), // primarily for ordered collections

// // FIXME: swift-subprocess stopped supporting 6.0 when it moved into a package;
// // we'll need to drop 6.0 as well, but currently blocked on doing so by swiftpm plugin pending design questions
Expand Down Expand Up @@ -400,6 +401,7 @@ let package = Package(
name: "SwiftJavaToolLib",
dependencies: [
.product(name: "Logging", package: "swift-log"),
.product(name: "OrderedCollections", package: "swift-collections"),
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
Expand Down
11 changes: 11 additions & 0 deletions Samples/JavaKitSampleApp/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,16 @@ let package = Package(
.plugin(name: "SwiftJavaPlugin", package: "swift-java"),
]
),

.testTarget(
name: "JavaKitExampleTests",
dependencies: [
"JavaKitExample"
],
swiftSettings: [
.swiftLanguageMode(.v5),
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
]
),
]
)
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,28 @@
public class ThreadSafeHelperClass {
public ThreadSafeHelperClass() { }

public Optional<String> text = Optional.of("");
public Optional<String> text = Optional.of("cool string");

public final OptionalDouble val = OptionalDouble.of(2);

public String getValue(Optional<String> name) {
return name.orElse("");
}


public String getOrElse(Optional<String> name) {
return name.orElse("or else value");
}

public Optional<String> getNil() {
return Optional.empty();
}

// @NonNull
// public Optional<String> getNil() {
// return Optional.empty();
// }

public Optional<String> getText() {
return text;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import JavaKitExample

import SwiftJava
import JavaUtilFunction
import Testing

@Suite
struct ManglingTests {

@Test
func methodMangling() throws {
let jvm = try! JavaVirtualMachine.shared(
classpath: [
".build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java"
]
)
let env = try! jvm.environment()

let helper = ThreadSafeHelperClass(environment: env)

let text: JavaString? = helper.textOptional
#expect(#"Optional("cool string")"# == String(describing: Optional("cool string")))
#expect(#"Optional("cool string")"# == String(describing: text))

// let defaultValue: String? = helper.getOrElse(JavaOptional<JavaString>.empty())
// #expect(#"Optional("or else value")"# == String(describing: defaultValue))

let noneValue: JavaOptional<JavaString> = helper.getNil()!
#expect(noneValue.isPresent() == false)
#expect("\(noneValue)" == "SwiftJava.JavaOptional<SwiftJava.JavaString>")

let textFunc: JavaString? = helper.getTextOptional()
#expect(#"Optional("cool string")"# == String(describing: textFunc))

let doubleOpt: Double? = helper.valOptional
#expect(#"Optional(2.0)"# == String(describing: doubleOpt))

let longOpt: Int64? = helper.fromOptional(21 as Int32?)
#expect(#"Optional(21)"# == String(describing: longOpt))
}

}
4 changes: 3 additions & 1 deletion Samples/JavaKitSampleApp/ci-validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
set -e
set -x

swift build
swift build \
--disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418

"$JAVA_HOME/bin/java" \
-cp .build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java \
-Djava.library.path=.build/debug \
Expand Down
6 changes: 5 additions & 1 deletion Sources/SwiftJava/AnyJavaObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ public protocol AnyJavaObjectWithCustomClassLoader: AnyJavaObject {
extension AnyJavaObject {
/// Retrieve the underlying Java object.
public var javaThis: jobject {
javaHolder.object!
javaHolder.object! // FIXME: this is a bad idea, can be null
}

public var javaThisOptional: jobject? {
javaHolder.object
}

/// Retrieve the environment in which this Java object resides.
Expand Down
18 changes: 9 additions & 9 deletions Sources/SwiftJava/JavaObject+Inheritance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ extension AnyJavaObject {
private func isInstanceOf<OtherClass: AnyJavaObject>(
_ otherClass: OtherClass.Type
) -> jclass? {
try? otherClass.withJNIClass(in: javaEnvironment) { otherJavaClass in
if javaEnvironment.interface.IsInstanceOf(
javaEnvironment,
javaThis,
otherJavaClass
) == 0 {
return nil
}
guard let this: jobject = javaThisOptional else {
return nil
}

return try? otherClass.withJNIClass(in: javaEnvironment) { otherJavaClass in
if javaEnvironment.interface.IsInstanceOf(javaEnvironment, this, otherJavaClass) == 0 {
return nil
}

return otherJavaClass
return otherJavaClass
}
}

Expand Down
34 changes: 34 additions & 0 deletions Sources/SwiftJava/JavaString+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import CSwiftJavaJNI
import JavaTypes

extension JavaString: CustomStringConvertible, CustomDebugStringConvertible {
public var description: String {
return toString()
}
public var debugDescription: String {
return "\"" + toString() + "\""
}
}

extension Optional where Wrapped == JavaString {
public var description: String {
switch self {
case .some(let value): "Optional(\(value.toString())"
case .none: "nil"
}
}
}
26 changes: 23 additions & 3 deletions Sources/SwiftJava/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,20 +123,40 @@ public macro JavaStaticField(_ javaFieldName: String? = nil, isFinal: Bool = fal
/// ```
///
/// corresponds to the Java constructor `HelloSwift(String name)`.
///
/// ### Generics and type-erasure
/// Swift and Java differ in how they represent generics at runtime.
/// In Java, generics are type-erased and the JVM representation of generic types is erased to `java.lang.Object`.
/// Swift on the other hand, reifies types which means a `Test<T>` in practice will be a specific type with
/// the generic substituted `Test<String>`. This means that at runtime, calling a generic @JavaMethod needs to know
/// which of the parameters (or result type) must be subjected to type-erasure as we form the call into the Java function.
///
/// In order to mark a generic return type you must indicate it to the @JavaMethod macro like this:
/// ```swift
/// // Java: class Test<T> { public <T> get(); }
/// @JavaMethod(typeErasedResult: "T!")
/// func get() -> T!
/// ```
/// This allows the macro to form a call into the get() method, which at runtime, will have an `java.lang.Object`
/// returning method signature, and then, convert the result to the expected `T` type on the Swift side.
@attached(body)
public macro JavaMethod() = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro")
public macro JavaMethod(
typeErasedResult: String? = nil
) = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro")

/// Attached macro that turns a Swift method on JavaClass into one that wraps
/// a Java static method on the underlying Java class object.
///
/// The macro must be used within a specific JavaClass instance.
///
/// ```swift
/// @JavaMethod
/// @JavaStaticMethod
/// func sayHelloBack(_ i: Int32) -> Double
/// ```
@attached(body)
public macro JavaStaticMethod() = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro")
public macro JavaStaticMethod(
typeErasedResult: String? = nil
) = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro")

/// Macro that marks extensions to specify that all of the @JavaMethod
/// methods are implementations of Java methods spelled as `native`.
Expand Down
14 changes: 14 additions & 0 deletions Sources/SwiftJava/Optional+JavaOptional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,17 @@ public extension Optional where Wrapped == Int64 {
}
}
}

extension JavaOptional {
public func empty(environment: JNIEnvironment? = nil) -> JavaOptional<T>! {
guard let env = try? environment ?? JavaVirtualMachine.shared().environment() else {
return nil
}

guard let opt = try? JavaClass<JavaOptional<T>>(environment: env).empty() else {
return nil
}

return opt.as(JavaOptional<T>.self)
}
}
4 changes: 0 additions & 4 deletions Sources/SwiftJava/String+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@
//===----------------------------------------------------------------------===//

import Foundation
// import SwiftJavaToolLib
// import SwiftJava
// import JavaUtilJar
// import SwiftJavaConfigurationShared

extension String {
/// For a String that's of the form java.util.Vector, return the "Vector"
Expand Down
6 changes: 3 additions & 3 deletions Sources/SwiftJava/generated/JavaLong.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,6 @@ extension JavaClass<JavaLong> {
@JavaStaticMethod
public func decode(_ arg0: String) throws -> JavaLong!

@JavaStaticMethod
public func highestOneBit(_ arg0: Int64) -> Int64

@JavaStaticMethod
public func sum(_ arg0: Int64, _ arg1: Int64) -> Int64

Expand All @@ -158,6 +155,9 @@ extension JavaClass<JavaLong> {
@JavaStaticMethod
public func toBinaryString(_ arg0: Int64) -> String

@JavaStaticMethod
public func highestOneBit(_ arg0: Int64) -> Int64

@JavaStaticMethod
public func lowestOneBit(_ arg0: Int64) -> Int64

Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftJava/generated/JavaOptional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import CSwiftJavaJNI

@JavaClass("java.util.Optional")
open class JavaOptional<T: AnyJavaObject>: JavaObject {
@JavaMethod
open func get() -> JavaObject! // FIXME: Currently we do generate -> T https://github.com/swiftlang/swift-java/issues/439
@JavaMethod(typeErasedResult: "T")
open func get() -> T!

@JavaMethod
open override func equals(_ arg0: JavaObject?) -> Bool
Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftJava/generated/JavaOptionalDouble.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ open class JavaOptionalDouble: JavaObject {
open func isEmpty() -> Bool

@JavaMethod
open func isPresent() -> Bool
open func orElse(_ arg0: Double) -> Double

@JavaMethod
open func orElse(_ arg0: Double) -> Double
open func isPresent() -> Bool

@JavaMethod
open func orElseThrow() -> Double
Expand Down
Loading