11/*
22 SPDX-FileCopyrightText: Copyright 2005 Gabriel Reid
33 SPDX-FileCopyrightText: Copyright 2006-2008 Roman Rokytskyy
4- SPDX-FileCopyrightText: Copyright 2011-2024 Mark Rotteveel
4+ SPDX-FileCopyrightText: Copyright 2011-2026 Mark Rotteveel
55 SPDX-FileCopyrightText: Copyright 2019 Vasiliy Yashkov
66 SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause
77*/
1919import org .firebirdsql .jaybird .util .SQLExceptionChainBuilder ;
2020import org .firebirdsql .jaybird .xca .FatalErrorHelper ;
2121import org .firebirdsql .jdbc .FirebirdConnection ;
22+ import org .jspecify .annotations .NullMarked ;
23+ import org .jspecify .annotations .Nullable ;
2224
2325import java .sql .Connection ;
2426import java .sql .SQLException ;
3234import static java .lang .System .Logger .Level .DEBUG ;
3335import static java .lang .System .Logger .Level .ERROR ;
3436import static java .lang .System .Logger .Level .WARNING ;
37+ import static java .util .Objects .requireNonNull ;
3538
3639/**
3740 * An {@link org.firebirdsql.event.EventManager} implementation to listen for database events.
4043 * @author Mark Rotteveel
4144 * @author Vasiliy Yashkov
4245 */
46+ @ NullMarked
4347public class FBEventManager implements EventManager {
4448
4549 private static final System .Logger log = System .getLogger (FBEventManager .class .getName ());
4650
4751 private final Lock lock = new ReentrantLock ();
4852 private final LockCloseable unlock = lock ::unlock ;
4953 private final GDSType gdsType ;
50- private FbDatabase fbDatabase ;
54+ private @ Nullable FbDatabase fbDatabase ;
5155 private final IConnectionProperties connectionProperties ;
5256 private final EventManagerBehaviour eventManagerBehaviour ;
5357 private volatile boolean connected = false ;
5458 private final Map <String , Set <EventListener >> listenerMap = new HashMap <>();
5559 private final Map <String , GdsEventHandler > handlerMap = new HashMap <>();
5660 private final BlockingQueue <DatabaseEvent > eventQueue = new LinkedBlockingQueue <>();
57- private EventDispatcher eventDispatcher ;
58- private Thread dispatchThread ;
61+ private @ Nullable EventDispatcher eventDispatcher ;
5962 private volatile long waitTimeout = 1000 ;
6063 private final DbListener dbListener = new DbListener ();
6164
@@ -78,8 +81,8 @@ public FBEventManager(GDSType gdsType) {
7881 */
7982 private FBEventManager (Connection connection ) throws SQLException {
8083 this .fbDatabase = connection .unwrap (FirebirdConnection .class ).getFbDatabase ();
81- gdsType = null ;
8284 connectionProperties = fbDatabase .getConnectionProperties ().asImmutable ();
85+ gdsType = GDSType .getType (connectionProperties .getType ());
8386 eventManagerBehaviour = new ManagedEventManagerBehaviour ();
8487 // NOTE In this implementation, we don't take into account pooled connections that might be closed while
8588 // the FbDatabase instance remains in use. This means that at the moment, it is possible that the event manager
@@ -127,9 +130,7 @@ public void connect() throws SQLException {
127130 connected = true ;
128131
129132 eventDispatcher = new EventDispatcher ();
130- dispatchThread = new Thread (eventDispatcher );
131- dispatchThread .setDaemon (true );
132- dispatchThread .start ();
133+ eventDispatcher .start ();
133134 }
134135 }
135136
@@ -182,17 +183,13 @@ public void disconnect() throws SQLException {
182183 private void terminateDispatcher () throws SQLException {
183184 EventDispatcher eventDispatcher = this .eventDispatcher ;
184185 if (eventDispatcher != null ) {
185- eventDispatcher .stop ();
186- dispatchThread .interrupt ();
187- // join the thread and wait until it dies
188186 try {
189- dispatchThread . join ();
187+ eventDispatcher . stopAndWait ();
190188 } catch (InterruptedException ex ) {
191189 Thread .currentThread ().interrupt ();
192190 throw new SQLException (ex );
193191 } finally {
194192 this .eventDispatcher = null ;
195- dispatchThread = null ;
196193 }
197194 }
198195 }
@@ -327,9 +324,8 @@ public void addEventListener(String eventName, EventListener listener) throws SQ
327324
328325 @ Override
329326 public void removeEventListener (String eventName , EventListener listener ) throws SQLException {
330- if (eventName == null || listener == null ) {
331- throw new NullPointerException ();
332- }
327+ requireNonNull (eventName , "eventName" );
328+ requireNonNull (listener , "listener" );
333329 try (LockCloseable ignored = withLock ()) {
334330 Set <EventListener > listenerSet = listenerMap .get (eventName );
335331 if (listenerSet != null ) {
@@ -349,9 +345,7 @@ public int waitForEvent(String eventName) throws InterruptedException, SQLExcept
349345
350346 @ Override
351347 public int waitForEvent (String eventName , final int timeout ) throws InterruptedException , SQLException {
352- if (eventName == null ) {
353- throw new NullPointerException ();
354- }
348+ requireNonNull (eventName , "eventName" );
355349 if (!connected ) {
356350 throw new IllegalStateException ("Can't wait for events with disconnected EventManager" );
357351 }
@@ -420,12 +414,14 @@ public Map<ConnectionProperty, Object> connectionPropertyValues() {
420414 }
421415
422416 private void addDbListeners (FbDatabase database ) {
417+ //noinspection ConstantValue : null check for robustness
423418 if (database == null ) return ;
424419 database .addDatabaseListener (dbListener );
425420 database .addExceptionListener (dbListener );
426421 }
427422
428423 private void removeDbListeners (FbDatabase database ) {
424+ //noinspection ConstantValue : null check for robustness
429425 if (database == null ) return ;
430426 database .removeExceptionListener (dbListener );
431427 database .removeDatabaseListener (dbListener );
@@ -439,7 +435,7 @@ private void removeDbListeners(FbDatabase database) {
439435 *
440436 * @return underlying {@link FbDatabase}
441437 */
442- FbDatabase getFbDatabase () {
438+ @ Nullable FbDatabase getFbDatabase () {
443439 return fbDatabase ;
444440 }
445441
@@ -465,8 +461,11 @@ public void connectDatabase() throws SQLException {
465461
466462 @ Override
467463 public void disconnectDatabase () throws SQLException {
468- removeDbListeners (fbDatabase );
469- fbDatabase .close ();
464+ FbDatabase fbDatabase = FBEventManager .this .fbDatabase ;
465+ if (fbDatabase != null ) {
466+ removeDbListeners (fbDatabase );
467+ fbDatabase .close ();
468+ }
470469 }
471470
472471 @ Override
@@ -480,7 +479,7 @@ public void handleDetaching(FbDatabase database) {
480479
481480 @ Override
482481 public void handleErrorOccurred (Object source , SQLException exception ) {
483- FbDatabase fbDatabase = FBEventManager . this . fbDatabase ;
482+ FbDatabase fbDatabase = getFbDatabase () ;
484483 if (fbDatabase == null || !fbDatabase .isAttached ()) return ;
485484
486485 if (FatalErrorHelper .isBrokenConnection (exception )) {
@@ -575,19 +574,19 @@ final class GdsEventHandler implements org.firebirdsql.gds.EventHandler {
575574 private volatile boolean cancelled = false ;
576575
577576 GdsEventHandler (String eventName ) throws SQLException {
578- eventHandle = fbDatabase .createEventHandle (eventName , this );
577+ eventHandle = requireNonNull ( fbDatabase , "fbDatabase" ) .createEventHandle (eventName , this );
579578 }
580579
581580 void register () throws SQLException {
582581 if (cancelled ) {
583582 throw new IllegalStateException ("Trying to register a cancelled event handler" );
584583 }
585- fbDatabase .queueEvent (eventHandle );
584+ requireNonNull ( fbDatabase , "fbDatabase" ) .queueEvent (eventHandle );
586585 }
587586
588587 void unregister () throws SQLException {
589588 if (cancelled ) return ;
590- fbDatabase .cancelEvent (eventHandle );
589+ requireNonNull ( fbDatabase , "fbDatabase" ) .cancelEvent (eventHandle );
591590 cancelled = true ;
592591 }
593592
@@ -597,7 +596,7 @@ public void eventOccurred(EventHandle eventHandle) {
597596 return ;
598597 }
599598 try {
600- fbDatabase .countEvents (eventHandle );
599+ requireNonNull ( fbDatabase , "fbDatabase" ) .countEvents (eventHandle );
601600 } catch (SQLException e ) {
602601 log .log (WARNING , "Exception processing event counts; see debug level for stacktrace" );
603602 log .log (DEBUG , "Exception processing event counts" , e );
@@ -620,12 +619,59 @@ public void eventOccurred(EventHandle eventHandle) {
620619
621620 final class EventDispatcher implements Runnable {
622621
623- private volatile boolean running = true ;
622+ private @ Nullable Thread thread ;
623+ private volatile boolean running ;
624+
625+ /**
626+ * Resets the running state if the dispatcher was previously stopped, and starts a new thread for the event
627+ * dispatcher.
628+ *
629+ * @throws IllegalStateException
630+ * if this dispatcher already has a thread, and that thread is still alive
631+ * @see #stop()
632+ * @see #stopAndWait()
633+ * @since 7
634+ */
635+ void start () {
636+ synchronized (this ) {
637+ if (thread != null && thread .isAlive ()) {
638+ throw new IllegalStateException ("EventDispatcher is already running" );
639+ }
640+ var thread = this .thread = new Thread (this );
641+ thread .setDaemon (true );
642+ running = true ;
643+ thread .start ();
644+ }
645+ }
624646
647+ /**
648+ * Stops the event dispatcher.
649+ *
650+ * @see #stopAndWait()
651+ */
625652 void stop () {
626653 running = false ;
627654 }
628655
656+ /**
657+ * Stops the event dispatcher, interrupts the dispatcher thread, and waits for it to end.
658+ *
659+ * @throws InterruptedException
660+ * if the call to {@link Thread#join()} throws
661+ * @since 7
662+ */
663+ void stopAndWait () throws InterruptedException {
664+ stop ();
665+ Thread thread ;
666+ synchronized (this ) {
667+ thread = this .thread ;
668+ }
669+ if (thread != null ) {
670+ thread .interrupt ();
671+ thread .join ();
672+ }
673+ }
674+
629675 @ Override
630676 public void run () {
631677 while (running ) {
@@ -642,11 +688,9 @@ public void run() {
642688
643689 private void notify (DatabaseEvent event ) {
644690 try (LockCloseable ignored = withLock ()) {
645- Set <EventListener > listenerSet = listenerMap .get (event .getEventName ());
646- if (listenerSet != null ) {
647- for (EventListener listener : listenerSet ) {
648- listener .eventOccurred (event );
649- }
691+ Set <EventListener > listenerSet = listenerMap .getOrDefault (event .getEventName (), Set .of ());
692+ for (EventListener listener : listenerSet ) {
693+ listener .eventOccurred (event );
650694 }
651695 }
652696 }
0 commit comments