@@ -80,7 +80,7 @@ public final class JavaVirtualMachine: @unchecked Sendable {
8080 classpath: [ String ] = [ ] ,
8181 vmOptions: [ String ] = [ ] ,
8282 ignoreUnrecognized: Bool = false
83- ) throws {
83+ ) throws ( VMError ) {
8484 self . classpath = classpath
8585 var jvm : JavaVMPointer ? = nil
8686 var environment : JNIEnvPointer ? = nil
@@ -129,7 +129,7 @@ public final class JavaVirtualMachine: @unchecked Sendable {
129129
130130 typealias CreateJavaVM = @convention ( c) ( _ pvm: UnsafeMutablePointer < JavaVMPointer ? > ? , _ penv: UnsafeMutablePointer < JNIEnvPointer ? > ? , _ args: UnsafeMutableRawPointer ) -> jint
131131 guard let createJavaVM: CreateJavaVM = symbol ( try loadLibJava ( ) , " JNI_CreateJavaVM " ) else {
132- throw VMError . cannotLoadCreateJavaVM
132+ throw VMError ( . cannotLoadCreateJavaVM)
133133 }
134134
135135 // Create the JVM instance.
@@ -141,7 +141,7 @@ public final class JavaVirtualMachine: @unchecked Sendable {
141141 self . destroyOnDeinit = . init( initialState: true )
142142 }
143143
144- public func destroyJVM( ) throws {
144+ public func destroyJVM( ) throws ( VMError ) {
145145 try self . detachCurrentThread ( )
146146 if let error = VMError ( fromJNIError: jvm. pointee!. pointee. DestroyJavaVM ( jvm) ) {
147147 throw error
@@ -177,7 +177,7 @@ extension JavaVirtualMachine {
177177 /// - Parameter
178178 /// - asDaemon: Whether this thread should be treated as a daemon
179179 /// thread in the Java Virtual Machine.
180- public func environment( asDaemon: Bool = false ) throws -> JNIEnvironment {
180+ public func environment( asDaemon: Bool = false ) throws ( VMError ) -> JNIEnvironment {
181181 // Check whether this thread is already attached. If so, return the
182182 // corresponding environment.
183183 var environment : UnsafeMutableRawPointer ? = nil
@@ -213,7 +213,7 @@ extension JavaVirtualMachine {
213213
214214 /// Detach the current thread from the Java Virtual Machine. All Java
215215 /// threads waiting for this thread to die are notified.
216- func detachCurrentThread( ) throws {
216+ func detachCurrentThread( ) throws ( VMError ) {
217217 if let resultError = VMError ( fromJNIError: jvm. pointee!. pointee. DetachCurrentThread ( jvm) ) {
218218 throw resultError
219219 }
@@ -260,13 +260,13 @@ extension JavaVirtualMachine {
260260 vmOptions: [ String ] = [ ] ,
261261 ignoreUnrecognized: Bool = false ,
262262 replace: Bool = false
263- ) throws -> JavaVirtualMachine {
263+ ) throws ( VMError ) -> JavaVirtualMachine {
264264 precondition (
265265 !classpath. contains ( where: { $0. contains ( FileManager . pathSeparator) } ) ,
266266 " Classpath element must not contain ` \( FileManager . pathSeparator) `! Split the path into elements! Was: \( classpath) "
267267 )
268268
269- return try sharedJVM. withLock { ( sharedJVMPointer: inout JavaVirtualMachine ? ) in
269+ return try sharedJVM. withLock { ( sharedJVMPointer: inout JavaVirtualMachine ? ) throws ( VMError ) in
270270 // If we already have a JavaVirtualMachine instance, return it.
271271 if replace {
272272 print ( " [swift-java] Replace JVM instance! " )
@@ -281,7 +281,7 @@ extension JavaVirtualMachine {
281281
282282 typealias GetCreatedJavaVMs = @convention ( c) ( _ pvm: UnsafeMutablePointer < JavaVMPointer ? > , _ count: Int32 , _ num: UnsafeMutablePointer < Int32 > ) -> jint
283283 guard let getCreatedJavaVMs: GetCreatedJavaVMs = symbol ( try loadLibJava ( ) , " JNI_GetCreatedJavaVMs " ) else {
284- throw VMError . cannotLoadGetCreatedJavaVMs
284+ throw VMError ( . cannotLoadGetCreatedJavaVMs)
285285 }
286286
287287 while true {
@@ -312,12 +312,16 @@ extension JavaVirtualMachine {
312312 vmOptions: vmOptions,
313313 ignoreUnrecognized: ignoreUnrecognized
314314 )
315- } catch VMError . existingVM {
315+ } catch . existingVM {
316316 // We raced with code outside of this JavaVirtualMachine instance
317317 // that created a VM while we were trying to do the same. Go
318318 // through the loop again to pick up the underlying JVM pointer.
319319 wasExistingVM = true
320320 continue
321+ } catch let error as VMError {
322+ throw error
323+ } catch {
324+ fatalError ( " Unexpected non-VMError from JavaVirtualMachine.init: \( error) " )
321325 }
322326
323327 sharedJVMPointer = javaVirtualMachine
@@ -346,57 +350,77 @@ extension JavaVirtualMachine {
346350}
347351
348352extension JavaVirtualMachine {
349- /// Describes the kinds of errors that can occur when interacting with JNI.
350- enum VMError : Error {
351- /// There is already a Java Virtual Machine .
352- case existingVM
353+ /// Describes an error that occurred when interacting with JNI.
354+ public struct VMError : Error {
355+ /// The specific kind of error that occurred .
356+ public let code : Code
353357
354- /// JNI version mismatch error.
355- case jniVersion
358+ /// The source file where the error was created .
359+ public let file : String
356360
357- /// Thread is detached from the VM .
358- case threadDetached
361+ /// The source line where the error was created .
362+ public let line : UInt
359363
360- /// Out of memory.
361- case outOfMemory
364+ public init ( _ code: Code , file: String = #fileID, line: UInt = #line) {
365+ self . code = code
366+ self . file = file
367+ self . line = line
368+ }
362369
363- /// Invalid arguments.
364- case invalidArguments
370+ init ? ( fromJNIError error: jint , file: String = #fileID, line: UInt = #line) {
371+ guard error != JNI_OK else { return nil }
372+ self . code = Code ( rawValue: error)
373+ self . file = file
374+ self . line = line
375+ }
365376
366- /// Cannot locate a `JAVA_HOME`
367- case javaHomeNotFound
377+ /// The kinds of errors that can occur when interacting with JNI.
378+ public struct Code : RawRepresentable , Equatable , Hashable , Sendable {
379+ public let rawValue : Int32
368380
369- /// Cannot find `libjvm`
370- case libjvmNotFound
381+ public init ( rawValue: Int32 ) {
382+ self . rawValue = rawValue
383+ }
371384
372- /// Cannot `dlopen` `libjvm`
373- case libjvmNotLoaded
385+ /// Thread is detached from the VM. (JNI_EDETACHED)
386+ public static var threadDetached : Code { Code ( rawValue : JNI_EDETACHED ) }
374387
375- /// Cannot load `JNI_GetCreatedJavaVMs` from `libjvm`
376- case cannotLoadGetCreatedJavaVMs
388+ /// JNI version mismatch error. (JNI_EVERSION)
389+ public static var jniVersion : Code { Code ( rawValue : JNI_EVERSION ) }
377390
378- /// Cannot load `JNI_CreateJavaVM` from `libjvm`
379- case cannotLoadCreateJavaVM
391+ /// Out of memory. (JNI_ENOMEM)
392+ public static var outOfMemory : Code { Code ( rawValue : JNI_ENOMEM ) }
380393
381- /// Unknown JNI error.
382- case unknown ( jint , file : String , line : UInt )
394+ /// There is already a Java Virtual Machine. (JNI_EEXIST)
395+ public static var existingVM : Code { Code ( rawValue : JNI_EEXIST ) }
383396
384- init ? ( fromJNIError error: jint , file: String = #fileID, line: UInt = #line) {
385- switch error {
386- case JNI_OK: return nil
387- case JNI_EDETACHED: self = . threadDetached
388- case JNI_EVERSION: self = . jniVersion
389- case JNI_ENOMEM: self = . outOfMemory
390- case JNI_EEXIST: self = . existingVM
391- case JNI_EINVAL: self = . invalidArguments
392- default : self = . unknown( error, file: file, line: line)
393- }
397+ /// Invalid arguments. (JNI_EINVAL)
398+ public static var invalidArguments : Code { Code ( rawValue: JNI_EINVAL) }
399+
400+ /// Cannot locate a `JAVA_HOME`.
401+ public static var javaHomeNotFound : Code { Code ( rawValue: - 100 ) }
402+
403+ /// Cannot find `libjvm`.
404+ public static var libjvmNotFound : Code { Code ( rawValue: - 101 ) }
405+
406+ /// Cannot `dlopen` `libjvm`.
407+ public static var libjvmNotLoaded : Code { Code ( rawValue: - 102 ) }
408+
409+ /// Cannot load `JNI_GetCreatedJavaVMs` from `libjvm`.
410+ public static var cannotLoadGetCreatedJavaVMs : Code { Code ( rawValue: - 103 ) }
411+
412+ /// Cannot load `JNI_CreateJavaVM` from `libjvm`.
413+ public static var cannotLoadCreateJavaVM : Code { Code ( rawValue: - 104 ) }
394414 }
395415 }
416+ }
396417
397- enum JavaKitError : Error {
398- case classpathEntryNotFound( entry: String , classpath: [ String ] )
418+ /// Pattern matching operator to enable switching on ``JavaVirtualMachine.VMError`` codes.
419+ public func ~= ( code: JavaVirtualMachine . VMError . Code , error: any Error ) -> Bool {
420+ guard let error = error as? JavaVirtualMachine . VMError else {
421+ return false
399422 }
423+ return error. code == code
400424}
401425
402426// ==== ------------------------------------------------------------------------
@@ -459,7 +483,7 @@ func systemJavaHome() -> String? {
459483}
460484
461485/// Located the shared library that includes the `JNI_GetCreatedJavaVMs` and `JNI_CreateJavaVM` entry points to the `JNINativeInterface` function table
462- private func loadLibJava( ) throws -> DylibType {
486+ private func loadLibJava( ) throws ( JavaVirtualMachine . VMError ) -> DylibType {
463487 #if os(Android)
464488 for libname in [ " libart.so " , " libdvm.so " , " libnativehelper.so " ] {
465489 if let lib = dlopen ( libname, RTLD_NOW) {
@@ -469,7 +493,7 @@ private func loadLibJava() throws -> DylibType {
469493 #endif
470494
471495 guard let javaHome = systemJavaHome ( ) else {
472- throw JavaVirtualMachine . VMError. javaHomeNotFound
496+ throw JavaVirtualMachine . VMError ( . javaHomeNotFound)
473497 }
474498
475499 let javaHomeURL = URL ( fileURLWithPath: javaHome, isDirectory: true )
@@ -502,7 +526,7 @@ private func loadLibJava() throws -> DylibType {
502526 FileManager . default. isReadableFile ( atPath: $0. path)
503527 } )
504528 else {
505- throw JavaVirtualMachine . VMError. libjvmNotFound
529+ throw JavaVirtualMachine . VMError ( . libjvmNotFound)
506530 }
507531
508532 #if os(Windows)
@@ -512,7 +536,7 @@ private func loadLibJava() throws -> DylibType {
512536 #endif
513537
514538 guard let dylib else {
515- throw JavaVirtualMachine . VMError. libjvmNotLoaded
539+ throw JavaVirtualMachine . VMError ( . libjvmNotLoaded)
516540 }
517541
518542 return dylib
0 commit comments