Skip to content

Commit 17f1e2e

Browse files
committed
Add nullability to FirebirdBlob and FBBlob* classes
1 parent bbf0a98 commit 17f1e2e

5 files changed

Lines changed: 188 additions & 102 deletions

File tree

src/main/org/firebirdsql/jaybird/props/PropertyConstants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public final class PropertyConstants {
3636
static final boolean DEFAULT_IGNORE_PROCEDURE_TYPE = false;
3737
static final boolean DEFAULT_WIRE_COMPRESSION = false;
3838
public static final int DEFAULT_BLOB_BUFFER_SIZE = 16384;
39-
static final int MIN_BLOB_BUFFER_SIZE = 512;
39+
public static final int MIN_BLOB_BUFFER_SIZE = 512;
4040
public static final int DEFAULT_PAGE_CACHE_SIZE = 0;
4141
static final boolean DEFAULT_USE_SERVER_BATCH = true;
4242
public static final int DEFAULT_SERVER_BATCH_BUFFER_SIZE = 0;

src/main/org/firebirdsql/jdbc/FBBlob.java

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,12 @@
3030
import org.firebirdsql.gds.ng.listeners.TransactionListener;
3131
import org.firebirdsql.gds.ng.wire.InlineBlob;
3232
import org.firebirdsql.jaybird.props.DatabaseConnectionProperties;
33+
import org.firebirdsql.jaybird.props.PropertyConstants;
3334
import org.firebirdsql.jaybird.util.SQLExceptionChainBuilder;
35+
import org.firebirdsql.jdbc.FBObjectListener.BlobListener;
3436
import org.firebirdsql.util.InternalApi;
3537
import org.jspecify.annotations.NullMarked;
38+
import org.jspecify.annotations.Nullable;
3639

3740
import java.io.*;
3841
import java.sql.Blob;
@@ -41,7 +44,6 @@
4144
import java.util.ArrayList;
4245
import java.util.Arrays;
4346
import java.util.Collection;
44-
import java.util.Collections;
4547
import java.util.HashSet;
4648

4749
import static java.util.Objects.requireNonNull;
@@ -62,22 +64,23 @@
6264
* </p>
6365
*/
6466
@InternalApi
67+
@NullMarked
6568
public final class FBBlob implements FirebirdBlob, TransactionListener {
6669

6770
private static final System.Logger logger = System.getLogger(FBBlob.class.getName());
6871

6972
private boolean isNew;
7073
private long blobId;
7174
@SuppressWarnings("java:S3077")
72-
private volatile GDSHelper gdsHelper;
73-
private FBObjectListener.BlobListener blobListener;
75+
private volatile @Nullable GDSHelper gdsHelper;
76+
private BlobListener blobListener;
7477
private final Config config;
7578

76-
private final Collection<FBBlobInputStream> inputStreams = Collections.synchronizedSet(new HashSet<>());
77-
private FBBlobOutputStream blobOut;
78-
private InlineBlob cachedInlineBlob;
79+
private final Collection<FBBlobInputStream> inputStreams = new HashSet<>();
80+
private @Nullable FBBlobOutputStream blobOut;
81+
private @Nullable InlineBlob cachedInlineBlob;
7982

80-
private FBBlob(GDSHelper c, boolean isNew, FBObjectListener.BlobListener blobListener, Config config) {
83+
private FBBlob(GDSHelper c, boolean isNew, @Nullable BlobListener blobListener, Config config) {
8184
gdsHelper = c;
8285
this.isNew = isNew;
8386
this.blobListener = blobListener != null ? blobListener : FBObjectListener.NoActionBlobListener.instance();
@@ -95,7 +98,7 @@ private FBBlob(GDSHelper c, boolean isNew, FBObjectListener.BlobListener blobLis
9598
* blob configuration (cannot be {@code null})
9699
* @since 5
97100
*/
98-
public FBBlob(GDSHelper c, FBObjectListener.BlobListener blobListener, Config config) {
101+
public FBBlob(GDSHelper c, @Nullable BlobListener blobListener, Config config) {
99102
this(c, true, blobListener, config);
100103
}
101104

@@ -112,7 +115,7 @@ public FBBlob(GDSHelper c, FBObjectListener.BlobListener blobListener, Config co
112115
* blob configuration (cannot be {@code null})
113116
* @since 5
114117
*/
115-
public FBBlob(GDSHelper c, long blobId, FBObjectListener.BlobListener blobListener, Config config) {
118+
public FBBlob(GDSHelper c, long blobId, @Nullable BlobListener blobListener, Config config) {
116119
this(c, false, blobListener, config);
117120
this.blobId = blobId;
118121
}
@@ -135,7 +138,7 @@ Config config() {
135138
*/
136139
FbBlob openBlob() throws SQLException {
137140
try (var ignored = withLock()) {
138-
checkClosed();
141+
GDSHelper gdsHelper = requireOpenGDSHelper();
139142
if (isNew) {
140143
throw new SQLException("No Blob ID is available in new Blob object",
141144
SQLStateConstants.SQL_STATE_GENERAL_ERROR);
@@ -164,8 +167,8 @@ FbBlob openBlob() throws SQLException {
164167
* @since 5
165168
*/
166169
FbBlob createBlob() throws SQLException {
167-
try (LockCloseable ignored = withLock()) {
168-
checkClosed();
170+
try (var ignored = withLock()) {
171+
GDSHelper gdsHelper = requireOpenGDSHelper();
169172
// For historic reasons we allow creating an output blob even if this is not a new blob. This may need to
170173
// be reconsidered in the future
171174
return gdsHelper.createBlob(config);
@@ -231,8 +234,8 @@ public InputStream getBinaryStream(long pos, long length) throws SQLException {
231234
* if something went wrong
232235
*/
233236
public byte[] getInfo(byte[] items, int bufferLength) throws SQLException {
234-
try (LockCloseable ignored = withLock()) {
235-
checkClosed();
237+
try (var ignored = withLock()) {
238+
GDSHelper gdsHelper = requireOpenGDSHelper();
236239
blobListener.executionStarted(this);
237240
// TODO Does it make sense to close blob here?
238241
try (FbBlob blob = gdsHelper.openBlob(blobId, config)) {
@@ -246,7 +249,7 @@ public byte[] getInfo(byte[] items, int bufferLength) throws SQLException {
246249
@Override
247250
public long length() throws SQLException {
248251
try (var ignored = withLock()) {
249-
checkClosed();
252+
checkOpen();
250253
blobListener.executionStarted(this);
251254
try (FbBlob blob = openBlob()) {
252255
return blob.length();
@@ -269,7 +272,7 @@ public boolean isSegmented() throws SQLException {
269272
@Override
270273
public FBBlob detach() throws SQLException {
271274
try (var ignored = withLock()) {
272-
checkClosed();
275+
GDSHelper gdsHelper = requireOpenGDSHelper();
273276
var blobCopy = new FBBlob(gdsHelper, blobId, blobListener, config);
274277
if (cachedInlineBlob != null) {
275278
blobCopy.cachedInlineBlob = cachedInlineBlob.copy();
@@ -343,8 +346,8 @@ public byte[] getBytes() throws SQLException {
343346

344347
@Override
345348
public InputStream getBinaryStream() throws SQLException {
346-
try (LockCloseable ignored = withLock()) {
347-
checkClosed();
349+
try (var ignored = withLock()) {
350+
checkOpen();
348351
FBBlobInputStream blobstream = new FBBlobInputStream(this);
349352
inputStreams.add(blobstream);
350353
return blobstream;
@@ -384,8 +387,8 @@ public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLExcep
384387

385388
@Override
386389
public OutputStream setBinaryStream(long pos) throws SQLException {
387-
try (LockCloseable ignored = withLock()) {
388-
checkClosed();
390+
try (var ignored = withLock()) {
391+
checkOpen();
389392
blobListener.executionStarted(this);
390393

391394
if (blobOut != null) {
@@ -430,6 +433,7 @@ public long getBlobId() throws SQLException {
430433
void setBlobId(long blobId) {
431434
try (LockCloseable ignored = withLock()) {
432435
this.blobId = blobId;
436+
GDSHelper gdsHelper = this.gdsHelper;
433437
if (isNew && gdsHelper != null) {
434438
FbTransaction currentTransaction = gdsHelper.getCurrentTransaction();
435439
if (currentTransaction != null) {
@@ -456,7 +460,7 @@ public void copyBytes(byte[] bytes, int pos, int len) throws SQLException {
456460
* @return The buffer length
457461
*/
458462
int getBufferLength() {
459-
return config.blobBufferSize();
463+
return Math.max(PropertyConstants.MIN_BLOB_BUFFER_SIZE, config.blobBufferSize());
460464
}
461465

462466
/**
@@ -466,7 +470,9 @@ int getBufferLength() {
466470
* InputStream that has been closed.
467471
*/
468472
void notifyClosed(FBBlobInputStream stream) {
469-
inputStreams.remove(stream);
473+
try (var ignored = withLock()) {
474+
inputStreams.remove(stream);
475+
}
470476
}
471477

472478
/**
@@ -647,18 +653,36 @@ public void copyCharacterStream(Reader reader) throws SQLException {
647653
public void transactionStateChanged(FbTransaction transaction, TransactionState newState,
648654
TransactionState previousState) {
649655
if (newState == TransactionState.COMMITTED || newState == TransactionState.ROLLED_BACK) {
650-
try (LockCloseable ignored = withLock()) {
656+
try {
651657
free();
652658
} catch (SQLException e) {
653659
logger.log(System.Logger.Level.ERROR, "Error calling free on blob during transaction end", e);
654660
}
655661
}
656662
}
657663

658-
private void checkClosed() throws SQLException {
664+
/**
665+
* Checks if this blob is open.
666+
* <p>
667+
* If the method also requires use of the {@code GDSHelper} object, use {@link #requireOpenGDSHelper()} instead.
668+
* </p>
669+
*
670+
* @throws SQLException
671+
* if this blob is currently not open (does not have a {@code GDSHelper}).
672+
* @see #requireOpenGDSHelper()
673+
*/
674+
private void checkOpen() throws SQLException {
675+
if (gdsHelper == null) {
676+
throw FbExceptionBuilder.toException(JaybirdErrorCodes.jb_blobClosed);
677+
}
678+
}
679+
680+
private GDSHelper requireOpenGDSHelper() throws SQLException {
681+
GDSHelper gdsHelper = this.gdsHelper;
659682
if (gdsHelper == null) {
660683
throw FbExceptionBuilder.toException(JaybirdErrorCodes.jb_blobClosed);
661684
}
685+
return gdsHelper;
662686
}
663687

664688
/**

0 commit comments

Comments
 (0)