Skip to content

Commit

Permalink
Mark partial nullability of FBField and subclasses
Browse files Browse the repository at this point in the history
We don't document full nullability of these classes for now given the volume of things that are actually nullable
  • Loading branch information
mrotteveel committed Sep 25, 2024
1 parent b24e3db commit 6e56ab7
Show file tree
Hide file tree
Showing 29 changed files with 208 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import org.firebirdsql.gds.ng.fields.FieldDescriptor;
import org.firebirdsql.gds.ng.tz.TimeZoneDatatypeCoder;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;

import java.sql.SQLException;
import java.sql.Time;
Expand All @@ -34,11 +36,13 @@
* @author Mark Rotteveel
* @since 4.0
*/
@SuppressWarnings("RedundantThrows")
abstract class AbstractWithTimeZoneField extends FBField {

private ZoneId defaultZoneId;
private final TimeZoneDatatypeCoder.TimeZoneCodec timeZoneCodec;
private final TimeZoneDatatypeCoder.@NonNull TimeZoneCodec timeZoneCodec;

@NullMarked
AbstractWithTimeZoneField(FieldDescriptor fieldDescriptor, FieldDataProvider dataProvider, int requiredType)
throws SQLException {
super(fieldDescriptor, dataProvider, requiredType);
Expand Down Expand Up @@ -129,18 +133,18 @@ public void setTimestamp(Timestamp value, Calendar cal) throws SQLException {
setTimestamp(value);
}

final TimeZoneDatatypeCoder.TimeZoneCodec getTimeZoneCodec() {
final TimeZoneDatatypeCoder.@NonNull TimeZoneCodec getTimeZoneCodec() {
return timeZoneCodec;
}

final ZoneId getDefaultZoneId() {
final @NonNull ZoneId getDefaultZoneId() {
if (defaultZoneId != null) {
return defaultZoneId;
}
return defaultZoneId = ZoneId.systemDefault();
}

private void setStringParse(String value) throws SQLException {
private void setStringParse(@NonNull String value) throws SQLException {
// TODO Better way to do this?
// TODO More lenient parsing?
if (value.indexOf('T') != -1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
package org.firebirdsql.jdbc.field;

import org.firebirdsql.gds.ng.fields.FieldDescriptor;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;

import java.sql.Date;
import java.sql.SQLException;
Expand All @@ -36,6 +38,7 @@ abstract class AbstractWithoutTimeZoneField extends FBField {

private Calendar calendar;

@NullMarked
AbstractWithoutTimeZoneField(FieldDescriptor fieldDescriptor, FieldDataProvider dataProvider, int requiredType)
throws SQLException {
super(fieldDescriptor, dataProvider, requiredType);
Expand Down Expand Up @@ -71,11 +74,11 @@ public final void setDate(Date value) throws SQLException {
setDate(value, getCalendar());
}

final Calendar getCalendar() {
final @NonNull Calendar getCalendar() {
return calendar != null ? calendar : initCalendar();
}

private Calendar initCalendar() {
private @NonNull Calendar initCalendar() {
TimeZone sessionTimeZone = gdsHelper != null ? gdsHelper.getSessionTimeZone() : null;
return calendar = sessionTimeZone != null ? Calendar.getInstance(sessionTimeZone) : Calendar.getInstance();
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/org/firebirdsql/jdbc/field/BlobListenableField.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import org.firebirdsql.jdbc.FBObjectListener;
import org.firebirdsql.util.InternalApi;
import org.jspecify.annotations.NullMarked;

/**
* Field which expects a blob listener.
Expand All @@ -28,6 +29,7 @@
* @since 5
*/
@InternalApi
@NullMarked
public interface BlobListenableField {

/**
Expand Down
41 changes: 27 additions & 14 deletions src/main/org/firebirdsql/jdbc/field/FBBigDecimalField.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import org.firebirdsql.gds.JaybirdErrorCodes;
import org.firebirdsql.gds.ng.FbExceptionBuilder;
import org.firebirdsql.gds.ng.fields.FieldDescriptor;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

import java.math.BigDecimal;
import java.math.BigInteger;
Expand Down Expand Up @@ -54,8 +57,9 @@ final class FBBigDecimalField extends FBField {
@SuppressWarnings("java:S2111")
private static final BigDecimal BD_MIN_DOUBLE = new BigDecimal(MIN_DOUBLE_VALUE);

private final FieldDataSize fieldDataSize;
private final @NonNull FieldDataSize fieldDataSize;

@NullMarked
FBBigDecimalField(FieldDescriptor fieldDescriptor, FieldDataProvider dataProvider, int requiredType)
throws SQLException {
super(fieldDescriptor, dataProvider, requiredType);
Expand Down Expand Up @@ -84,6 +88,7 @@ public byte getByte() throws SQLException {
return (byte) longValue;
}

@NullMarked
private SQLException outOfRangeGetConversion(String type, long value) {
return invalidGetConversion(type, "value %d out of range".formatted(value));
}
Expand Down Expand Up @@ -209,17 +214,19 @@ public void setBigInteger(BigInteger value) throws SQLException {
* @author Mark Rotteveel
*/
@SuppressWarnings("java:S1168")
@NullMarked
private enum FieldDataSize {
SHORT {
@Override
protected BigDecimal decode(FieldDescriptor fieldDescriptor, byte[] fieldData) {
protected @Nullable BigDecimal decode(FieldDescriptor fieldDescriptor, byte @Nullable [] fieldData) {
if (fieldData == null) return null;
long value = fieldDescriptor.getDatatypeCoder().decodeShort(fieldData);
return BigDecimal.valueOf(value, -1 * fieldDescriptor.getScale());
}

@Override
protected byte[] encode(FieldDescriptor fieldDescriptor, BigDecimal value) throws SQLException {
protected byte @Nullable [] encode(FieldDescriptor fieldDescriptor, @Nullable BigDecimal value)
throws SQLException {
if (value == null) return null;
BigInteger unscaledValue = normalize(value, -1 * fieldDescriptor.getScale());
if (unscaledValue.compareTo(MAX_SHORT) > 0 || unscaledValue.compareTo(MIN_SHORT) < 0) {
Expand All @@ -230,14 +237,15 @@ protected byte[] encode(FieldDescriptor fieldDescriptor, BigDecimal value) throw
},
INTEGER {
@Override
protected BigDecimal decode(FieldDescriptor fieldDescriptor, byte[] fieldData) {
protected @Nullable BigDecimal decode(FieldDescriptor fieldDescriptor, byte @Nullable [] fieldData) {
if (fieldData == null) return null;
long value = fieldDescriptor.getDatatypeCoder().decodeInt(fieldData);
return BigDecimal.valueOf(value, -1 * fieldDescriptor.getScale());
}

@Override
protected byte[] encode(FieldDescriptor fieldDescriptor, BigDecimal value) throws SQLException {
protected byte @Nullable [] encode(FieldDescriptor fieldDescriptor, @Nullable BigDecimal value)
throws SQLException {
if (value == null) return null;
BigInteger unscaledValue = normalize(value, -1 * fieldDescriptor.getScale());
if (unscaledValue.compareTo(MAX_INT) > 0 || unscaledValue.compareTo(MIN_INT) < 0) {
Expand All @@ -248,14 +256,15 @@ protected byte[] encode(FieldDescriptor fieldDescriptor, BigDecimal value) throw
},
LONG {
@Override
protected BigDecimal decode(FieldDescriptor fieldDescriptor, byte[] fieldData) {
protected @Nullable BigDecimal decode(FieldDescriptor fieldDescriptor, byte @Nullable [] fieldData) {
if (fieldData == null) return null;
long value = fieldDescriptor.getDatatypeCoder().decodeLong(fieldData);
return BigDecimal.valueOf(value, -1 * fieldDescriptor.getScale());
}

@Override
protected byte[] encode(FieldDescriptor fieldDescriptor, BigDecimal value) throws SQLException {
protected byte @Nullable [] encode(FieldDescriptor fieldDescriptor, @Nullable BigDecimal value)
throws SQLException {
if (value == null) return null;
BigInteger unscaledValue = normalize(value, -1 * fieldDescriptor.getScale());
if (unscaledValue.compareTo(MAX_LONG) > 0 || unscaledValue.compareTo(MIN_LONG) < 0) {
Expand All @@ -266,14 +275,15 @@ protected byte[] encode(FieldDescriptor fieldDescriptor, BigDecimal value) throw
},
DOUBLE {
@Override
protected BigDecimal decode(FieldDescriptor fieldDescriptor, byte[] fieldData) {
protected @Nullable BigDecimal decode(FieldDescriptor fieldDescriptor, byte @Nullable [] fieldData) {
if (fieldData == null) return null;
BigDecimal value = BigDecimal.valueOf(fieldDescriptor.getDatatypeCoder().decodeDouble(fieldData));
return value.setScale(Math.abs(fieldDescriptor.getScale()), RoundingMode.HALF_EVEN);
}

@Override
protected byte[] encode(FieldDescriptor fieldDescriptor, BigDecimal value) throws SQLException {
protected byte @Nullable [] encode(FieldDescriptor fieldDescriptor, @Nullable BigDecimal value)
throws SQLException {
if (value == null) return null;
// check if value is within bounds
if (value.compareTo(BD_MAX_DOUBLE) > 0 || value.compareTo(BD_MIN_DOUBLE) < 0) {
Expand All @@ -285,14 +295,15 @@ protected byte[] encode(FieldDescriptor fieldDescriptor, BigDecimal value) throw
},
INT128 {
@Override
protected BigDecimal decode(FieldDescriptor fieldDescriptor, byte[] fieldData) {
protected @Nullable BigDecimal decode(FieldDescriptor fieldDescriptor, byte @Nullable [] fieldData) {
if (fieldData == null) return null;
BigInteger int128Value = fieldDescriptor.getDatatypeCoder().decodeInt128(fieldData);
return new BigDecimal(int128Value, -1 * fieldDescriptor.getScale());
}

@Override
protected byte[] encode(FieldDescriptor fieldDescriptor, BigDecimal value) throws SQLException {
protected byte @Nullable [] encode(FieldDescriptor fieldDescriptor, @Nullable BigDecimal value)
throws SQLException {
if (value == null) return null;
BigInteger unscaledValue = normalize(value, -1 * fieldDescriptor.getScale());
if (unscaledValue.bitLength() > 127) {
Expand All @@ -313,7 +324,8 @@ protected byte[] encode(FieldDescriptor fieldDescriptor, BigDecimal value) throw
* encoded data
* @return BigDecimal instance
*/
protected abstract BigDecimal decode(FieldDescriptor fieldDescriptor, byte[] fieldData) throws SQLException;
protected abstract @Nullable BigDecimal decode(FieldDescriptor fieldDescriptor, byte @Nullable [] fieldData)
throws SQLException;

/**
* Encodes the provided BigDecimal to fieldData
Expand All @@ -324,7 +336,8 @@ protected byte[] encode(FieldDescriptor fieldDescriptor, BigDecimal value) throw
* BigDecimal instance
* @return encoded data
*/
protected abstract byte[] encode(FieldDescriptor fieldDescriptor, BigDecimal value) throws SQLException;
protected abstract byte @Nullable [] encode(FieldDescriptor fieldDescriptor, @Nullable BigDecimal value)
throws SQLException;

/**
* Helper method to rescale the BigDecimal to the provided scale and return the unscaled value of
Expand Down Expand Up @@ -363,7 +376,7 @@ protected static FieldDataSize getFieldDataSize(FieldDescriptor fieldDescriptor)
};
}

static SQLException bigDecimalConversionError(FieldDescriptor fieldDescriptor, BigDecimal value) {
static SQLException bigDecimalConversionError(FieldDescriptor fieldDescriptor, @Nullable BigDecimal value) {
String message = String.format(
"Unsupported set conversion requested for field %s at index %d (JDBC type %s), "
+ "source type: " + BIG_DECIMAL_CLASS_NAME + ", reason: value %f out of range",
Expand Down
8 changes: 6 additions & 2 deletions src/main/org/firebirdsql/jdbc/field/FBBinaryField.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.firebirdsql.gds.ng.fields.FieldDescriptor;
import org.firebirdsql.jaybird.util.IOUtils;
import org.firebirdsql.jdbc.FBRowId;
import org.jspecify.annotations.NullMarked;

import java.io.*;
import java.sql.DataTruncation;
Expand All @@ -39,7 +40,9 @@
*/
class FBBinaryField extends FBField {

FBBinaryField(FieldDescriptor fieldDescriptor, FieldDataProvider dataProvider, int requiredType) throws SQLException {
@NullMarked
FBBinaryField(FieldDescriptor fieldDescriptor, FieldDataProvider dataProvider, int requiredType)
throws SQLException {
super(fieldDescriptor, dataProvider, requiredType);
}

Expand All @@ -59,7 +62,7 @@ public void setString(String value) throws SQLException {
}

@Override
@SuppressWarnings("java:S1168")
@SuppressWarnings({ "java:S1168", "DataFlowIssue" })
public byte[] getBytes() throws SQLException {
if (isNull()) return null;
// protect against unintentional modification of cached or shared byte-arrays (e.g. in DatabaseMetaData)
Expand All @@ -76,6 +79,7 @@ public void setBytes(byte[] value) throws SQLException {
setFieldData(value);
}

@SuppressWarnings("DataFlowIssue")
@Override
public InputStream getBinaryStream() throws SQLException {
if (isNull()) return null;
Expand Down
30 changes: 18 additions & 12 deletions src/main/org/firebirdsql/jdbc/field/FBBlobField.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import org.firebirdsql.jdbc.FBClob;
import org.firebirdsql.jdbc.FBObjectListener;
import org.firebirdsql.jdbc.FirebirdBlob;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

import java.io.InputStream;
import java.io.Reader;
Expand All @@ -49,13 +52,14 @@ class FBBlobField extends FBField implements FBCloseableField, FBFlushableField,
private InputStream binaryStream;
private Reader characterStream;
private byte[] bytes;
private FBObjectListener.BlobListener blobListener = FBObjectListener.NoActionBlobListener.instance();
final FBBlob.Config blobConfig;
private FBObjectListener.@NonNull BlobListener blobListener = FBObjectListener.NoActionBlobListener.instance();
final FBBlob.@NonNull Config blobConfig;

FBBlobField(FieldDescriptor fieldDescriptor, FieldDataProvider dataProvider, int requiredType, GDSHelper gdsHelper)
throws SQLException {
@NullMarked
FBBlobField(FieldDescriptor fieldDescriptor, FieldDataProvider dataProvider, int requiredType,
@Nullable GDSHelper gdsHelper) throws SQLException {
super(fieldDescriptor, dataProvider, requiredType);
this.gdsHelper = gdsHelper;
setConnection(gdsHelper);
// NOTE: If gdsHelper is really null, it will fail at a later point when attempting to open the blob
// It should only be null for certain types of tests
blobConfig = gdsHelper != null
Expand All @@ -65,6 +69,7 @@ class FBBlobField extends FBField implements FBCloseableField, FBFlushableField,
}

@Override
@NullMarked
public void setBlobListener(FBObjectListener.BlobListener blobListener) {
this.blobListener = blobListener;
}
Expand All @@ -80,7 +85,7 @@ public void close() throws SQLException {
binaryStream = null;
characterStream = null;
length = 0;
blobListener = null;
blobListener = FBObjectListener.NoActionBlobListener.instance();
}
}

Expand Down Expand Up @@ -135,7 +140,7 @@ public byte[] getCachedData() throws SQLException {
}

@Override
public FBFlushableField.CachedObject getCachedObject() throws SQLException {
public FBFlushableField.@NonNull CachedObject getCachedObject() throws SQLException {
if (isNull()) {
return new CachedObject(bytes, binaryStream, characterStream, length);
}
Expand All @@ -144,7 +149,7 @@ public FBFlushableField.CachedObject getCachedObject() throws SQLException {
}

@Override
public void setCachedObject(FBFlushableField.CachedObject cachedObject) {
public void setCachedObject(FBFlushableField.@NonNull CachedObject cachedObject) {
// setNull() to reset field to empty state
setNull();
bytes = cachedObject.bytes;
Expand Down Expand Up @@ -206,21 +211,21 @@ public void flushCachedData() throws SQLException {
this.length = 0;
}

private void copyBinaryStream(InputStream in, long length) throws SQLException {
private void copyBinaryStream(@NonNull InputStream in, long length) throws SQLException {
FBBlob blob = createBlob();
blob.copyStream(in, length);
setFieldData(getDatatypeCoder().encodeLong(blob.getBlobId()));
blobExplicitNull = false;
}

private void copyCharacterStream(Reader in, long length) throws SQLException {
private void copyCharacterStream(@NonNull Reader in, long length) throws SQLException {
FBBlob blob = createBlob();
blob.copyCharacterStream(in, length);
setFieldData(getDatatypeCoder().encodeLong(blob.getBlobId()));
blobExplicitNull = false;
}

private void copyBytes(byte[] bytes, int length) throws SQLException {
private void copyBytes(byte @NonNull [] bytes, int length) throws SQLException {
FBBlob blob = createBlob();
blob.copyBytes(bytes, 0, length);
setFieldData(getDatatypeCoder().encodeLong(blob.getBlobId()));
Expand Down Expand Up @@ -266,7 +271,7 @@ public void setBlob(Blob blob) throws SQLException {
}

@Override
FBBlob createBlob() {
@NonNull FBBlob createBlob() {
return new FBBlob(gdsHelper, blobListener, blobConfig);
}

Expand Down Expand Up @@ -303,6 +308,7 @@ public void setNull() {
}
}

@NullMarked
private <T extends FirebirdBlob> T registerWithTransaction(T blob) {
if (blob instanceof TransactionListener transactionListener) {
FbTransaction currentTransaction = gdsHelper.getCurrentTransaction();
Expand Down
Loading

0 comments on commit 6e56ab7

Please sign in to comment.