From 0a5c0db59745e8911b75188f84282d1c042bd379 Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 8 May 2026 17:03:57 +0900 Subject: [PATCH] Collection types can accept unrelated values --- .../example/swift/SwiftDictionaryMapTest.java | 3 + .../java/com/example/swift/SwiftSetTest.java | 2 + .../SwiftJava/BridgedValues/JavaBoxing.swift | 98 ++++++++++++++----- 3 files changed, 79 insertions(+), 24 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftDictionaryMapTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftDictionaryMapTest.java index 635d46f21..a065c7460 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftDictionaryMapTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftDictionaryMapTest.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.*; public class SwiftDictionaryMapTest { + @SuppressWarnings("SuspiciousMethodCalls") @Test void makeStringToLongDictionary() { try (var arena = SwiftArena.ofConfined()) { @@ -33,6 +34,8 @@ void makeStringToLongDictionary() { assertTrue(dict.containsKey("hello")); assertFalse(dict.containsKey("missing")); assertNull(dict.get("missing")); + assertNull(dict.get(99999L), "Java's Map accepts keys of different types"); + assertFalse(dict.containsKey(99999L), "Java's Map accepts keys of different types"); } } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftSetTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftSetTest.java index 847330938..573af94f4 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftSetTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftSetTest.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.*; public class SwiftSetTest { + @SuppressWarnings("SuspiciousMethodCalls") @Test void makeStringSet() { try (var arena = SwiftArena.ofConfined()) { @@ -31,6 +32,7 @@ void makeStringSet() { assertTrue(set.contains("hello")); assertTrue(set.contains("world")); assertFalse(set.contains("missing")); + assertFalse(set.contains(99999L), "Java's Set accepts keys of different types"); } } diff --git a/Sources/SwiftJava/BridgedValues/JavaBoxing.swift b/Sources/SwiftJava/BridgedValues/JavaBoxing.swift index 00efeb500..5d37a778a 100644 --- a/Sources/SwiftJava/BridgedValues/JavaBoxing.swift +++ b/Sources/SwiftJava/BridgedValues/JavaBoxing.swift @@ -24,6 +24,9 @@ public protocol JavaBoxable: JavaValue { /// Convert this Swift value to a boxed Java object. func toJavaObject(in environment: JNIEnvironment) -> jobject? + /// The Java metatype used to box values of this Swift type. + static var javaBoxClass: jclass { get } + /// Create a Swift value from a boxed Java object. static func fromJavaObject(_ obj: jobject?, in environment: JNIEnvironment) -> Self } @@ -31,11 +34,25 @@ public protocol JavaBoxable: JavaValue { // ==== ----------------------------------------------------------------------- // MARK: JavaBoxable conformances +private func findJavaClass(_ className: String) -> jclass { + let environment = try! JavaVirtualMachine.shared().environment() + let clazz: jobject + if let jniClass = environment.interface.FindClass(environment, className) { + clazz = environment.interface.NewGlobalRef(environment, jniClass)! + environment.interface.DeleteLocalRef(environment, jniClass) + } else { + fatalError("Class \(className) could not be found!") + } + return clazz +} + extension String: JavaBoxable { public func toJavaObject(in environment: JNIEnvironment) -> jobject? { self.getJNIValue(in: environment) } + public static let javaBoxClass: jclass = findJavaClass("java/lang/String") + public static func fromJavaObject(_ obj: jobject?, in environment: JNIEnvironment) -> String { String(fromJNI: obj, in: environment) } @@ -43,16 +60,18 @@ extension String: JavaBoxable { extension Int64: JavaBoxable { public func toJavaObject(in environment: JNIEnvironment) -> jobject? { - let cls = environment.interface.FindClass(environment, "java/lang/Long") + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "", "(J)V") var args = [jvalue()] args[0].j = self.getJNIValue(in: environment) return environment.interface.NewObjectA(environment, cls, methodID, &args) } + public static let javaBoxClass: jclass = findJavaClass("java/lang/Long") + public static func fromJavaObject(_ obj: jobject?, in environment: JNIEnvironment) -> Int64 { guard let obj else { return 0 } - let cls = environment.interface.GetObjectClass(environment, obj) + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "longValue", "()J") let result = environment.interface.CallLongMethodA(environment, obj, methodID, nil) return Int64(fromJNI: result, in: environment) @@ -61,16 +80,18 @@ extension Int64: JavaBoxable { extension Int32: JavaBoxable { public func toJavaObject(in environment: JNIEnvironment) -> jobject? { - let cls = environment.interface.FindClass(environment, "java/lang/Integer") + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "", "(I)V") var args = [jvalue()] args[0].i = self.getJNIValue(in: environment) return environment.interface.NewObjectA(environment, cls, methodID, &args) } + public static let javaBoxClass: jclass = findJavaClass("java/lang/Integer") + public static func fromJavaObject(_ obj: jobject?, in environment: JNIEnvironment) -> Int32 { guard let obj else { return 0 } - let cls = environment.interface.GetObjectClass(environment, obj) + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "intValue", "()I") let result = environment.interface.CallIntMethodA(environment, obj, methodID, nil) return Int32(fromJNI: result, in: environment) @@ -79,16 +100,18 @@ extension Int32: JavaBoxable { extension Double: JavaBoxable { public func toJavaObject(in environment: JNIEnvironment) -> jobject? { - let cls = environment.interface.FindClass(environment, "java/lang/Double") + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "", "(D)V") var args = [jvalue()] args[0].d = self.getJNIValue(in: environment) return environment.interface.NewObjectA(environment, cls, methodID, &args) } + public static let javaBoxClass: jclass = findJavaClass("java/lang/Double") + public static func fromJavaObject(_ obj: jobject?, in environment: JNIEnvironment) -> Double { guard let obj else { return 0.0 } - let cls = environment.interface.GetObjectClass(environment, obj) + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "doubleValue", "()D") let result = environment.interface.CallDoubleMethodA(environment, obj, methodID, nil) return Double(fromJNI: result, in: environment) @@ -97,16 +120,18 @@ extension Double: JavaBoxable { extension Float: JavaBoxable { public func toJavaObject(in environment: JNIEnvironment) -> jobject? { - let cls = environment.interface.FindClass(environment, "java/lang/Float") + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "", "(F)V") var args = [jvalue()] args[0].f = self.getJNIValue(in: environment) return environment.interface.NewObjectA(environment, cls, methodID, &args) } + public static let javaBoxClass: jclass = findJavaClass("java/lang/Float") + public static func fromJavaObject(_ obj: jobject?, in environment: JNIEnvironment) -> Float { guard let obj else { return 0.0 } - let cls = environment.interface.GetObjectClass(environment, obj) + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "floatValue", "()F") let result = environment.interface.CallFloatMethodA(environment, obj, methodID, nil) return Float(fromJNI: result, in: environment) @@ -115,16 +140,18 @@ extension Float: JavaBoxable { extension Bool: JavaBoxable { public func toJavaObject(in environment: JNIEnvironment) -> jobject? { - let cls = environment.interface.FindClass(environment, "java/lang/Boolean") + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "", "(Z)V") var args = [jvalue()] args[0].z = self.getJNIValue(in: environment) return environment.interface.NewObjectA(environment, cls, methodID, &args) } + public static let javaBoxClass: jclass = findJavaClass("java/lang/Boolean") + public static func fromJavaObject(_ obj: jobject?, in environment: JNIEnvironment) -> Bool { guard let obj else { return false } - let cls = environment.interface.GetObjectClass(environment, obj) + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "booleanValue", "()Z") let result = environment.interface.CallBooleanMethodA(environment, obj, methodID, nil) return Bool(fromJNI: result, in: environment) @@ -136,16 +163,18 @@ extension Bool: JavaBoxable { extension Int8: JavaBoxable { public func toJavaObject(in environment: JNIEnvironment) -> jobject? { - let cls = environment.interface.FindClass(environment, "java/lang/Byte") + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "", "(B)V") var args = [jvalue()] args[0].b = self.getJNIValue(in: environment) return environment.interface.NewObjectA(environment, cls, methodID, &args) } + public static let javaBoxClass: jclass = findJavaClass("java/lang/Byte") + public static func fromJavaObject(_ obj: jobject?, in environment: JNIEnvironment) -> Int8 { guard let obj else { return 0 } - let cls = environment.interface.GetObjectClass(environment, obj) + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "byteValue", "()B") let result = environment.interface.CallByteMethodA(environment, obj, methodID, nil) return Int8(fromJNI: result, in: environment) @@ -154,16 +183,18 @@ extension Int8: JavaBoxable { extension UInt8: JavaBoxable { public func toJavaObject(in environment: JNIEnvironment) -> jobject? { - let cls = environment.interface.FindClass(environment, "java/lang/Byte") + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "", "(B)V") var args = [jvalue()] args[0].b = self.getJNIValue(in: environment) return environment.interface.NewObjectA(environment, cls, methodID, &args) } + public static let javaBoxClass: jclass = findJavaClass("java/lang/Byte") + public static func fromJavaObject(_ obj: jobject?, in environment: JNIEnvironment) -> UInt8 { guard let obj else { return 0 } - let cls = environment.interface.GetObjectClass(environment, obj) + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "byteValue", "()B") let result = environment.interface.CallByteMethodA(environment, obj, methodID, nil) return UInt8(fromJNI: result, in: environment) @@ -175,16 +206,18 @@ extension UInt8: JavaBoxable { extension Int16: JavaBoxable { public func toJavaObject(in environment: JNIEnvironment) -> jobject? { - let cls = environment.interface.FindClass(environment, "java/lang/Short") + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "", "(S)V") var args = [jvalue()] args[0].s = self.getJNIValue(in: environment) return environment.interface.NewObjectA(environment, cls, methodID, &args) } + public static let javaBoxClass: jclass = findJavaClass("java/lang/Short") + public static func fromJavaObject(_ obj: jobject?, in environment: JNIEnvironment) -> Int16 { guard let obj else { return 0 } - let cls = environment.interface.GetObjectClass(environment, obj) + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "shortValue", "()S") let result = environment.interface.CallShortMethodA(environment, obj, methodID, nil) return Int16(fromJNI: result, in: environment) @@ -196,16 +229,18 @@ extension Int16: JavaBoxable { extension UInt32: JavaBoxable { public func toJavaObject(in environment: JNIEnvironment) -> jobject? { - let cls = environment.interface.FindClass(environment, "java/lang/Integer") + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "", "(I)V") var args = [jvalue()] args[0].i = self.getJNIValue(in: environment) return environment.interface.NewObjectA(environment, cls, methodID, &args) } + public static let javaBoxClass: jclass = findJavaClass("java/lang/Integer") + public static func fromJavaObject(_ obj: jobject?, in environment: JNIEnvironment) -> UInt32 { guard let obj else { return 0 } - let cls = environment.interface.GetObjectClass(environment, obj) + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "intValue", "()I") let result = environment.interface.CallIntMethodA(environment, obj, methodID, nil) return UInt32(fromJNI: result, in: environment) @@ -214,16 +249,18 @@ extension UInt32: JavaBoxable { extension UInt64: JavaBoxable { public func toJavaObject(in environment: JNIEnvironment) -> jobject? { - let cls = environment.interface.FindClass(environment, "java/lang/Long") + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "", "(J)V") var args = [jvalue()] args[0].j = self.getJNIValue(in: environment) return environment.interface.NewObjectA(environment, cls, methodID, &args) } + public static let javaBoxClass: jclass = findJavaClass("java/lang/Long") + public static func fromJavaObject(_ obj: jobject?, in environment: JNIEnvironment) -> UInt64 { guard let obj else { return 0 } - let cls = environment.interface.GetObjectClass(environment, obj) + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "longValue", "()J") let result = environment.interface.CallLongMethodA(environment, obj, methodID, nil) return UInt64(fromJNI: result, in: environment) @@ -235,16 +272,18 @@ extension UInt64: JavaBoxable { extension Int: JavaBoxable { public func toJavaObject(in environment: JNIEnvironment) -> jobject? { - let cls = environment.interface.FindClass(environment, "java/lang/Long") + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "", "(J)V") var args = [jvalue()] args[0].j = Int64(self).getJNIValue(in: environment) return environment.interface.NewObjectA(environment, cls, methodID, &args) } + public static let javaBoxClass: jclass = findJavaClass("java/lang/Long") + public static func fromJavaObject(_ obj: jobject?, in environment: JNIEnvironment) -> Int { guard let obj else { return 0 } - let cls = environment.interface.GetObjectClass(environment, obj) + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "longValue", "()J") let result = environment.interface.CallLongMethodA(environment, obj, methodID, nil) return Int(Int64(fromJNI: result, in: environment)) @@ -253,16 +292,18 @@ extension Int: JavaBoxable { extension UInt: JavaBoxable { public func toJavaObject(in environment: JNIEnvironment) -> jobject? { - let cls = environment.interface.FindClass(environment, "java/lang/Long") + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "", "(J)V") var args = [jvalue()] args[0].j = Int64(bitPattern: UInt64(self)).getJNIValue(in: environment) return environment.interface.NewObjectA(environment, cls, methodID, &args) } + public static let javaBoxClass: jclass = findJavaClass("java/lang/Long") + public static func fromJavaObject(_ obj: jobject?, in environment: JNIEnvironment) -> UInt { guard let obj else { return 0 } - let cls = environment.interface.GetObjectClass(environment, obj) + let cls = Self.javaBoxClass let methodID = environment.interface.GetMethodID(environment, cls, "longValue", "()J") let result = environment.interface.CallLongMethodA(environment, obj, methodID, nil) return UInt(UInt64(fromJNI: result, in: environment)) @@ -304,12 +345,18 @@ final class SwiftDictionaryBox: AnySw } override func get(key: jobject?, environment: JNIEnvironment) -> jobject? { + guard environment.interface.IsInstanceOf(environment, key, K.javaBoxClass) == JNI_TRUE else { + return nil + } let swiftKey = K.fromJavaObject(key, in: environment) guard let value = dictionary[swiftKey] else { return nil } return value.toJavaObject(in: environment) } override func containsKey(key: jobject?, environment: JNIEnvironment) -> Bool { + guard environment.interface.IsInstanceOf(environment, key, K.javaBoxClass) == JNI_TRUE else { + return false + } let swiftKey = K.fromJavaObject(key, in: environment) return dictionary[swiftKey] != nil } @@ -369,6 +416,9 @@ final class SwiftSetBox: AnySwiftSetBox { } override func contains(element: jobject?, environment: JNIEnvironment) -> Bool { + guard environment.interface.IsInstanceOf(environment, element, E.javaBoxClass) == JNI_TRUE else { + return false + } let swiftElement = E.fromJavaObject(element, in: environment) return set.contains(swiftElement) }