11/*
2- * Copyright (c) 2021, 2022 Oracle and/or its affiliates.
2+ * Copyright (c) 2021, 2023 Oracle and/or its affiliates.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
@@ -94,28 +94,42 @@ public class ConditionallyCloseableConnection extends DelegatingConnection {
9494 private SQLBooleanSupplier isClosedFunction ;
9595
9696 /**
97- * Whether or not the {@link #close()} method will actually close this {@link DelegatingConnection }.
97+ * The internal state of this {@link ConditionallyCloseableConnection }.
9898 *
99- * <p>This field is set based on the value of the {@code strictClosedChecking} argument supplied to the {@link
100- * #ConditionallyCloseableConnection(Connection, boolean, boolean)} constructor. It may end up deliberately doing
101- * nothing.</p>
99+ * <p>This field is never {@code null}.</p>
100+ *
101+ * <!--
102+ * digraph ConditionallyCloseableConnection {
103+ *
104+ * CLOSEABLE -> CLOSED [label="close()"];
105+ * CLOSEABLE -> NOT_CLOSEABLE [label="setCloseable(false)"];
106+ * CLOSEABLE -> CLOSEABLE;
107+ *
108+ * NOT_CLOSEABLE -> CLOSE_PENDING [label="close()"];
109+ * NOT_CLOSEABLE -> NOT_CLOSEABLE [label="setCloseable(false), isCloseable(), isClosed()"];
110+ * NOT_CLOSEABLE -> CLOSEABLE [label="setCloseable(true)"];
111+ *
112+ * CLOSE_PENDING -> CLOSE_PENDING [label="close(), setCloseable(false), isCloseable(), isClosed()"];
113+ * CLOSE_PENDING -> CLOSEABLE [label="setCloseable(true)"];
114+ *
115+ * CLOSED -> CLOSED [label="close(), isCloseable(), isClosed()"];
116+ *
117+ * }
118+ * -->
119+ *
120+ * @see #isClosed()
102121 *
103122 * @see #isCloseable()
104123 *
105124 * @see #setCloseable(boolean)
106125 *
107- * @see #ConditionallyCloseableConnection(Connection, boolean, boolean)
108- */
109- private volatile boolean closeable ;
110-
111- /**
112- * Whether or not a {@link #close()} request has been issued from another thread.
126+ * @see #close()
113127 *
114128 * @see #isClosePending()
115129 *
116- * @see #isClosed( )
130+ * @see #ConditionallyCloseableConnection(Connection, boolean, boolean )
117131 */
118- private volatile boolean closePending ;
132+ private volatile State state ;
119133
120134
121135 /*
@@ -205,12 +219,12 @@ public ConditionallyCloseableConnection(Connection delegate,
205219 super (delegate );
206220 if (strictClosedChecking ) {
207221 this .closedChecker = this ::failWhenClosed ;
208- this .isClosedFunction = () -> this . isClosePending () || super . isClosed () ;
222+ this .isClosedFunction = this :: strictIsClosed ;
209223 } else {
210224 this .closedChecker = ConditionallyCloseableConnection ::doNothing ;
211225 this .isClosedFunction = super ::isClosed ;
212226 }
213- this .closeable = closeable ;
227+ this .state = closeable ? State . CLOSEABLE : State . NOT_CLOSEABLE ;
214228 }
215229
216230
@@ -252,15 +266,29 @@ public ConditionallyCloseableConnection(Connection delegate,
252266 @ Override // DelegatingConnection
253267 public void close () throws SQLException {
254268 // this.checkOpen(); // Deliberately omitted per spec.
255- if (this .isCloseable ()) {
269+ switch (this .state ) {
270+ case CLOSEABLE :
256271 try {
257272 super .close ();
258273 } finally {
259- this .closePending = false ;
274+ this .state = State . CLOSED ;
260275 this .onClose ();
261276 }
262- } else {
263- this .closePending = true ;
277+ break ;
278+ case NOT_CLOSEABLE :
279+ this .state = State .CLOSE_PENDING ;
280+ break ;
281+ case CLOSE_PENDING :
282+ break ;
283+ case CLOSED :
284+ try {
285+ super .close ();
286+ } finally {
287+ this .onClose ();
288+ }
289+ break ;
290+ default :
291+ throw new AssertionError ();
264292 }
265293 }
266294
@@ -329,7 +357,21 @@ protected void onClose() throws SQLException {
329357 */
330358 public boolean isCloseable () throws SQLException {
331359 // this.checkOpen(); // Deliberately omitted.
332- return this .closeable && !this .isClosed ();
360+ switch (this .state ) {
361+ case CLOSEABLE :
362+ return !this .isClosed (); // reduces to !super.isClosed() which reduces to !this.delegate().isClosed()
363+ case NOT_CLOSEABLE :
364+ assert !this .isClosed (); // reduces to !super.isClosed() which reduces to !this.delegate().isClosed()
365+ return false ;
366+ case CLOSE_PENDING :
367+ // (Can't assert about isClosed() because its behavior depends on strictClosedChecking constructor parameter.)
368+ return false ;
369+ case CLOSED :
370+ assert this .isClosed (); // reduces to super.isClosed() which reduces to this.delegate().isClosed()
371+ return false ;
372+ default :
373+ throw new AssertionError ();
374+ }
333375 }
334376
335377 /**
@@ -364,9 +406,22 @@ public boolean isCloseable() throws SQLException {
364406 */
365407 public void setCloseable (boolean closeable ) {
366408 // this.checkOpen(); // Deliberately omitted.
367- this .closeable = closeable ;
368- if (closeable ) {
369- this .closePending = false ;
409+ switch (this .state ) {
410+ case CLOSEABLE :
411+ if (!closeable ) {
412+ this .state = State .NOT_CLOSEABLE ;
413+ }
414+ break ;
415+ case NOT_CLOSEABLE :
416+ case CLOSE_PENDING :
417+ if (closeable ) {
418+ this .state = State .CLOSEABLE ;
419+ }
420+ break ;
421+ case CLOSED :
422+ break ;
423+ default :
424+ throw new AssertionError ();
370425 }
371426 }
372427
@@ -393,7 +448,7 @@ public void setCloseable(boolean closeable) {
393448 */
394449 public boolean isClosePending () {
395450 // this.checkOpen(); // Deliberately omitted.
396- return this .closePending ;
451+ return this .state == State . CLOSE_PENDING ;
397452 }
398453
399454 @ Override // DelegatingConnection
@@ -481,6 +536,11 @@ public boolean isClosed() throws SQLException {
481536 return this .isClosedFunction .getAsBoolean ();
482537 }
483538
539+ // (Invoked by method reference only.)
540+ private boolean strictIsClosed () throws SQLException {
541+ return this .isClosePending () || super .isClosed ();
542+ }
543+
484544 @ Override // DelegatingConnection
485545 public DatabaseMetaData getMetaData () throws SQLException {
486546 this .checkOpen ();
@@ -866,4 +926,50 @@ private static void doNothing() {
866926
867927 }
868928
929+
930+ /*
931+ * Inner and nested classes.
932+ */
933+
934+
935+ /**
936+ * A state that a {@link ConditionallyCloseableConnection} can have.
937+ */
938+ private enum State {
939+
940+ /**
941+ * A {@link State} indicating that an invocation of a {@link ConditionallyCloseableConnection}'s {@link
942+ * ConditionallyCloseableConnection#close() close()} method will close {@linkplain
943+ * ConditionallyCloseableConnection#delegate() its underlying delegate}.
944+ */
945+ CLOSEABLE ,
946+
947+ /**
948+ * A {@link State} indicating that an invocation of a {@link ConditionallyCloseableConnection}'s {@link
949+ * ConditionallyCloseableConnection#close() close()} method will place it into the {@link #CLOSE_PENDING} state.
950+ *
951+ * @see ConditionallyCloseableConnection#setCloseable(boolean)
952+ */
953+ NOT_CLOSEABLE ,
954+
955+ /**
956+ * A {@link State} indicating that an invocation of a {@link ConditionallyCloseableConnection}'s {@link
957+ * ConditionallyCloseableConnection#close() close()} method has placed it into this state, and actual closing of
958+ * {@linkplain ConditionallyCloseableConnection#delegate() its underlying delegate} will need to be arranged.
959+ *
960+ * @see ConditionallyCloseableConnection#setCloseable(boolean)
961+ */
962+ CLOSE_PENDING ,
963+
964+ /**
965+ * A {@link State} indicating that an invocation of a {@link ConditionallyCloseableConnection}'s {@link
966+ * ConditionallyCloseableConnection#close() close()} method has placed it into this state, and that {@linkplain
967+ * ConditionallyCloseableConnection#delegate() its underlying delegate} has also been closed.
968+ *
969+ * <p>This is a terminal state.</p>
970+ */
971+ CLOSED ;
972+
973+ }
974+
869975}
0 commit comments