diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java index c9bc88718..57dab6643 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java @@ -893,9 +893,9 @@ private static String fractionalSecondsString(long subSecondNanos, int scale) { * * @return a Java object of the desired type. */ - static final Object convertTemporalToObject( - SQLServerConnection connection, JDBCType jdbcType, SSType ssType, Calendar timeZoneCalendar, - int daysSinceBaseDate, long ticksSinceMidnight, int fractionalSecondsScale) throws SQLServerException { + static final Object convertTemporalToObject(SQLServerConnection connection, JDBCType jdbcType, SSType ssType, + Calendar timeZoneCalendar, int daysSinceBaseDate, long ticksSinceMidnight, + int fractionalSecondsScale) throws SQLServerException { // In cases where a Calendar object (and therefore Timezone) is not passed to the method, // use the path below instead to optimize performance. @@ -1132,9 +1132,9 @@ static final Object convertTemporalToObject( ts2.setNanos(subSecondNanos); if (jdbcType == JDBCType.LOCALDATETIME) { if (connection.getIgnoreOffsetOnDateTimeOffsetConversion()) { - return LocalDateTime.of( - cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH), - cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), subSecondNanos); + return LocalDateTime.of(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, + cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), + cal.get(Calendar.SECOND), subSecondNanos); } else { return ts2.toLocalDateTime(); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java index 9358f835e..018c483f8 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java @@ -1115,7 +1115,8 @@ private DataTypes() { static final void throwConversionError(String fromType, String toType) throws SQLServerException { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedConversionFromTo")); Object[] msgArgs = {fromType, toType}; - SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), SQLState.ERROR_IN_ASSIGNMENT.getSQLStateCode(), true); + SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), + SQLState.ERROR_IN_ASSIGNMENT.getSQLStateCode(), true); } /** diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/FailOverInfo.java b/src/main/java/com/microsoft/sqlserver/jdbc/FailOverInfo.java index 940e5dc97..a96bff905 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/FailOverInfo.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/FailOverInfo.java @@ -99,8 +99,8 @@ void failoverAdd(SQLServerConnection connection, boolean actualUseFailoverPartne try { if (useFailoverPartner != actualUseFailoverPartner) { if (connection.getConnectionLogger().isLoggable(Level.FINE)) - connection.getConnectionLogger() - .fine(connection.toString() + " Failover detected. failover partner=" + actualFailoverPartner); + connection.getConnectionLogger().fine( + connection.toString() + " Failover detected. failover partner=" + actualFailoverPartner); useFailoverPartner = actualUseFailoverPartner; } // The checking for actualUseFailoverPartner may look weird but this is required diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 0039457dc..ae2f58082 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -3763,16 +3763,14 @@ void writeDatetime(java.sql.Timestamp dateValue) throws SQLServerException { int subSecondNanos; subSecondNanos = ldt.getNano(); - // Number of days there have been since the SQL Base Date. // These are based on SQL Server algorithms - int daysSinceSQLBaseDate = DDC.daysSinceBaseDate(ldt.getYear(), - ldt.getDayOfYear(), TDS.BASE_YEAR_1900); + int daysSinceSQLBaseDate = DDC.daysSinceBaseDate(ldt.getYear(), ldt.getDayOfYear(), TDS.BASE_YEAR_1900); // Number of milliseconds since midnight of the current day. int millisSinceMidnight = (subSecondNanos + Nanos.PER_MILLISECOND / 2) / Nanos.PER_MILLISECOND + // Millis into - // the current - // second + // the current + // second 1000 * ldt.getSecond() + // Seconds into the current minute 60 * 1000 * ldt.getMinute() + // Minutes into the current hour 60 * 60 * 1000 * ldt.getHour(); // Hours into the current day @@ -5029,8 +5027,8 @@ void writeTVPRows(TVP value) throws SQLServerException { } private void writeInternalTVPRowValues(JDBCType jdbcType, String currentColumnStringValue, Object currentObject, - Map.Entry columnPair, boolean isSqlVariant) - throws SQLServerException, IllegalArgumentException { + Map.Entry columnPair, + boolean isSqlVariant) throws SQLServerException, IllegalArgumentException { boolean isShortValue, isNull; int dataLength; switch (jdbcType) { @@ -5104,7 +5102,6 @@ private void writeInternalTVPRowValues(JDBCType jdbcType, String currentColumnSt * setScale of all BigDecimal value based on metadata as scale is not sent separately for individual * value. Use the rounding used in Server. Say, for BigDecimal("0.1"), if scale in metadata is 0, * then ArithmeticException would be thrown if RoundingMode is not set - * * Additionally, we should check here if the scale is within the bounds of SQLServer as it is * possible for a number with a scale larger than 38 to be passed in. */ @@ -7278,9 +7275,9 @@ final Object readDate(int valueLength, Calendar appTimeZoneCalendar, JDBCType jd // Convert the DATE value to the desired Java type. return DDC.convertTemporalToObject(con, jdbcType, SSType.DATE, appTimeZoneCalendar, localDaysIntoCE, 0, // midnight - // local to - // app time - // zone + // local to + // app time + // zone 0); // scale (ignored for DATE) } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerBulkData.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerBulkData.java index 072ac24fe..7c0525fef 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerBulkData.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerBulkData.java @@ -8,6 +8,7 @@ import java.io.Serializable; import java.sql.SQLException; + /** * Provides an interface used to create classes that read in data from any source (such as a file) and allows a * SQLServerBulkCopy class to write the data to SQL Server tables. diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java index 28e065980..f9bb13215 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java @@ -486,7 +486,7 @@ CallableStatement prepareCall(String sql, int nType, int nConcur, int nHold, * Returns the current flag for calcBigDecimalScale. * * @return calcBigDecimalScale - * Whether calculating big decimal scale from input values is enabled. + * Whether calculating big decimal scale from input values is enabled. */ boolean getCalcBigDecimalScale(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java index af6b1afc1..411aed7b2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java @@ -883,13 +883,14 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource { boolean getUseDefaultJaasConfig(); /** - * Sets whether the default JAAS Configuration will be used. This means the system-wide JAAS configuration + * Sets whether the default JAAS Configuration will be used. This means the system-wide JAAS configuration * is ignored to avoid conflicts with libraries that override the JAAS configuration. * * @param useDefaultJaasConfig - * boolean property to use the default JAAS configuration + * boolean property to use the default JAAS configuration */ void setUseDefaultJaasConfig(boolean useDefaultJaasConfig); + /** * Sets whether Fips Mode should be enabled/disabled on the connection. For FIPS enabled JVM this property should be * true. @@ -1337,7 +1338,7 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource { * Returns value of 'calcBigDecimalScale' from Connection String. * * @param calcBigDecimalScale - * indicates whether the driver should attempt to calculate scale from inputted big decimal values + * indicates whether the driver should attempt to calculate scale from inputted big decimal values */ void setCalcBigDecimalScale(boolean calcBigDecimalScale); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java index fbc078f37..7f2c1653f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java @@ -7,6 +7,7 @@ import java.io.Serializable; + /** * Provides an interface to the {@link SQLServerStatement} class. */ diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IdleConnectionResiliency.java b/src/main/java/com/microsoft/sqlserver/jdbc/IdleConnectionResiliency.java index abc15bf5c..f3d296ea8 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IdleConnectionResiliency.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IdleConnectionResiliency.java @@ -426,8 +426,8 @@ private ReconnectThread() {} eReceived = null; stopRequested = false; if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency.finer("Idle connection resiliency - ReconnectThread initialized. Connection retry count = " + connectRetryCount - + "; Command = " + cmd.toString()); + loggerResiliency.finer("Idle connection resiliency - ReconnectThread initialized. Connection retry count = " + + connectRetryCount + "; Command = " + cmd.toString()); } } @@ -435,7 +435,8 @@ private ReconnectThread() {} @Override public void run() { if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency.finer("Idle connection resiliency - starting ReconnectThread for command: " + command.toString()); + loggerResiliency + .finer("Idle connection resiliency - starting ReconnectThread for command: " + command.toString()); } boolean interruptsEnabled = command.getInterruptsEnabled(); /* @@ -460,8 +461,8 @@ public void run() { while ((connectRetryCount >= 0) && (!stopRequested) && keepRetrying) { if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency.finer("Idle connection resiliency - running reconnect for command: " + command.toString() + " ; connectRetryCount = " - + connectRetryCount); + loggerResiliency.finer("Idle connection resiliency - running reconnect for command: " + + command.toString() + " ; connectRetryCount = " + connectRetryCount); } try { @@ -470,13 +471,16 @@ public void run() { keepRetrying = false; if (loggerResiliency.isLoggable(Level.FINE)) { - loggerResiliency.fine("Idle connection resiliency - reconnect attempt succeeded ; connectRetryCount = " + connectRetryCount); + loggerResiliency + .fine("Idle connection resiliency - reconnect attempt succeeded ; connectRetryCount = " + + connectRetryCount); } } catch (SQLServerException e) { if (loggerResiliency.isLoggable(Level.FINE)) { - loggerResiliency.fine("Idle connection resiliency - reconnect attempt failed ; connectRetryCount = " + connectRetryCount); + loggerResiliency.fine("Idle connection resiliency - reconnect attempt failed ; connectRetryCount = " + + connectRetryCount); } if (!stopRequested) { @@ -484,7 +488,9 @@ public void run() { if (con.isFatalError(e)) { if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency.finer("Idle connection resiliency - reconnect for command: " + command.toString() + " encountered fatal error: " + e.getMessage() + " - stopping reconnect attempt."); + loggerResiliency.finer("Idle connection resiliency - reconnect for command: " + + command.toString() + " encountered fatal error: " + e.getMessage() + + " - stopping reconnect attempt."); } keepRetrying = false; @@ -495,7 +501,8 @@ public void run() { } } catch (InterruptedException ie) { if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency.finer("Idle connection resiliency - query timed out for command: " + command.toString() + ". Stopping reconnect attempt."); + loggerResiliency.finer("Idle connection resiliency - query timed out for command: " + + command.toString() + ". Stopping reconnect attempt."); } // re-interrupt thread @@ -513,7 +520,8 @@ public void run() { command.checkForInterrupt(); } catch (SQLServerException e) { if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency.finer("Idle connection resiliency - timeout occurred on reconnect: " + command.toString() + ". Stopping reconnect attempt."); + loggerResiliency.finer("Idle connection resiliency - timeout occurred on reconnect: " + + command.toString() + ". Stopping reconnect attempt."); } // Interrupted, timeout occurred. Stop retrying. keepRetrying = false; @@ -530,7 +538,8 @@ public void run() { command.setInterruptsEnabled(interruptsEnabled); if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency.finer("Idle connection resiliency - ReconnectThread exiting for command: " + command.toString()); + loggerResiliency + .finer("Idle connection resiliency - ReconnectThread exiting for command: " + command.toString()); } if (timeout != null) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java index 4c3c91093..3c21e7711 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java @@ -65,7 +65,8 @@ private void initAuthInit() throws SQLServerException { GSSName remotePeerName = manager.createName(spn, null); if (useDefaultNativeGSSCredential) { - peerCredentials = manager.createCredential(null, GSSCredential.DEFAULT_LIFETIME, kerberos, GSSCredential.INITIATE_ONLY); + peerCredentials = manager.createCredential(null, GSSCredential.DEFAULT_LIFETIME, kerberos, + GSSCredential.INITIATE_ONLY); } if (null != peerCredentials) { @@ -82,13 +83,13 @@ private void initAuthInit() throws SQLServerException { SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.toString(), Boolean.toString(SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.getDefaultValue()))); - if (!configName.equals( - SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue()) && useDefaultJaas) { + if (!configName.equals(SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue()) + && useDefaultJaas) { // Reset configName to default -- useDefaultJaas setting takes priority over jaasConfigName if (authLogger.isLoggable(Level.WARNING)) { - authLogger.warning(toString() + String.format( - "Using default JAAS configuration, configured %s=%s will not be used.", - SQLServerDriverStringProperty.JAAS_CONFIG_NAME, configName)); + authLogger.warning(toString() + + String.format("Using default JAAS configuration, configured %s=%s will not be used.", + SQLServerDriverStringProperty.JAAS_CONFIG_NAME, configName)); } configName = SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue(); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java index 858111ca9..5cd9cccde 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java @@ -594,8 +594,8 @@ private void setTypeDefinition(DTV dtv) { } else { if (con.getCalcBigDecimalScale() && dtv.getJavaType() == JavaType.BIGDECIMAL && null != dtv.getSetterValue()) { - String[] plainValueArray - = ((BigDecimal) dtv.getSetterValue()).abs().toPlainString().split("\\."); + String[] plainValueArray = ((BigDecimal) dtv.getSetterValue()).abs().toPlainString() + .split("\\."); // Precision is computed as opposed to using BigDecimal.precision(). This is because the // BigDecimal method can lead to inaccurate results. @@ -611,12 +611,12 @@ private void setTypeDefinition(DTV dtv) { } else { calculatedPrecision = plainValueArray[0].length() + plainValueArray[1].length(); } - } else { + } else { calculatedPrecision = plainValueArray[0].length(); } - param.typeDefinition = SSType.DECIMAL.toString() + "(" + calculatedPrecision + "," + - (plainValueArray.length == 2 ? plainValueArray[1].length() : 0) + ")"; + param.typeDefinition = SSType.DECIMAL.toString() + "(" + calculatedPrecision + "," + + (plainValueArray.length == 2 ? plainValueArray[1].length() : 0) + ")"; } else { param.typeDefinition = SSType.DECIMAL.toString() + "(" + SQLServerConnection.MAX_DECIMAL_PRECISION + "," + scale + ")"; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/PersistentTokenCacheAccessAspect.java b/src/main/java/com/microsoft/sqlserver/jdbc/PersistentTokenCacheAccessAspect.java index 091c5a5e8..31b87fb27 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/PersistentTokenCacheAccessAspect.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/PersistentTokenCacheAccessAspect.java @@ -53,7 +53,8 @@ public void beforeCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { lock.lock(); try { - if (null != iTokenCacheAccessContext && iTokenCacheAccessContext.hasCacheChanged() && null != iTokenCacheAccessContext.tokenCache()) + if (null != iTokenCacheAccessContext && iTokenCacheAccessContext.hasCacheChanged() + && null != iTokenCacheAccessContext.tokenCache()) cache = iTokenCacheAccessContext.tokenCache().serialize(); } finally { lock.unlock(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index 427fe916f..8b20c4f8f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -1729,17 +1729,20 @@ private void getDestinationMetadata() throws SQLServerException { if (null != destinationTableMetadata) { rs = (SQLServerResultSet) destinationTableMetadata; } else { - stmt = (SQLServerStatement) connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, connection.getHoldability(), stmtColumnEncriptionSetting); + stmt = (SQLServerStatement) connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, connection.getHoldability(), stmtColumnEncriptionSetting); // Get destination metadata - rs = stmt.executeQueryInternal("sp_executesql N'SET FMTONLY ON SELECT * FROM " + escapedDestinationTableName + " '"); + rs = stmt.executeQueryInternal( + "sp_executesql N'SET FMTONLY ON SELECT * FROM " + escapedDestinationTableName + " '"); } int destColumnMetadataCount = rs.getMetaData().getColumnCount(); destColumnMetadata = new HashMap<>(); destCekTable = rs.getCekTable(); - metaDataQuery = "select * from sys.columns where " + "object_id=OBJECT_ID('" + escapedDestinationTableName + "') " + "order by column_id ASC"; + metaDataQuery = "select * from sys.columns where " + "object_id=OBJECT_ID('" + + escapedDestinationTableName + "') " + "order by column_id ASC"; try (SQLServerStatement statementMoreMetadata = (SQLServerStatement) connection.createStatement(); SQLServerResultSet rsMoreMetaData = statementMoreMetadata.executeQueryInternal(metaDataQuery)) { @@ -1751,8 +1754,8 @@ private void getDestinationMetadata() throws SQLServerException { } // Skip computed columns if (!rsMoreMetaData.getBoolean("is_computed")) { - destColumnMetadata.put(i, new BulkColumnMetaData(rs.getColumn(i), rsMoreMetaData.getString("collation_name"), - bulkCopyEncryptionType)); + destColumnMetadata.put(i, new BulkColumnMetaData(rs.getColumn(i), + rsMoreMetaData.getString("collation_name"), bulkCopyEncryptionType)); } } else { destColumnMetadata.put(i, new BulkColumnMetaData(rs.getColumn(i))); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java index 07c8969a2..6ccb1b185 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java @@ -599,8 +599,8 @@ public final String getNString(int parameterIndex) throws SQLException { public final String getNString(String parameterName) throws SQLException { loggerExternal.entering(getClassNameLogging(), "getNString", parameterName); checkClosed(); - String value = (String) getValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), - JDBCType.NCHAR); + String value = (String) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.NCHAR); loggerExternal.exiting(getClassNameLogging(), "getNString", value); return value; } @@ -691,8 +691,8 @@ public byte[] getBytes(int index) throws SQLServerException { public byte[] getBytes(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getBytes", parameterName); checkClosed(); - byte[] value = (byte[]) getValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), - JDBCType.BINARY); + byte[] value = (byte[]) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.BINARY); loggerExternal.exiting(getClassNameLogging(), "getBytes", value); return value; } @@ -750,8 +750,8 @@ public double getDouble(int index) throws SQLServerException { public double getDouble(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getDouble", parameterName); checkClosed(); - Double value = (Double) getValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), - JDBCType.DOUBLE); + Double value = (Double) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.DOUBLE); loggerExternal.exiting(getClassNameLogging(), "getDouble", value); return null != value ? value : 0; } @@ -1017,7 +1017,8 @@ public Timestamp getTimestamp(String parameterName, Calendar cal) throws SQLServ loggerExternal.entering(getClassNameLogging(), GET_TIMESTAMP, new Object[] {parameterName, cal}); checkClosed(); java.sql.Timestamp value = (java.sql.Timestamp) getValue( - findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.TIMESTAMP, cal); + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.TIMESTAMP, + cal); loggerExternal.exiting(getClassNameLogging(), GET_TIMESTAMP, value); return value; } @@ -1066,7 +1067,8 @@ public Timestamp getDateTime(String parameterName, Calendar cal) throws SQLServe loggerExternal.entering(getClassNameLogging(), "getDateTime", new Object[] {parameterName, cal}); checkClosed(); java.sql.Timestamp value = (java.sql.Timestamp) getValue( - findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.DATETIME, cal); + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.DATETIME, + cal); loggerExternal.exiting(getClassNameLogging(), "getDateTime", value); return value; } @@ -1086,7 +1088,8 @@ public Timestamp getSmallDateTime(String parameterName) throws SQLServerExceptio loggerExternal.entering(getClassNameLogging(), "getSmallDateTime", parameterName); checkClosed(); java.sql.Timestamp value = (java.sql.Timestamp) getValue( - findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.SMALLDATETIME); + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), + JDBCType.SMALLDATETIME); loggerExternal.exiting(getClassNameLogging(), "getSmallDateTime", value); return value; } @@ -1140,7 +1143,8 @@ public microsoft.sql.DateTimeOffset getDateTimeOffset(String parameterName) thro SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET, null); microsoft.sql.DateTimeOffset value = (microsoft.sql.DateTimeOffset) getValue( - findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.DATETIMEOFFSET); + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), + JDBCType.DATETIMEOFFSET); loggerExternal.exiting(getClassNameLogging(), "getDateTimeOffset", value); return value; } @@ -1520,8 +1524,7 @@ private int findColumn(String columnName, CallableStatementGetterSetterMethod me String columnNameWithoutAtSign = stripLeadingAtSign(columnName); for (int i = 0; i < inOutParam.length; i++) { - if (null != inOutParam[i].getName() - && inOutParam[i].getName().equalsIgnoreCase(columnNameWithoutAtSign)) { + if (null != inOutParam[i].getName() && inOutParam[i].getName().equalsIgnoreCase(columnNameWithoutAtSign)) { return i + 1; } } @@ -1646,8 +1649,8 @@ public final void setNCharacterStream(String parameterName, Reader value) throws if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNCharacterStream", new Object[] {parameterName, value}); checkClosed(); - setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.NCHARACTER, - value, JavaType.READER, DataTypes.UNKNOWN_STREAM_LENGTH); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), + StreamType.NCHARACTER, value, JavaType.READER, DataTypes.UNKNOWN_STREAM_LENGTH); loggerExternal.exiting(getClassNameLogging(), "setNCharacterStream"); } @@ -1657,8 +1660,8 @@ public final void setNCharacterStream(String parameterName, Reader value, long l loggerExternal.entering(getClassNameLogging(), "setNCharacterStream", new Object[] {parameterName, value, length}); checkClosed(); - setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.NCHARACTER, - value, JavaType.READER, length); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), + StreamType.NCHARACTER, value, JavaType.READER, length); loggerExternal.exiting(getClassNameLogging(), "setNCharacterStream"); } @@ -1707,8 +1710,8 @@ public final void setNClob(String parameterName, Reader value) throws SQLExcepti if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[] {parameterName, value}); checkClosed(); - setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.NCHARACTER, - value, JavaType.READER, DataTypes.UNKNOWN_STREAM_LENGTH); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), + StreamType.NCHARACTER, value, JavaType.READER, DataTypes.UNKNOWN_STREAM_LENGTH); loggerExternal.exiting(getClassNameLogging(), "setNClob"); } @@ -1717,8 +1720,8 @@ public final void setNClob(String parameterName, Reader value, long length) thro if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[] {parameterName, value, length}); checkClosed(); - setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.NCHARACTER, - value, JavaType.READER, length); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), + StreamType.NCHARACTER, value, JavaType.READER, length); loggerExternal.exiting(getClassNameLogging(), "setNClob"); } @@ -1777,8 +1780,8 @@ public void setObject(String parameterName, Object value, int sqlType, int decim loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {parameterName, value, sqlType, decimals}); checkClosed(); - setObject(setterGetParam(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD)), value, - JavaType.of(value), JDBCType.of(sqlType), decimals, null, false, + setObject(setterGetParam(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD)), + value, JavaType.of(value), JDBCType.of(sqlType), decimals, null, false, findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), null); loggerExternal.exiting(getClassNameLogging(), "setObject"); } @@ -1795,8 +1798,8 @@ public void setObject(String parameterName, Object value, int sqlType, int decim // this is the number of digits after the decimal point. // For all other types, this value will be ignored. - setObject(setterGetParam(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD)), value, - JavaType.of(value), JDBCType.of(sqlType), + setObject(setterGetParam(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD)), + value, JavaType.of(value), JDBCType.of(sqlType), (java.sql.Types.NUMERIC == sqlType || java.sql.Types.DECIMAL == sqlType) ? decimals : null, null, forceEncrypt, findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), null); @@ -1816,11 +1819,12 @@ public final void setObject(String parameterName, Object value, int targetSqlTyp // InputStream and Reader, this is the length of the data in the stream or reader. // For all other types, this value will be ignored. - setObject(setterGetParam(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD)), value, - JavaType.of(value), JDBCType.of(targetSqlType), + setObject(setterGetParam(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD)), + value, JavaType.of(value), JDBCType.of(targetSqlType), (java.sql.Types.NUMERIC == targetSqlType || java.sql.Types.DECIMAL == targetSqlType || InputStream.class.isInstance(value) || Reader.class.isInstance(value)) ? scale : null, - precision, false, findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), null); + precision, false, findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), + null); loggerExternal.exiting(getClassNameLogging(), "setObject"); } @@ -1957,8 +1961,8 @@ public void setDateTimeOffset(String parameterName, microsoft.sql.DateTimeOffset if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DATETIMEOFFSET, - value, JavaType.DATETIMEOFFSET, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), + JDBCType.DATETIMEOFFSET, value, JavaType.DATETIMEOFFSET, false); loggerExternal.exiting(getClassNameLogging(), "setDateTimeOffset"); } @@ -1968,8 +1972,8 @@ public void setDateTimeOffset(String parameterName, microsoft.sql.DateTimeOffset if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DATETIMEOFFSET, - value, JavaType.DATETIMEOFFSET, null, scale, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), + JDBCType.DATETIMEOFFSET, value, JavaType.DATETIMEOFFSET, null, scale, false); loggerExternal.exiting(getClassNameLogging(), "setDateTimeOffset"); } @@ -1980,8 +1984,8 @@ public void setDateTimeOffset(String parameterName, microsoft.sql.DateTimeOffset loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DATETIMEOFFSET, - value, JavaType.DATETIMEOFFSET, null, scale, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), + JDBCType.DATETIMEOFFSET, value, JavaType.DATETIMEOFFSET, null, scale, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setDateTimeOffset"); } @@ -2054,8 +2058,8 @@ public void setSmallDateTime(String parameterName, java.sql.Timestamp value) thr if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setSmallDateTime", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.SMALLDATETIME, - value, JavaType.TIMESTAMP, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), + JDBCType.SMALLDATETIME, value, JavaType.TIMESTAMP, false); loggerExternal.exiting(getClassNameLogging(), "setSmallDateTime"); } @@ -2066,8 +2070,8 @@ public void setSmallDateTime(String parameterName, java.sql.Timestamp value, loggerExternal.entering(getClassNameLogging(), "setSmallDateTime", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.SMALLDATETIME, - value, JavaType.TIMESTAMP, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), + JDBCType.SMALLDATETIME, value, JavaType.TIMESTAMP, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setSmallDateTime"); } @@ -2097,8 +2101,8 @@ public void setBytes(String parameterName, byte[] value) throws SQLServerExcepti if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBytes", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BINARY, value, - JavaType.BYTEARRAY, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BINARY, + value, JavaType.BYTEARRAY, false); loggerExternal.exiting(getClassNameLogging(), "setBytes"); } @@ -2108,8 +2112,8 @@ public void setBytes(String parameterName, byte[] value, boolean forceEncrypt) t loggerExternal.entering(getClassNameLogging(), "setBytes", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BINARY, value, - JavaType.BYTEARRAY, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BINARY, + value, JavaType.BYTEARRAY, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setBytes"); } @@ -2118,8 +2122,8 @@ public void setByte(String parameterName, byte value) throws SQLServerException if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setByte", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TINYINT, value, - JavaType.BYTE, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TINYINT, + value, JavaType.BYTE, false); loggerExternal.exiting(getClassNameLogging(), "setByte"); } @@ -2129,8 +2133,8 @@ public void setByte(String parameterName, byte value, boolean forceEncrypt) thro loggerExternal.entering(getClassNameLogging(), "setByte", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TINYINT, value, - JavaType.BYTE, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TINYINT, + value, JavaType.BYTE, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setByte"); } @@ -2139,8 +2143,8 @@ public void setString(String parameterName, String value) throws SQLServerExcept if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setString", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.VARCHAR, value, - JavaType.STRING, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.VARCHAR, + value, JavaType.STRING, false); loggerExternal.exiting(getClassNameLogging(), "setString"); } @@ -2150,8 +2154,8 @@ public void setString(String parameterName, String value, boolean forceEncrypt) loggerExternal.entering(getClassNameLogging(), "setString", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.VARCHAR, value, - JavaType.STRING, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.VARCHAR, + value, JavaType.STRING, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setString"); } @@ -2202,8 +2206,8 @@ public void setBigDecimal(String parameterName, BigDecimal value) throws SQLServ if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DECIMAL, value, - JavaType.BIGDECIMAL, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DECIMAL, + value, JavaType.BIGDECIMAL, false); loggerExternal.exiting(getClassNameLogging(), "setBigDecimal"); } @@ -2214,8 +2218,8 @@ public void setBigDecimal(String parameterName, BigDecimal value, int precision, loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[] {parameterName, value, precision, scale}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DECIMAL, value, - JavaType.BIGDECIMAL, precision, scale, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DECIMAL, + value, JavaType.BIGDECIMAL, precision, scale, false); loggerExternal.exiting(getClassNameLogging(), "setBigDecimal"); } @@ -2226,8 +2230,8 @@ public void setBigDecimal(String parameterName, BigDecimal value, int precision, loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[] {parameterName, value, precision, scale, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DECIMAL, value, - JavaType.BIGDECIMAL, precision, scale, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DECIMAL, + value, JavaType.BIGDECIMAL, precision, scale, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setBigDecimal"); } @@ -2236,8 +2240,8 @@ public void setDouble(String parameterName, double value) throws SQLServerExcept if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDouble", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DOUBLE, value, - JavaType.DOUBLE, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DOUBLE, + value, JavaType.DOUBLE, false); loggerExternal.exiting(getClassNameLogging(), "setDouble"); } @@ -2247,8 +2251,8 @@ public void setDouble(String parameterName, double value, boolean forceEncrypt) loggerExternal.entering(getClassNameLogging(), "setDouble", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DOUBLE, value, - JavaType.DOUBLE, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DOUBLE, + value, JavaType.DOUBLE, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setDouble"); } @@ -2278,8 +2282,8 @@ public void setInt(String parameterName, int value) throws SQLServerException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setInt", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.INTEGER, value, - JavaType.INTEGER, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.INTEGER, + value, JavaType.INTEGER, false); loggerExternal.exiting(getClassNameLogging(), "setInt"); } @@ -2288,8 +2292,8 @@ public void setInt(String parameterName, int value, boolean forceEncrypt) throws if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setInt", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.INTEGER, value, - JavaType.INTEGER, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.INTEGER, + value, JavaType.INTEGER, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setInt"); } @@ -2298,8 +2302,8 @@ public void setLong(String parameterName, long value) throws SQLServerException if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setLong", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BIGINT, value, - JavaType.LONG, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BIGINT, + value, JavaType.LONG, false); loggerExternal.exiting(getClassNameLogging(), "setLong"); } @@ -2309,8 +2313,8 @@ public void setLong(String parameterName, long value, boolean forceEncrypt) thro loggerExternal.entering(getClassNameLogging(), "setLong", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BIGINT, value, - JavaType.LONG, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BIGINT, + value, JavaType.LONG, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setLong"); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 5e148f313..f9df28f24 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -165,7 +165,8 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial private transient SQLServerAccessTokenCallback accessTokenCallback = null; /** Flag indicating whether to use sp_sproc_columns for parameter name lookup */ - private boolean useFlexibleCallableStatements = SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.getDefaultValue(); + private boolean useFlexibleCallableStatements = SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS + .getDefaultValue(); /** * Keep this distinct from _federatedAuthenticationRequested, since some fedauth library types may not need more @@ -983,7 +984,8 @@ public void setDelayLoadingLobs(boolean b) { } /** Boolean that indicates whether datetime types are converted to java.time objects using java.time rules */ - private boolean ignoreOffsetOnDateTimeOffsetConversion = SQLServerDriverBooleanProperty.IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION.getDefaultValue(); + private boolean ignoreOffsetOnDateTimeOffsetConversion = SQLServerDriverBooleanProperty.IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION + .getDefaultValue(); @Override public boolean getIgnoreOffsetOnDateTimeOffsetConversion() { @@ -2103,7 +2105,8 @@ Connection connectInternal(Properties propsIn, sPropKey = SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_SCALE.toString(); sPropValue = activeConnectionProperties.getProperty(sPropKey); if (null == sPropValue) { - sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_SCALE.getDefaultValue()); + sPropValue = Boolean + .toString(SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_SCALE.getDefaultValue()); activeConnectionProperties.setProperty(sPropKey, sPropValue); } @@ -2264,8 +2267,8 @@ Connection connectInternal(Properties propsIn, sPropKey = SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.toString(); sPropValue = activeConnectionProperties.getProperty(sPropKey); if (null == sPropValue) { - sPropValue = Boolean - .toString(SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.getDefaultValue()); + sPropValue = Boolean.toString( + SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.getDefaultValue()); activeConnectionProperties.setProperty(sPropKey, sPropValue); } useFlexibleCallableStatements = isBooleanPropertyOn(sPropKey, sPropValue); @@ -2901,7 +2904,9 @@ else if (0 == requestedPacketSize) sPropKey = SQLServerDriverBooleanProperty.IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION.toString(); sPropValue = activeConnectionProperties.getProperty(sPropKey); if (null == sPropValue) { - sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION.getDefaultValue()); + sPropValue = Boolean + .toString(SQLServerDriverBooleanProperty.IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION + .getDefaultValue()); activeConnectionProperties.setProperty(sPropKey, sPropValue); } ignoreOffsetOnDateTimeOffsetConversion = isBooleanPropertyOn(sPropKey, sPropValue); @@ -3113,8 +3118,8 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu long intervalExpireFullTimeout = loginStartTime + loginTimeoutMs; if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency.finer(toString() + " Connection open - start time: " + loginStartTime + " time out time: " + timerExpire - + " timeout unit interval: " + timeoutUnitInterval); + loggerResiliency.finer(toString() + " Connection open - start time: " + loginStartTime + " time out time: " + + timerExpire + " timeout unit interval: " + timeoutUnitInterval); } // Initialize loop variables @@ -3147,7 +3152,9 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu } else { if (routingInfo != null) { if (loggerRedirection.isLoggable(Level.FINE)) { - loggerRedirection.fine(toString() + " Connection open - redirecting to server and instance: " + routingInfo.getFullServerName()); + loggerRedirection + .fine(toString() + " Connection open - redirecting to server and instance: " + + routingInfo.getFullServerName()); } currentPrimaryPlaceHolder = routingInfo; routingInfo = null; @@ -3159,14 +3166,15 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu } if (loggerResiliency.isLoggable(Level.FINE) && retryAttempt > 0) { - loggerResiliency.fine(toString() + " Connection open - starting connection retry attempt number: " + retryAttempt); + loggerResiliency.fine(toString() + " Connection open - starting connection retry attempt number: " + + retryAttempt); } if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency - .finer(toString() + " Connection open - attempt server name: " + currentConnectPlaceHolder.getServerName() - + " port: " + currentConnectPlaceHolder.getPortNumber() + " InstanceName: " - + currentConnectPlaceHolder.getInstanceName() + " useParallel: " + useParallel); + loggerResiliency.finer(toString() + " Connection open - attempt server name: " + + currentConnectPlaceHolder.getServerName() + " port: " + + currentConnectPlaceHolder.getPortNumber() + " InstanceName: " + + currentConnectPlaceHolder.getInstanceName() + " useParallel: " + useParallel); loggerResiliency.finer(toString() + " Connection open - attempt end time: " + intervalExpire); loggerResiliency.finer(toString() + " Connection open - attempt number: " + retryAttempt); } @@ -3192,7 +3200,8 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu noOfRedirections++; if (loggerRedirection.isLoggable(Level.FINE)) { - loggerRedirection.fine(toString() + " Connection open - redirection count: " + noOfRedirections); + loggerRedirection + .fine(toString() + " Connection open - redirection count: " + noOfRedirections); } if (noOfRedirections > 1) { @@ -3241,7 +3250,8 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu } } else { if (loggerResiliency.isLoggable(Level.FINE) && retryAttempt > 0) { - loggerResiliency.fine(toString() + " Connection open - connection retry succeeded on attempt number: " + retryAttempt); + loggerResiliency.fine(toString() + + " Connection open - connection retry succeeded on attempt number: " + retryAttempt); } break; // leave the while loop -- we've successfully connected @@ -3249,11 +3259,14 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu } catch (SQLServerException e) { if (loggerResiliency.isLoggable(Level.FINE) && retryAttempt > 0) { - loggerResiliency.fine(toString() + " Connection open - connection retry failed on attempt number: " + retryAttempt); + loggerResiliency.fine(toString() + " Connection open - connection retry failed on attempt number: " + + retryAttempt); } if (loggerResiliency.isLoggable(Level.FINER) && (retryAttempt >= connectRetryCount)) { - loggerResiliency.finer(toString() + " Connection open - connection failed. Maximum connection retry count " + connectRetryCount + " reached."); + loggerResiliency + .finer(toString() + " Connection open - connection failed. Maximum connection retry count " + + connectRetryCount + " reached."); } int errorCode = e.getErrorCode(); @@ -3274,10 +3287,14 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu // for non-dbmirroring cases, do not retry after tcp socket connection succeeds ) { if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency.finer(toString() + " Connection open - connection failed on attempt: " + retryAttempt + "."); - loggerResiliency.finer(toString() + " Connection open - connection failure. Driver error code: " + driverErrorCode); + loggerResiliency.finer( + toString() + " Connection open - connection failed on attempt: " + retryAttempt + "."); + loggerResiliency.finer(toString() + " Connection open - connection failure. Driver error code: " + + driverErrorCode); if (null != sqlServerError && !sqlServerError.getErrorMessage().isEmpty()) { - loggerResiliency.finer(toString() + " Connection open - connection failure. SQL Server error : " + sqlServerError.getErrorMessage()); + loggerResiliency + .finer(toString() + " Connection open - connection failure. SQL Server error : " + + sqlServerError.getErrorMessage()); } } @@ -3286,10 +3303,14 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu throw e; } else { if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency.finer(toString() + " Connection open - connection failed on attempt: " + retryAttempt + "."); - loggerResiliency.finer(toString() + " Connection open - connection failure. Driver error code: " + driverErrorCode); + loggerResiliency.finer( + toString() + " Connection open - connection failed on attempt: " + retryAttempt + "."); + loggerResiliency.finer(toString() + " Connection open - connection failure. Driver error code: " + + driverErrorCode); if (null != sqlServerError && !sqlServerError.getErrorMessage().isEmpty()) { - loggerResiliency.finer(toString() + " Connection open - connection failure. SQL Server error : " + sqlServerError.getErrorMessage()); + loggerResiliency + .finer(toString() + " Connection open - connection failure. SQL Server error : " + + sqlServerError.getErrorMessage()); } } @@ -3308,10 +3329,14 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu || TimeUnit.SECONDS.toMillis(connectRetryInterval) >= remainingTime) && (remainingTime <= TimeUnit.SECONDS.toMillis(connectRetryInterval))) { if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency.finer(toString() + " Connection open - connection failed on attempt: " + retryAttempt + "."); - loggerResiliency.finer(toString() + " Connection open - connection failure. Driver error code: " + driverErrorCode); + loggerResiliency.finer( + toString() + " Connection open - connection failed on attempt: " + retryAttempt + "."); + loggerResiliency.finer(toString() + " Connection open - connection failure. Driver error code: " + + driverErrorCode); if (null != sqlServerError && !sqlServerError.getErrorMessage().isEmpty()) { - loggerResiliency.finer(toString() + " Connection open - connection failure. SQL Server error : " + sqlServerError.getErrorMessage()); + loggerResiliency + .finer(toString() + " Connection open - connection failure. SQL Server error : " + + sqlServerError.getErrorMessage()); } } @@ -3329,15 +3354,16 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu // special case for TLS intermittent failures: no wait retries tlsRetryAttempt++; if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency.finer(toString() + - " Connection open - connection failed during SSL handshake. Retry due to an intermittent TLS 1.2 failure issue. Retry attempt = " - + tlsRetryAttempt + " of " + INTERMITTENT_TLS_MAX_RETRY); + loggerResiliency.finer(toString() + + " Connection open - connection failed during SSL handshake. Retry due to an intermittent TLS 1.2 failure issue. Retry attempt = " + + tlsRetryAttempt + " of " + INTERMITTENT_TLS_MAX_RETRY); } } else { if (retryAttempt++ >= connectRetryCount && TransientError.isTransientError(sqlServerError) && !timerHasExpired(timerExpire) && (!isDBMirroring || (1 == retryAttempt % 2))) { if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency.finer(toString() + " Connection open - sleeping milisec: " + connectRetryInterval); + loggerResiliency + .finer(toString() + " Connection open - sleeping milisec: " + connectRetryInterval); } if (loggerResiliency.isLoggable(Level.FINER)) { loggerResiliency.finer(toString() + " Connection open - connection failed on transient error " @@ -4166,7 +4192,8 @@ boolean executeCommand(TDSCommand newCommand) throws SQLServerException { } if (loggerResiliency.isLoggable(Level.FINE)) { - loggerResiliency.fine(toString() + " Idle connection resiliency - starting idle connection resiliency reconnect."); + loggerResiliency.fine(toString() + + " Idle connection resiliency - starting idle connection resiliency reconnect."); } sessionRecovery.reconnect(newCommand); @@ -4182,8 +4209,8 @@ boolean executeCommand(TDSCommand newCommand) throws SQLServerException { if (sessionRecovery.getReconnectException() != null) { if (loggerResiliency.isLoggable(Level.FINER)) { - loggerResiliency.finer( - this.toString() + " Idle connection resiliency - connection is broken and recovery is not possible."); + loggerResiliency.finer(this.toString() + + " Idle connection resiliency - connection is broken and recovery is not possible."); } throw sessionRecovery.getReconnectException(); } @@ -7338,7 +7365,7 @@ public T unwrap(Class iface) throws SQLException { /** original delayLoadingLobs */ private boolean originalDelayLoadingLobs; - + /** original ignoreOffsetOnDateTimeOffsetConversion */ private boolean originalIgnoreOffsetOnDateTimeOffsetConversion; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java index ca9e773df..60974e2f2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java @@ -696,7 +696,7 @@ public void setAccessTokenCallbackClass(String accessTokenCallbackClass) { * Returns the current value for 'calcBigDecimalScale'. * * @return calcBigDecimalScale - * a boolean + * a boolean */ @Override public boolean getCalcBigDecimalScale() { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java index a24e5f514..2ea5df433 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java @@ -241,23 +241,26 @@ public GSSCredential getGSSCredentials() { @Override public void setUseDefaultGSSCredential(boolean enable) { - setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(), enable); + setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(), + enable); } @Override public boolean getUseDefaultGSSCredential() { - return getBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(), - SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.getDefaultValue()); + return getBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(), + SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.getDefaultValue()); } @Override public void setUseFlexibleCallableStatements(boolean enable) { - setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.toString(), enable); + setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.toString(), + enable); } @Override public boolean getUseFlexibleCallableStatements() { - return getBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.toString(), + return getBooleanProperty(connectionProps, + SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.toString(), SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.getDefaultValue()); } @@ -1341,7 +1344,7 @@ public String getAccessTokenCallbackClass() { * Sets the 'calcBigDecimalScale' setting. * * @param calcBigDecimalScale - * boolean property to have the driver calculate a big decimal's scale from input + * boolean property to have the driver calculate a big decimal's scale from input */ @Override public void setCalcBigDecimalScale(boolean calcBigDecimalScale) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java index 8141f5383..1d2868879 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java @@ -1722,7 +1722,7 @@ public String getUserName() throws SQLServerException, SQLTimeoutException { loggerExternal.finest(toString() + " Impersonation context is not supported in this version of SQL Server. Re-try getting CURRENT_USER"); } - + try (SQLServerResultSet rs = s.executeQueryInternal("SELECT CURRENT_USER")) { boolean next = rs.next(); assert next; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index 727e165b1..4044a4ac9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -773,11 +773,11 @@ public final class SQLServerDriver implements java.sql.Driver { Boolean.toString(SQLServerDriverBooleanProperty.INTEGRATED_SECURITY.getDefaultValue()), false, TRUE_FALSE), new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(), - Boolean.toString(SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.getDefaultValue()), false, - TRUE_FALSE), + Boolean.toString(SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.getDefaultValue()), + false, TRUE_FALSE), new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.toString(), - Boolean.toString(SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.getDefaultValue()), false, - TRUE_FALSE), + Boolean.toString(SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.getDefaultValue()), + false, TRUE_FALSE), new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.KEY_STORE_AUTHENTICATION.toString(), SQLServerDriverStringProperty.KEY_STORE_AUTHENTICATION.getDefaultValue(), false, new String[] {KeyStoreAuthentication.JAVA_KEYSTORE_PASSWORD.toString()}), diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java index e7af0f39c..3e2cf8a34 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java @@ -24,7 +24,6 @@ private DriverJDBCVersion() { throw new UnsupportedOperationException(SQLServerException.getErrString("R_notSupported")); } - static final boolean checkSupportsJDBC43() { return false; } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index 7b42fde5c..fff271b2a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -4918,8 +4918,7 @@ private String getUpdatedColumnTableName() throws SQLServerException { for (Column column : columns) { if (column.hasUpdates() && columnTableName.isEmpty()) { columnTableName = column.getTableName().asEscapedString(); - } else if (column.hasUpdates() - && !columnTableName.equals(column.getTableName().asEscapedString())) { + } else if (column.hasUpdates() && !columnTableName.equals(column.getTableName().asEscapedString())) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AmbiguousRowUpdate")); Object[] msgArgs = {columnTableName, column.getTableName().asEscapedString()}; SQLServerException.makeFromDriverError(stmt.connection, stmt, form.format(msgArgs), null, false); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java index 2fe360514..6a8057298 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -1629,8 +1629,8 @@ boolean onRetStatus(TDSReader tdsReader) throws SQLServerException { // Only read the return value from stored procedure if we are expecting one. Also, check that it is // not cursorable and not TVP type. For these two, the driver is still following the old behavior of // executing sp_executesql for stored procedures. - if (!isCursorable(executeMethod) && !isTVPType && null != inOutParam - && inOutParam.length > 0 && inOutParam[0].isReturnValue()) { + if (!isCursorable(executeMethod) && !isTVPType && null != inOutParam && inOutParam.length > 0 + && inOutParam[0].isReturnValue()) { inOutParam[0].setFromReturnStatus(procedureRetStatToken.getStatus(), connection); return false; } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSymmetricKeyCache.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSymmetricKeyCache.java index 76967861c..b95a494e2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSymmetricKeyCache.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSymmetricKeyCache.java @@ -73,13 +73,12 @@ SQLServerSymmetricKey getKey(EncryptionKeyInfo keyInfo, SQLServerConnection conn Boolean[] hasEntry = new Boolean[1]; List trustedKeyPaths = SQLServerConnection.getColumnEncryptionTrustedMasterKeyPaths(serverName, hasEntry); - if (hasEntry[0] && - ((null == trustedKeyPaths) || (trustedKeyPaths.isEmpty()) - || (!trustedKeyPaths.contains(keyInfo.keyPath)))) { + if (hasEntry[0] && ((null == trustedKeyPaths) || (trustedKeyPaths.isEmpty()) + || (!trustedKeyPaths.contains(keyInfo.keyPath)))) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UntrustedKeyPath")); - Object[] msgArgs = {keyInfo.keyPath, serverName}; - throw new SQLServerException(this, form.format(msgArgs), null, 0, false); - } + Object[] msgArgs = {keyInfo.keyPath, serverName}; + throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + } if (aeLogger.isLoggable(java.util.logging.Level.FINE)) { aeLogger.fine("Checking Symmetric key cache..."); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SqlAuthenticationToken.java b/src/main/java/com/microsoft/sqlserver/jdbc/SqlAuthenticationToken.java index 6c4420919..c57cd2268 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SqlAuthenticationToken.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SqlAuthenticationToken.java @@ -23,7 +23,6 @@ public class SqlAuthenticationToken implements Serializable { /** The access token string. **/ private final String accessToken; - /** * Contructs a SqlAuthentication token. * diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dataclassification/SensitivityClassification.java b/src/main/java/com/microsoft/sqlserver/jdbc/dataclassification/SensitivityClassification.java index 2210d2713..0b93b5534 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dataclassification/SensitivityClassification.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dataclassification/SensitivityClassification.java @@ -28,27 +28,27 @@ public enum SensitivityRank { * rank not defined */ NOT_DEFINED(-1), - + /** * rank NONE */ NONE(0), - + /** * rank LOW */ LOW(10), - + /** * rank MEDIUM */ MEDIUM(20), - + /** * rank HIGH */ HIGH(30), - + /** * rank CRITICAL */ diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java index a909fb75b..4a79791bd 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java @@ -9,6 +9,7 @@ import javax.naming.NameNotFoundException; import javax.naming.NamingException; + /** * Represents a DNS Kerberos Locator */ @@ -38,7 +39,7 @@ public static boolean isRealmValid(String realmName) throws NamingException { Set records = DNSUtilities.findSrvRecords("_kerberos._udp." + realmName); return !records.isEmpty(); } catch (NameNotFoundException wrongDomainException) { - // config error - domain controller cannot be located via DNS + // config error - domain controller cannot be located via DNS return false; } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java index 1be900a36..c03d67eb0 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java @@ -851,18 +851,17 @@ private void sendTemporal(DTV dtv, JavaType javaType, Object value) throws SQLSe tdsWriter.writeEncryptedRPCDateTime(name, timestampNormalizedCalendar(calendar, javaType, conn.baseYear()), subSecondNanos, isOutParam, jdbcType, statement); - } else { - if (conn.getDatetimeParameterType().equals(DatetimeType.DATETIME2.toString())) { - tdsWriter.writeRPCDateTime2(name, - timestampNormalizedCalendar(calendar, javaType, conn.baseYear()), - subSecondNanos, TDS.DEFAULT_FRACTIONAL_SECONDS_SCALE, isOutParam); - } else if (conn.getDatetimeParameterType() - .equals(DatetimeType.DATETIME.toString())) { - tdsWriter.writeRPCDateTime(name, - timestampNormalizedCalendar(calendar, javaType, conn.baseYear()), - subSecondNanos, isOutParam); - } + } else { + if (conn.getDatetimeParameterType().equals(DatetimeType.DATETIME2.toString())) { + tdsWriter.writeRPCDateTime2(name, + timestampNormalizedCalendar(calendar, javaType, conn.baseYear()), + subSecondNanos, TDS.DEFAULT_FRACTIONAL_SECONDS_SCALE, isOutParam); + } else if (conn.getDatetimeParameterType().equals(DatetimeType.DATETIME.toString())) { + tdsWriter.writeRPCDateTime(name, + timestampNormalizedCalendar(calendar, javaType, conn.baseYear()), + subSecondNanos, isOutParam); } + } break; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/osgi/Activator.java b/src/main/java/com/microsoft/sqlserver/jdbc/osgi/Activator.java index df29400ee..a0395ae6c 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/osgi/Activator.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/osgi/Activator.java @@ -38,12 +38,11 @@ public void start(BundleContext context) throws Exception { properties.put(DataSourceFactory.OSGI_JDBC_DRIVER_NAME, "Microsoft JDBC Driver for SQL Server"); properties.put(DataSourceFactory.OSGI_JDBC_DRIVER_VERSION, driver.getMajorVersion() + "." + driver.getMinorVersion()); - properties.put(DataSourceFactory.OSGI_JDBC_CAPABILITY, new String[] { - DataSourceFactory.OSGI_JDBC_CAPABILITY_DRIVER, - DataSourceFactory.OSGI_JDBC_CAPABILITY_DATASOURCE, - DataSourceFactory.OSGI_JDBC_CAPABILITY_CONNECTIONPOOLDATASOURCE, - DataSourceFactory.OSGI_JDBC_CAPABILITY_XADATASOURCE - }); + properties.put(DataSourceFactory.OSGI_JDBC_CAPABILITY, + new String[] {DataSourceFactory.OSGI_JDBC_CAPABILITY_DRIVER, + DataSourceFactory.OSGI_JDBC_CAPABILITY_DATASOURCE, + DataSourceFactory.OSGI_JDBC_CAPABILITY_CONNECTIONPOOLDATASOURCE, + DataSourceFactory.OSGI_JDBC_CAPABILITY_XADATASOURCE}); service = context.registerService(DataSourceFactory.class, new SQLServerDataSourceFactory(), properties); SQLServerDriver.register(); } diff --git a/src/main/java/mssql/googlecode/cityhash/CityHash.java b/src/main/java/mssql/googlecode/cityhash/CityHash.java index 2585916e4..7ae9e351d 100644 --- a/src/main/java/mssql/googlecode/cityhash/CityHash.java +++ b/src/main/java/mssql/googlecode/cityhash/CityHash.java @@ -24,34 +24,30 @@ public final class CityHash { private static final long k2 = 0x9ae16a3b2f90404fL; private static final long k3 = 0xc949d7c7509e6557L; - private static long toLongLE(byte[] b, - int i) { - return (((long) b[i + 7] << 56) + ((long) (b[i + 6] & 255) << 48) + ((long) (b[i + 5] & 255) << 40) + ((long) (b[i + 4] & 255) << 32) - + ((long) (b[i + 3] & 255) << 24) + ((b[i + 2] & 255) << 16) + ((b[i + 1] & 255) << 8) + ((b[i + 0] & 255) << 0)); + private static long toLongLE(byte[] b, int i) { + return (((long) b[i + 7] << 56) + ((long) (b[i + 6] & 255) << 48) + ((long) (b[i + 5] & 255) << 40) + + ((long) (b[i + 4] & 255) << 32) + ((long) (b[i + 3] & 255) << 24) + ((b[i + 2] & 255) << 16) + + ((b[i + 1] & 255) << 8) + ((b[i + 0] & 255) << 0)); } - private static int toIntLE(byte[] b, - int i) { - return (((b[i + 3] & 255) << 24) + ((b[i + 2] & 255) << 16) + ((b[i + 1] & 255) << 8) + ((b[i + 0] & 255) << 0)); + private static int toIntLE(byte[] b, int i) { + return (((b[i + 3] & 255) << 24) + ((b[i + 2] & 255) << 16) + ((b[i + 1] & 255) << 8) + + ((b[i + 0] & 255) << 0)); } - private static long fetch64(byte[] s, - int pos) { + private static long fetch64(byte[] s, int pos) { return toLongLE(s, pos); } - private static int fetch32(byte[] s, - int pos) { + private static int fetch32(byte[] s, int pos) { return toIntLE(s, pos); } - private static long rotate(long val, - int shift) { + private static long rotate(long val, int shift) { return shift == 0 ? val : (val >>> shift) | (val << (64 - shift)); } - private static long rotateByAtLeast1(long val, - int shift) { + private static long rotateByAtLeast1(long val, int shift) { return (val >>> shift) | (val << (64 - shift)); } @@ -61,8 +57,7 @@ private static long shiftMix(long val) { private static final long kMul = 0x9ddfea08eb382d69L; - private static long hash128to64(long u, - long v) { + private static long hash128to64(long u, long v) { long a = (u ^ v) * kMul; a ^= (a >>> 47); long b = (v ^ a) * kMul; @@ -71,14 +66,11 @@ private static long hash128to64(long u, return b; } - private static long hashLen16(long u, - long v) { + private static long hashLen16(long u, long v) { return hash128to64(u, v); } - private static long hashLen0to16(byte[] s, - int pos, - int len) { + private static long hashLen0to16(byte[] s, int pos, int len) { if (len > 8) { long a = fetch64(s, pos + 0); long b = fetch64(s, pos + len - 8); @@ -99,9 +91,7 @@ private static long hashLen0to16(byte[] s, return k2; } - private static long hashLen17to32(byte[] s, - int pos, - int len) { + private static long hashLen17to32(byte[] s, int pos, int len) { long a = fetch64(s, pos + 0) * k1; long b = fetch64(s, pos + 8); long c = fetch64(s, pos + len - 8) * k2; @@ -109,12 +99,7 @@ private static long hashLen17to32(byte[] s, return hashLen16(rotate(a - b, 43) + rotate(c, 30) + d, a + rotate(b ^ k3, 20) - c + len); } - private static long[] weakHashLen32WithSeeds(long w, - long x, - long y, - long z, - long a, - long b) { + private static long[] weakHashLen32WithSeeds(long w, long x, long y, long z, long a, long b) { a += w; b = rotate(b + a + z, 21); @@ -125,16 +110,12 @@ private static long[] weakHashLen32WithSeeds(long w, return new long[] {a + z, b + c}; } - private static long[] weakHashLen32WithSeeds(byte[] s, - int pos, - long a, - long b) { - return weakHashLen32WithSeeds(fetch64(s, pos + 0), fetch64(s, pos + 8), fetch64(s, pos + 16), fetch64(s, pos + 24), a, b); + private static long[] weakHashLen32WithSeeds(byte[] s, int pos, long a, long b) { + return weakHashLen32WithSeeds(fetch64(s, pos + 0), fetch64(s, pos + 8), fetch64(s, pos + 16), + fetch64(s, pos + 24), a, b); } - private static long hashLen33to64(byte[] s, - int pos, - int len) { + private static long hashLen33to64(byte[] s, int pos, int len) { long z = fetch64(s, pos + 24); long a = fetch64(s, pos + 0) + (fetch64(s, pos + len - 16) + len) * k0; @@ -164,19 +145,15 @@ private static long hashLen33to64(byte[] s, } - static long cityHash64(byte[] s, - int pos, - int len) { + static long cityHash64(byte[] s, int pos, int len) { if (len <= 32) { if (len <= 16) { return hashLen0to16(s, pos, len); - } - else { + } else { return hashLen17to32(s, pos, len); } - } - else if (len <= 64) { + } else if (len <= 64) { return hashLen33to64(s, pos, len); } @@ -204,33 +181,21 @@ else if (len <= 64) { } pos += 64; len -= 64; - } - while (len != 0); + } while (len != 0); return hashLen16(hashLen16(v[0], w[0]) + shiftMix(y) * k1 + z, hashLen16(v[1], w[1]) + x); } - static long cityHash64WithSeed(byte[] s, - int pos, - int len, - long seed) { + static long cityHash64WithSeed(byte[] s, int pos, int len, long seed) { return cityHash64WithSeeds(s, pos, len, k2, seed); } - static long cityHash64WithSeeds(byte[] s, - int pos, - int len, - long seed0, - long seed1) { + static long cityHash64WithSeeds(byte[] s, int pos, int len, long seed0, long seed1) { return hashLen16(cityHash64(s, pos, len) - seed0, seed1); } - static long[] cityMurmur(byte[] s, - int pos, - int len, - long seed0, - long seed1) { + static long[] cityMurmur(byte[] s, int pos, int len, long seed0, long seed1) { long a = seed0; long b = seed1; @@ -242,8 +207,7 @@ static long[] cityMurmur(byte[] s, a = shiftMix(a * k1) * k1; c = b * k1 + hashLen0to16(s, pos, len); d = shiftMix(a + (len >= 8 ? fetch64(s, pos + 0) : c)); - } - else { + } else { c = hashLen16(fetch64(s, pos + len - 8) + k1, a); d = hashLen16(b + len, c + fetch64(s, pos + len - 16)); @@ -258,8 +222,7 @@ static long[] cityMurmur(byte[] s, d ^= c; pos += 16; l -= 16; - } - while (l > 0); + } while (l > 0); } a = hashLen16(a, c); @@ -269,11 +232,7 @@ static long[] cityMurmur(byte[] s, } - static long[] cityHash128WithSeed(byte[] s, - int pos, - int len, - long seed0, - long seed1) { + static long[] cityHash128WithSeed(byte[] s, int pos, int len, long seed0, long seed1) { if (len < 128) { return cityMurmur(s, pos, len, seed0, seed1); @@ -318,8 +277,7 @@ static long[] cityHash128WithSeed(byte[] s, } pos += 64; len -= 128; - } - while (len >= 128); + } while (len >= 128); x += rotate(v[0] + z, 49) * k0; z += rotate(w[0], 37) * k0; @@ -341,17 +299,14 @@ static long[] cityHash128WithSeed(byte[] s, } - public static long[] cityHash128(byte[] s, - int pos, - int len) { + public static long[] cityHash128(byte[] s, int pos, int len) { if (len >= 16) { return cityHash128WithSeed(s, pos + 16, len - 16, fetch64(s, pos + 0) ^ k3, fetch64(s, pos + 8)); - } - else if (len >= 8) { - return cityHash128WithSeed(new byte[0], 0, 0, fetch64(s, pos + 0) ^ (len * k0), fetch64(s, pos + len - 8) ^ k1); - } - else { + } else if (len >= 8) { + return cityHash128WithSeed(new byte[0], 0, 0, fetch64(s, pos + 0) ^ (len * k0), + fetch64(s, pos + len - 8) ^ k1); + } else { return cityHash128WithSeed(s, pos, len, k0, k1); } } diff --git a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java index b213621e7..3da8eb83d 100644 --- a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java +++ b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java @@ -1,12 +1,9 @@ /* * Copyright 2010 Google Inc. All Rights Reserved. - * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -45,6 +42,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; + /** * A hash table supporting full concurrency of retrievals, adjustable expected * concurrency for updates, and a maximum capacity to bound the map by. This @@ -91,1493 +89,1541 @@ * obtained in ascending and descending order of retention. * * @author ben.manes@gmail.com (Ben Manes) - * @param the type of keys maintained by this map - * @param the type of mapped values + * @param + * the type of keys maintained by this map + * @param + * the type of mapped values * @see * http://code.google.com/p/concurrentlinkedhashmap/ */ @SuppressWarnings("deprecation") public final class ConcurrentLinkedHashMap extends AbstractMap - implements ConcurrentMap, Serializable { - - /* - * This class performs a best-effort bounding of a ConcurrentHashMap using a - * page-replacement algorithm to determine which entries to evict when the - * capacity is exceeded. - * - * The page replacement algorithm's data structures are kept eventually - * consistent with the map. An update to the map and recording of reads may - * not be immediately reflected on the algorithm's data structures. These - * structures are guarded by a lock and operations are applied in batches to - * avoid lock contention. The penalty of applying the batches is spread across - * threads so that the amortized cost is slightly higher than performing just - * the ConcurrentHashMap operation. - * - * A memento of the reads and writes that were performed on the map are - * recorded in buffers. These buffers are drained at the first opportunity - * after a write or when the read buffer exceeds a threshold size. The reads - * are recorded in a lossy buffer, allowing the reordering operations to be - * discarded if the draining process cannot keep up. Due to the concurrent - * nature of the read and write operations a strict policy ordering is not - * possible, but is observably strict when single threaded. - * - * Due to a lack of a strict ordering guarantee, a task can be executed - * out-of-order, such as a removal followed by its addition. The state of the - * entry is encoded within the value's weight. - * - * Alive: The entry is in both the hash-table and the page replacement policy. - * This is represented by a positive weight. - * - * Retired: The entry is not in the hash-table and is pending removal from the - * page replacement policy. This is represented by a negative weight. - * - * Dead: The entry is not in the hash-table and is not in the page replacement - * policy. This is represented by a weight of zero. - * - * The Least Recently Used page replacement algorithm was chosen due to its - * simplicity, high hit rate, and ability to be implemented with O(1) time - * complexity. - */ - - /** The number of CPUs */ - static final int NCPU = Runtime.getRuntime().availableProcessors(); - - /** The maximum weighted capacity of the map. */ - static final long MAXIMUM_CAPACITY = Long.MAX_VALUE - Integer.MAX_VALUE; - - /** The number of read buffers to use. */ - static final int NUMBER_OF_READ_BUFFERS = ceilingNextPowerOfTwo(NCPU); - - /** Mask value for indexing into the read buffers. */ - static final int READ_BUFFERS_MASK = NUMBER_OF_READ_BUFFERS - 1; - - /** The number of pending read operations before attempting to drain. */ - static final int READ_BUFFER_THRESHOLD = 32; - - /** The maximum number of read operations to perform per amortized drain. */ - static final int READ_BUFFER_DRAIN_THRESHOLD = 2 * READ_BUFFER_THRESHOLD; - - /** The maximum number of pending reads per buffer. */ - static final int READ_BUFFER_SIZE = 2 * READ_BUFFER_DRAIN_THRESHOLD; - - /** Mask value for indexing into the read buffer. */ - static final int READ_BUFFER_INDEX_MASK = READ_BUFFER_SIZE - 1; - - /** The maximum number of write operations to perform per amortized drain. */ - static final int WRITE_BUFFER_DRAIN_THRESHOLD = 16; - - /** A queue that discards all entries. */ - static final Queue DISCARDING_QUEUE = new DiscardingQueue(); - - static int ceilingNextPowerOfTwo(int x) { - // From Hacker's Delight, Chapter 3, Harry S. Warren Jr. - return 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(x - 1)); - } - - // The backing data store holding the key-value associations - final ConcurrentMap> data; - final int concurrencyLevel; - - // These fields provide support to bound the map by a maximum capacity - final long[] readBufferReadCount; - final LinkedDeque> evictionDeque; - - final AtomicLong weightedSize; - final AtomicLong capacity; - - final Lock evictionLock; - final Queue writeBuffer; - final AtomicLong[] readBufferWriteCount; - final AtomicLong[] readBufferDrainAtWriteCount; - final AtomicReference>[][] readBuffers; - - final AtomicReference drainStatus; - final EntryWeigher weigher; - - // These fields provide support for notifying a listener. - final Queue> pendingNotifications; - final EvictionListener listener; - - transient Set keySet; - transient Collection values; - transient Set> entrySet; - - /** - * Creates an instance based on the builder's configuration. - */ - @SuppressWarnings({"unchecked", "cast"}) - private ConcurrentLinkedHashMap(Builder builder) { - // The data store and its maximum capacity - concurrencyLevel = builder.concurrencyLevel; - capacity = new AtomicLong(Math.min(builder.capacity, MAXIMUM_CAPACITY)); - data = new ConcurrentHashMap>(builder.initialCapacity, 0.75f, concurrencyLevel); - - // The eviction support - weigher = builder.weigher; - evictionLock = new ReentrantLock(); - weightedSize = new AtomicLong(); - evictionDeque = new LinkedDeque>(); - writeBuffer = new ConcurrentLinkedQueue(); - drainStatus = new AtomicReference(IDLE); - - readBufferReadCount = new long[NUMBER_OF_READ_BUFFERS]; - readBufferWriteCount = new AtomicLong[NUMBER_OF_READ_BUFFERS]; - readBufferDrainAtWriteCount = new AtomicLong[NUMBER_OF_READ_BUFFERS]; - readBuffers = new AtomicReference[NUMBER_OF_READ_BUFFERS][READ_BUFFER_SIZE]; - for (int i = 0; i < NUMBER_OF_READ_BUFFERS; i++) { - readBufferWriteCount[i] = new AtomicLong(); - readBufferDrainAtWriteCount[i] = new AtomicLong(); - readBuffers[i] = new AtomicReference[READ_BUFFER_SIZE]; - for (int j = 0; j < READ_BUFFER_SIZE; j++) { - readBuffers[i][j] = new AtomicReference>(); - } - } - - // The notification queue and listener - listener = builder.listener; - pendingNotifications = (listener == DiscardingListener.INSTANCE) - ? (Queue>) DISCARDING_QUEUE - : new ConcurrentLinkedQueue>(); - } - - /** Ensures that the object is not null. */ - static void checkNotNull(Object o) { - if (o == null) { - throw new NullPointerException(); - } - } - - /** Ensures that the argument expression is true. */ - static void checkArgument(boolean expression) { - if (!expression) { - throw new IllegalArgumentException(); - } - } - - /** Ensures that the state expression is true. */ - static void checkState(boolean expression) { - if (!expression) { - throw new IllegalStateException(); - } - } - - /* ---------------- Eviction Support -------------- */ - - /** - * Retrieves the maximum weighted capacity of the map. - * - * @return the maximum weighted capacity - */ - public long capacity() { - return capacity.get(); - } - - /** - * Sets the maximum weighted capacity of the map and eagerly evicts entries - * until it shrinks to the appropriate size. - * - * @param capacity the maximum weighted capacity of the map - * @throws IllegalArgumentException if the capacity is negative - */ - public void setCapacity(long capacity) { - checkArgument(capacity >= 0); - evictionLock.lock(); - try { - this.capacity.lazySet(Math.min(capacity, MAXIMUM_CAPACITY)); - drainBuffers(); - evict(); - } finally { - evictionLock.unlock(); - } - notifyListener(); - } - - /** Determines whether the map has exceeded its capacity. */ - boolean hasOverflowed() { - return weightedSize.get() > capacity.get(); - } - - /** - * Evicts entries from the map while it exceeds the capacity and appends - * evicted entries to the notification queue for processing. - */ - void evict() { - // Attempts to evict entries from the map if it exceeds the maximum - // capacity. If the eviction fails due to a concurrent removal of the - // victim, that removal may cancel out the addition that triggered this - // eviction. The victim is eagerly unlinked before the removal task so - // that if an eviction is still required then a new victim will be chosen - // for removal. - while (hasOverflowed()) { - final Node node = evictionDeque.poll(); - - // If weighted values are used, then the pending operations will adjust - // the size to reflect the correct weight - if (node == null) { - return; - } - - // Notify the listener only if the entry was evicted - if (data.remove(node.key, node)) { - pendingNotifications.add(node); - } - - makeDead(node); - } - } - - /** - * Performs the post-processing work required after a read. - * - * @param node the entry in the page replacement policy - */ - void afterRead(Node node) { - final int bufferIndex = readBufferIndex(); - final long writeCount = recordRead(bufferIndex, node); - drainOnReadIfNeeded(bufferIndex, writeCount); - notifyListener(); - } - - /** Returns the index to the read buffer to record into. */ - static int readBufferIndex() { - // A buffer is chosen by the thread's id so that tasks are distributed in a - // pseudo evenly manner. This helps avoid hot entries causing contention - // due to other threads trying to append to the same buffer. - return ((int) Thread.currentThread().getId()) & READ_BUFFERS_MASK; - } - - /** - * Records a read in the buffer and return its write count. - * - * @param bufferIndex the index to the chosen read buffer - * @param node the entry in the page replacement policy - * @return the number of writes on the chosen read buffer - */ - long recordRead(int bufferIndex, Node node) { - // The location in the buffer is chosen in a racy fashion as the increment - // is not atomic with the insertion. This means that concurrent reads can - // overlap and overwrite one another, resulting in a lossy buffer. - final AtomicLong counter = readBufferWriteCount[bufferIndex]; - final long writeCount = counter.get(); - counter.lazySet(writeCount + 1); - - final int index = (int) (writeCount & READ_BUFFER_INDEX_MASK); - readBuffers[bufferIndex][index].lazySet(node); - - return writeCount; - } - - /** - * Attempts to drain the buffers if it is determined to be needed when - * post-processing a read. - * - * @param bufferIndex the index to the chosen read buffer - * @param writeCount the number of writes on the chosen read buffer - */ - void drainOnReadIfNeeded(int bufferIndex, long writeCount) { - final long pending = (writeCount - readBufferDrainAtWriteCount[bufferIndex].get()); - final boolean delayable = (pending < READ_BUFFER_THRESHOLD); - final DrainStatus status = drainStatus.get(); - if (status.shouldDrainBuffers(delayable)) { - tryToDrainBuffers(); - } - } - - /** - * Performs the post-processing work required after a write. - * - * @param task the pending operation to be applied - */ - void afterWrite(Runnable task) { - writeBuffer.add(task); - drainStatus.lazySet(REQUIRED); - tryToDrainBuffers(); - notifyListener(); - } - - /** - * Attempts to acquire the eviction lock and apply the pending operations, up - * to the amortized threshold, to the page replacement policy. - */ - void tryToDrainBuffers() { - if (evictionLock.tryLock()) { - try { - drainStatus.lazySet(PROCESSING); - drainBuffers(); - } finally { - drainStatus.compareAndSet(PROCESSING, IDLE); - evictionLock.unlock(); - } - } - } - - /** Drains the read and write buffers up to an amortized threshold. */ - void drainBuffers() { - drainReadBuffers(); - drainWriteBuffer(); - } - - /** Drains the read buffers, each up to an amortized threshold. */ - void drainReadBuffers() { - final int start = (int) Thread.currentThread().getId(); - final int end = start + NUMBER_OF_READ_BUFFERS; - for (int i = start; i < end; i++) { - drainReadBuffer(i & READ_BUFFERS_MASK); - } - } - - /** Drains the read buffer up to an amortized threshold. */ - void drainReadBuffer(int bufferIndex) { - final long writeCount = readBufferWriteCount[bufferIndex].get(); - for (int i = 0; i < READ_BUFFER_DRAIN_THRESHOLD; i++) { - final int index = (int) (readBufferReadCount[bufferIndex] & READ_BUFFER_INDEX_MASK); - final AtomicReference> slot = readBuffers[bufferIndex][index]; - final Node node = slot.get(); - if (node == null) { - break; - } - - slot.lazySet(null); - applyRead(node); - readBufferReadCount[bufferIndex]++; - } - readBufferDrainAtWriteCount[bufferIndex].lazySet(writeCount); - } - - /** Updates the node's location in the page replacement policy. */ - void applyRead(Node node) { - // An entry may be scheduled for reordering despite having been removed. - // This can occur when the entry was concurrently read while a writer was - // removing it. If the entry is no longer linked then it does not need to - // be processed. - if (evictionDeque.contains(node)) { - evictionDeque.moveToBack(node); - } - } - - /** Drains the read buffer up to an amortized threshold. */ - void drainWriteBuffer() { - for (int i = 0; i < WRITE_BUFFER_DRAIN_THRESHOLD; i++) { - final Runnable task = writeBuffer.poll(); - if (task == null) { - break; - } - task.run(); - } - } - - /** - * Attempts to transition the node from the alive state to the - * retired state. - * - * @param node the entry in the page replacement policy - * @param expect the expected weighted value - * @return if successful - */ - boolean tryToRetire(Node node, WeightedValue expect) { - if (expect.isAlive()) { - final WeightedValue retired = new WeightedValue(expect.value, -expect.weight); - return node.compareAndSet(expect, retired); - } - return false; - } - - /** - * Atomically transitions the node from the alive state to the - * retired state, if a valid transition. - * - * @param node the entry in the page replacement policy - */ - void makeRetired(Node node) { - for (;;) { - final WeightedValue current = node.get(); - if (!current.isAlive()) { - return; - } - final WeightedValue retired = new WeightedValue(current.value, -current.weight); - if (node.compareAndSet(current, retired)) { - return; - } - } - } - - /** - * Atomically transitions the node to the dead state and decrements - * the weightedSize. - * - * @param node the entry in the page replacement policy - */ - void makeDead(Node node) { - for (;;) { - WeightedValue current = node.get(); - WeightedValue dead = new WeightedValue(current.value, 0); - if (node.compareAndSet(current, dead)) { - weightedSize.lazySet(weightedSize.get() - Math.abs(current.weight)); - return; - } - } - } - - /** Notifies the listener of entries that were evicted. */ - void notifyListener() { - Node node; - while ((node = pendingNotifications.poll()) != null) { - listener.onEviction(node.key, node.getValue()); - } - } - - /** Adds the node to the page replacement policy. */ - final class AddTask implements Runnable { - final Node node; - final int weight; - - AddTask(Node node, int weight) { - this.weight = weight; - this.node = node; - } + implements ConcurrentMap, Serializable { + + /* + * This class performs a best-effort bounding of a ConcurrentHashMap using a + * page-replacement algorithm to determine which entries to evict when the + * capacity is exceeded. + * The page replacement algorithm's data structures are kept eventually + * consistent with the map. An update to the map and recording of reads may + * not be immediately reflected on the algorithm's data structures. These + * structures are guarded by a lock and operations are applied in batches to + * avoid lock contention. The penalty of applying the batches is spread across + * threads so that the amortized cost is slightly higher than performing just + * the ConcurrentHashMap operation. + * A memento of the reads and writes that were performed on the map are + * recorded in buffers. These buffers are drained at the first opportunity + * after a write or when the read buffer exceeds a threshold size. The reads + * are recorded in a lossy buffer, allowing the reordering operations to be + * discarded if the draining process cannot keep up. Due to the concurrent + * nature of the read and write operations a strict policy ordering is not + * possible, but is observably strict when single threaded. + * Due to a lack of a strict ordering guarantee, a task can be executed + * out-of-order, such as a removal followed by its addition. The state of the + * entry is encoded within the value's weight. + * Alive: The entry is in both the hash-table and the page replacement policy. + * This is represented by a positive weight. + * Retired: The entry is not in the hash-table and is pending removal from the + * page replacement policy. This is represented by a negative weight. + * Dead: The entry is not in the hash-table and is not in the page replacement + * policy. This is represented by a weight of zero. + * The Least Recently Used page replacement algorithm was chosen due to its + * simplicity, high hit rate, and ability to be implemented with O(1) time + * complexity. + */ - @Override - public void run() { - weightedSize.lazySet(weightedSize.get() + weight); + /** The number of CPUs */ + static final int NCPU = Runtime.getRuntime().availableProcessors(); + + /** The maximum weighted capacity of the map. */ + static final long MAXIMUM_CAPACITY = Long.MAX_VALUE - Integer.MAX_VALUE; + + /** The number of read buffers to use. */ + static final int NUMBER_OF_READ_BUFFERS = ceilingNextPowerOfTwo(NCPU); + + /** Mask value for indexing into the read buffers. */ + static final int READ_BUFFERS_MASK = NUMBER_OF_READ_BUFFERS - 1; + + /** The number of pending read operations before attempting to drain. */ + static final int READ_BUFFER_THRESHOLD = 32; - // ignore out-of-order write operations - if (node.get().isAlive()) { - evictionDeque.add(node); - evict(); - } + /** The maximum number of read operations to perform per amortized drain. */ + static final int READ_BUFFER_DRAIN_THRESHOLD = 2 * READ_BUFFER_THRESHOLD; + + /** The maximum number of pending reads per buffer. */ + static final int READ_BUFFER_SIZE = 2 * READ_BUFFER_DRAIN_THRESHOLD; + + /** Mask value for indexing into the read buffer. */ + static final int READ_BUFFER_INDEX_MASK = READ_BUFFER_SIZE - 1; + + /** The maximum number of write operations to perform per amortized drain. */ + static final int WRITE_BUFFER_DRAIN_THRESHOLD = 16; + + /** A queue that discards all entries. */ + static final Queue DISCARDING_QUEUE = new DiscardingQueue(); + + static int ceilingNextPowerOfTwo(int x) { + // From Hacker's Delight, Chapter 3, Harry S. Warren Jr. + return 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(x - 1)); } - } - /** Removes a node from the page replacement policy. */ - final class RemovalTask implements Runnable { - final Node node; + // The backing data store holding the key-value associations + final ConcurrentMap> data; + final int concurrencyLevel; + + // These fields provide support to bound the map by a maximum capacity + final long[] readBufferReadCount; + final LinkedDeque> evictionDeque; + + final AtomicLong weightedSize; + final AtomicLong capacity; - RemovalTask(Node node) { - this.node = node; + final Lock evictionLock; + final Queue writeBuffer; + final AtomicLong[] readBufferWriteCount; + final AtomicLong[] readBufferDrainAtWriteCount; + final AtomicReference>[][] readBuffers; + + final AtomicReference drainStatus; + final EntryWeigher weigher; + + // These fields provide support for notifying a listener. + final Queue> pendingNotifications; + final EvictionListener listener; + + transient Set keySet; + transient Collection values; + transient Set> entrySet; + + /** + * Creates an instance based on the builder's configuration. + */ + @SuppressWarnings({"unchecked", "cast"}) + private ConcurrentLinkedHashMap(Builder builder) { + // The data store and its maximum capacity + concurrencyLevel = builder.concurrencyLevel; + capacity = new AtomicLong(Math.min(builder.capacity, MAXIMUM_CAPACITY)); + data = new ConcurrentHashMap>(builder.initialCapacity, 0.75f, concurrencyLevel); + + // The eviction support + weigher = builder.weigher; + evictionLock = new ReentrantLock(); + weightedSize = new AtomicLong(); + evictionDeque = new LinkedDeque>(); + writeBuffer = new ConcurrentLinkedQueue(); + drainStatus = new AtomicReference(IDLE); + + readBufferReadCount = new long[NUMBER_OF_READ_BUFFERS]; + readBufferWriteCount = new AtomicLong[NUMBER_OF_READ_BUFFERS]; + readBufferDrainAtWriteCount = new AtomicLong[NUMBER_OF_READ_BUFFERS]; + readBuffers = new AtomicReference[NUMBER_OF_READ_BUFFERS][READ_BUFFER_SIZE]; + for (int i = 0; i < NUMBER_OF_READ_BUFFERS; i++) { + readBufferWriteCount[i] = new AtomicLong(); + readBufferDrainAtWriteCount[i] = new AtomicLong(); + readBuffers[i] = new AtomicReference[READ_BUFFER_SIZE]; + for (int j = 0; j < READ_BUFFER_SIZE; j++) { + readBuffers[i][j] = new AtomicReference>(); + } + } + + // The notification queue and listener + listener = builder.listener; + pendingNotifications = (listener == DiscardingListener.INSTANCE) ? (Queue>) DISCARDING_QUEUE + : new ConcurrentLinkedQueue>(); } - @Override - public void run() { - // add may not have been processed yet - evictionDeque.remove(node); - makeDead(node); + /** Ensures that the object is not null. */ + static void checkNotNull(Object o) { + if (o == null) { + throw new NullPointerException(); + } } - } - /** Updates the weighted size and evicts an entry on overflow. */ - final class UpdateTask implements Runnable { - final int weightDifference; - final Node node; + /** Ensures that the argument expression is true. */ + static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } - public UpdateTask(Node node, int weightDifference) { - this.weightDifference = weightDifference; - this.node = node; + /** Ensures that the state expression is true. */ + static void checkState(boolean expression) { + if (!expression) { + throw new IllegalStateException(); + } } - @Override - public void run() { - weightedSize.lazySet(weightedSize.get() + weightDifference); - applyRead(node); - evict(); - } - } - - /* ---------------- Concurrent Map Support -------------- */ - - @Override - public boolean isEmpty() { - return data.isEmpty(); - } - - @Override - public int size() { - return data.size(); - } - - /** - * Returns the weighted size of this map. - * - * @return the combined weight of the values in this map - */ - public long weightedSize() { - return Math.max(0, weightedSize.get()); - } - - @Override - public void clear() { - evictionLock.lock(); - try { - // Discard all entries - Node node; - while ((node = evictionDeque.poll()) != null) { - data.remove(node.key, node); - makeDead(node); - } - - // Discard all pending reads - for (AtomicReference>[] buffer : readBuffers) { - for (AtomicReference> slot : buffer) { - slot.lazySet(null); - } - } - - // Apply all pending writes - Runnable task; - while ((task = writeBuffer.poll()) != null) { - task.run(); - } - } finally { - evictionLock.unlock(); - } - } - - @Override - public boolean containsKey(Object key) { - return data.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - checkNotNull(value); - - for (Node node : data.values()) { - if (node.getValue().equals(value)) { - return true; - } - } - return false; - } - - @Override - public V get(Object key) { - final Node node = data.get(key); - if (node == null) { - return null; - } - afterRead(node); - return node.getValue(); - } - - /** - * Returns the value to which the specified key is mapped, or {@code null} - * if this map contains no mapping for the key. This method differs from - * {@link #get(Object)} in that it does not record the operation with the - * page replacement policy. - * - * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped, or - * {@code null} if this map contains no mapping for the key - * @throws NullPointerException if the specified key is null - */ - public V getQuietly(Object key) { - final Node node = data.get(key); - return (node == null) ? null : node.getValue(); - } - - @Override - public V put(K key, V value) { - return put(key, value, false); - } - - @Override - public V putIfAbsent(K key, V value) { - return put(key, value, true); - } - - /** - * Adds a node to the list and the data store. If an existing node is found, - * then its value is updated if allowed. - * - * @param key key with which the specified value is to be associated - * @param value value to be associated with the specified key - * @param onlyIfAbsent a write is performed only if the key is not already - * associated with a value - * @return the prior value in the data store or null if no mapping was found - */ - V put(K key, V value, boolean onlyIfAbsent) { - checkNotNull(key); - checkNotNull(value); - - final int weight = weigher.weightOf(key, value); - final WeightedValue weightedValue = new WeightedValue(value, weight); - final Node node = new Node(key, weightedValue); - - for (;;) { - final Node prior = data.putIfAbsent(node.key, node); - if (prior == null) { - afterWrite(new AddTask(node, weight)); - return null; - } else if (onlyIfAbsent) { - afterRead(prior); - return prior.getValue(); - } - for (;;) { - final WeightedValue oldWeightedValue = prior.get(); - if (!oldWeightedValue.isAlive()) { - break; - } - - if (prior.compareAndSet(oldWeightedValue, weightedValue)) { - final int weightedDifference = weight - oldWeightedValue.weight; - if (weightedDifference == 0) { - afterRead(prior); - } else { - afterWrite(new UpdateTask(prior, weightedDifference)); - } - return oldWeightedValue.value; - } - } - } - } - - @Override - public V remove(Object key) { - final Node node = data.remove(key); - if (node == null) { - return null; - } - - makeRetired(node); - afterWrite(new RemovalTask(node)); - return node.getValue(); - } - - @Override - public boolean remove(Object key, Object value) { - final Node node = data.get(key); - if ((node == null) || (value == null)) { - return false; - } - - WeightedValue weightedValue = node.get(); - for (;;) { - if (weightedValue.contains(value)) { - if (tryToRetire(node, weightedValue)) { - if (data.remove(key, node)) { - afterWrite(new RemovalTask(node)); - return true; - } - } else { - weightedValue = node.get(); - if (weightedValue.isAlive()) { - // retry as an intermediate update may have replaced the value with - // an equal instance that has a different reference identity - continue; - } - } - } - return false; - } - } - - @Override - public V replace(K key, V value) { - checkNotNull(key); - checkNotNull(value); - - final int weight = weigher.weightOf(key, value); - final WeightedValue weightedValue = new WeightedValue(value, weight); - - final Node node = data.get(key); - if (node == null) { - return null; - } - for (;;) { - final WeightedValue oldWeightedValue = node.get(); - if (!oldWeightedValue.isAlive()) { - return null; - } - if (node.compareAndSet(oldWeightedValue, weightedValue)) { - final int weightedDifference = weight - oldWeightedValue.weight; - if (weightedDifference == 0) { - afterRead(node); - } else { - afterWrite(new UpdateTask(node, weightedDifference)); - } - return oldWeightedValue.value; - } - } - } - - @Override - public boolean replace(K key, V oldValue, V newValue) { - checkNotNull(key); - checkNotNull(oldValue); - checkNotNull(newValue); - - final int weight = weigher.weightOf(key, newValue); - final WeightedValue newWeightedValue = new WeightedValue(newValue, weight); - - final Node node = data.get(key); - if (node == null) { - return false; - } - for (;;) { - final WeightedValue weightedValue = node.get(); - if (!weightedValue.isAlive() || !weightedValue.contains(oldValue)) { - return false; - } - if (node.compareAndSet(weightedValue, newWeightedValue)) { - final int weightedDifference = weight - weightedValue.weight; - if (weightedDifference == 0) { - afterRead(node); - } else { - afterWrite(new UpdateTask(node, weightedDifference)); - } - return true; - } - } - } - - @Override - public Set keySet() { - final Set ks = keySet; - return (ks == null) ? (keySet = new KeySet()) : ks; - } - - /** - * Returns a unmodifiable snapshot {@link Set} view of the keys contained in - * this map. The set's iterator returns the keys whose order of iteration is - * the ascending order in which its entries are considered eligible for - * retention, from the least-likely to be retained to the most-likely. - *

- * Beware that, unlike in {@link #keySet()}, obtaining the set is NOT - * a constant-time operation. Because of the asynchronous nature of the page - * replacement policy, determining the retention ordering requires a traversal - * of the keys. - * - * @return an ascending snapshot view of the keys in this map - */ - public Set ascendingKeySet() { - return ascendingKeySetWithLimit(Integer.MAX_VALUE); - } - - /** - * Returns an unmodifiable snapshot {@link Set} view of the keys contained in - * this map. The set's iterator returns the keys whose order of iteration is - * the ascending order in which its entries are considered eligible for - * retention, from the least-likely to be retained to the most-likely. - *

- * Beware that, unlike in {@link #keySet()}, obtaining the set is NOT - * a constant-time operation. Because of the asynchronous nature of the page - * replacement policy, determining the retention ordering requires a traversal - * of the keys. - * - * @param limit the maximum size of the returned set - * @return a ascending snapshot view of the keys in this map - * @throws IllegalArgumentException if the limit is negative - */ - public Set ascendingKeySetWithLimit(int limit) { - return orderedKeySet(true, limit); - } - - /** - * Returns an unmodifiable snapshot {@link Set} view of the keys contained in - * this map. The set's iterator returns the keys whose order of iteration is - * the descending order in which its entries are considered eligible for - * retention, from the most-likely to be retained to the least-likely. - *

- * Beware that, unlike in {@link #keySet()}, obtaining the set is NOT - * a constant-time operation. Because of the asynchronous nature of the page - * replacement policy, determining the retention ordering requires a traversal - * of the keys. - * - * @return a descending snapshot view of the keys in this map - */ - public Set descendingKeySet() { - return descendingKeySetWithLimit(Integer.MAX_VALUE); - } - - /** - * Returns an unmodifiable snapshot {@link Set} view of the keys contained in - * this map. The set's iterator returns the keys whose order of iteration is - * the descending order in which its entries are considered eligible for - * retention, from the most-likely to be retained to the least-likely. - *

- * Beware that, unlike in {@link #keySet()}, obtaining the set is NOT - * a constant-time operation. Because of the asynchronous nature of the page - * replacement policy, determining the retention ordering requires a traversal - * of the keys. - * - * @param limit the maximum size of the returned set - * @return a descending snapshot view of the keys in this map - * @throws IllegalArgumentException if the limit is negative - */ - public Set descendingKeySetWithLimit(int limit) { - return orderedKeySet(false, limit); - } - - Set orderedKeySet(boolean ascending, int limit) { - checkArgument(limit >= 0); - evictionLock.lock(); - try { - drainBuffers(); - - final int initialCapacity = (weigher == Weighers.entrySingleton()) - ? Math.min(limit, (int) weightedSize()) - : 16; - final Set keys = new LinkedHashSet(initialCapacity); - final Iterator> iterator = ascending - ? evictionDeque.iterator() - : evictionDeque.descendingIterator(); - while (iterator.hasNext() && (limit > keys.size())) { - keys.add(iterator.next().key); - } - return unmodifiableSet(keys); - } finally { - evictionLock.unlock(); - } - } - - @Override - public Collection values() { - final Collection vs = values; - return (vs == null) ? (values = new Values()) : vs; - } - - @Override - public Set> entrySet() { - final Set> es = entrySet; - return (es == null) ? (entrySet = new EntrySet()) : es; - } - - /** - * Returns an unmodifiable snapshot {@link Map} view of the mappings contained - * in this map. The map's collections return the mappings whose order of - * iteration is the ascending order in which its entries are considered - * eligible for retention, from the least-likely to be retained to the - * most-likely. - *

- * Beware that obtaining the mappings is NOT a constant-time - * operation. Because of the asynchronous nature of the page replacement - * policy, determining the retention ordering requires a traversal of the - * entries. - * - * @return a ascending snapshot view of this map - */ - public Map ascendingMap() { - return ascendingMapWithLimit(Integer.MAX_VALUE); - } - - /** - * Returns an unmodifiable snapshot {@link Map} view of the mappings contained - * in this map. The map's collections return the mappings whose order of - * iteration is the ascending order in which its entries are considered - * eligible for retention, from the least-likely to be retained to the - * most-likely. - *

- * Beware that obtaining the mappings is NOT a constant-time - * operation. Because of the asynchronous nature of the page replacement - * policy, determining the retention ordering requires a traversal of the - * entries. - * - * @param limit the maximum size of the returned map - * @return a ascending snapshot view of this map - * @throws IllegalArgumentException if the limit is negative - */ - public Map ascendingMapWithLimit(int limit) { - return orderedMap(true, limit); - } - - /** - * Returns an unmodifiable snapshot {@link Map} view of the mappings contained - * in this map. The map's collections return the mappings whose order of - * iteration is the descending order in which its entries are considered - * eligible for retention, from the most-likely to be retained to the - * least-likely. - *

- * Beware that obtaining the mappings is NOT a constant-time - * operation. Because of the asynchronous nature of the page replacement - * policy, determining the retention ordering requires a traversal of the - * entries. - * - * @return a descending snapshot view of this map - */ - public Map descendingMap() { - return descendingMapWithLimit(Integer.MAX_VALUE); - } - - /** - * Returns an unmodifiable snapshot {@link Map} view of the mappings contained - * in this map. The map's collections return the mappings whose order of - * iteration is the descending order in which its entries are considered - * eligible for retention, from the most-likely to be retained to the - * least-likely. - *

- * Beware that obtaining the mappings is NOT a constant-time - * operation. Because of the asynchronous nature of the page replacement - * policy, determining the retention ordering requires a traversal of the - * entries. - * - * @param limit the maximum size of the returned map - * @return a descending snapshot view of this map - * @throws IllegalArgumentException if the limit is negative - */ - public Map descendingMapWithLimit(int limit) { - return orderedMap(false, limit); - } - - Map orderedMap(boolean ascending, int limit) { - checkArgument(limit >= 0); - evictionLock.lock(); - try { - drainBuffers(); - - final int initialCapacity = (weigher == Weighers.entrySingleton()) - ? Math.min(limit, (int) weightedSize()) - : 16; - final Map map = new LinkedHashMap(initialCapacity); - final Iterator> iterator = ascending - ? evictionDeque.iterator() - : evictionDeque.descendingIterator(); - while (iterator.hasNext() && (limit > map.size())) { - Node node = iterator.next(); - map.put(node.key, node.getValue()); - } - return unmodifiableMap(map); - } finally { - evictionLock.unlock(); - } - } - - /** The draining status of the buffers. */ - enum DrainStatus { - - /** A drain is not taking place. */ - IDLE { - @Override boolean shouldDrainBuffers(boolean delayable) { - return !delayable; - } - }, - - /** A drain is required due to a pending write modification. */ - REQUIRED { - @Override boolean shouldDrainBuffers(boolean delayable) { - return true; - } - }, - - /** A drain is in progress. */ - PROCESSING { - @Override boolean shouldDrainBuffers(boolean delayable) { - return false; - } - }; + /* ---------------- Eviction Support -------------- */ /** - * Determines whether the buffers should be drained. + * Retrieves the maximum weighted capacity of the map. * - * @param delayable if a drain should be delayed until required - * @return if a drain should be attempted + * @return the maximum weighted capacity */ - abstract boolean shouldDrainBuffers(boolean delayable); - } - - /** A value, its weight, and the entry's status. */ - static final class WeightedValue { - final int weight; - final V value; + public long capacity() { + return capacity.get(); + } - WeightedValue(V value, int weight) { - this.weight = weight; - this.value = value; + /** + * Sets the maximum weighted capacity of the map and eagerly evicts entries + * until it shrinks to the appropriate size. + * + * @param capacity + * the maximum weighted capacity of the map + * @throws IllegalArgumentException + * if the capacity is negative + */ + public void setCapacity(long capacity) { + checkArgument(capacity >= 0); + evictionLock.lock(); + try { + this.capacity.lazySet(Math.min(capacity, MAXIMUM_CAPACITY)); + drainBuffers(); + evict(); + } finally { + evictionLock.unlock(); + } + notifyListener(); } - boolean contains(Object o) { - return (o == value) || value.equals(o); + /** Determines whether the map has exceeded its capacity. */ + boolean hasOverflowed() { + return weightedSize.get() > capacity.get(); } /** - * If the entry is available in the hash-table and page replacement policy. + * Evicts entries from the map while it exceeds the capacity and appends + * evicted entries to the notification queue for processing. */ - boolean isAlive() { - return weight > 0; + void evict() { + // Attempts to evict entries from the map if it exceeds the maximum + // capacity. If the eviction fails due to a concurrent removal of the + // victim, that removal may cancel out the addition that triggered this + // eviction. The victim is eagerly unlinked before the removal task so + // that if an eviction is still required then a new victim will be chosen + // for removal. + while (hasOverflowed()) { + final Node node = evictionDeque.poll(); + + // If weighted values are used, then the pending operations will adjust + // the size to reflect the correct weight + if (node == null) { + return; + } + + // Notify the listener only if the entry was evicted + if (data.remove(node.key, node)) { + pendingNotifications.add(node); + } + + makeDead(node); + } } /** - * If the entry was removed from the hash-table and is awaiting removal from - * the page replacement policy. + * Performs the post-processing work required after a read. + * + * @param node + * the entry in the page replacement policy */ - boolean isRetired() { - return weight < 0; + void afterRead(Node node) { + final int bufferIndex = readBufferIndex(); + final long writeCount = recordRead(bufferIndex, node); + drainOnReadIfNeeded(bufferIndex, writeCount); + notifyListener(); + } + + /** Returns the index to the read buffer to record into. */ + static int readBufferIndex() { + // A buffer is chosen by the thread's id so that tasks are distributed in a + // pseudo evenly manner. This helps avoid hot entries causing contention + // due to other threads trying to append to the same buffer. + return ((int) Thread.currentThread().getId()) & READ_BUFFERS_MASK; } /** - * If the entry was removed from the hash-table and the page replacement - * policy. + * Records a read in the buffer and return its write count. + * + * @param bufferIndex + * the index to the chosen read buffer + * @param node + * the entry in the page replacement policy + * @return the number of writes on the chosen read buffer */ - boolean isDead() { - return weight == 0; - } - } + long recordRead(int bufferIndex, Node node) { + // The location in the buffer is chosen in a racy fashion as the increment + // is not atomic with the insertion. This means that concurrent reads can + // overlap and overwrite one another, resulting in a lossy buffer. + final AtomicLong counter = readBufferWriteCount[bufferIndex]; + final long writeCount = counter.get(); + counter.lazySet(writeCount + 1); - /** - * A node contains the key, the weighted value, and the linkage pointers on - * the page-replacement algorithm's data structures. - */ - @SuppressWarnings("serial") - static final class Node extends AtomicReference> - implements Linked> { - final K key; - Node prev; - Node next; + final int index = (int) (writeCount & READ_BUFFER_INDEX_MASK); + readBuffers[bufferIndex][index].lazySet(node); - /** Creates a new, unlinked node. */ - Node(K key, WeightedValue weightedValue) { - super(weightedValue); - this.key = key; + return writeCount; } - @Override - public Node getPrevious() { - return prev; + /** + * Attempts to drain the buffers if it is determined to be needed when + * post-processing a read. + * + * @param bufferIndex + * the index to the chosen read buffer + * @param writeCount + * the number of writes on the chosen read buffer + */ + void drainOnReadIfNeeded(int bufferIndex, long writeCount) { + final long pending = (writeCount - readBufferDrainAtWriteCount[bufferIndex].get()); + final boolean delayable = (pending < READ_BUFFER_THRESHOLD); + final DrainStatus status = drainStatus.get(); + if (status.shouldDrainBuffers(delayable)) { + tryToDrainBuffers(); + } } - @Override - public void setPrevious(Node prev) { - this.prev = prev; + /** + * Performs the post-processing work required after a write. + * + * @param task + * the pending operation to be applied + */ + void afterWrite(Runnable task) { + writeBuffer.add(task); + drainStatus.lazySet(REQUIRED); + tryToDrainBuffers(); + notifyListener(); } - @Override - public Node getNext() { - return next; + /** + * Attempts to acquire the eviction lock and apply the pending operations, up + * to the amortized threshold, to the page replacement policy. + */ + void tryToDrainBuffers() { + if (evictionLock.tryLock()) { + try { + drainStatus.lazySet(PROCESSING); + drainBuffers(); + } finally { + drainStatus.compareAndSet(PROCESSING, IDLE); + evictionLock.unlock(); + } + } } - @Override - public void setNext(Node next) { - this.next = next; + /** Drains the read and write buffers up to an amortized threshold. */ + void drainBuffers() { + drainReadBuffers(); + drainWriteBuffer(); } - /** Retrieves the value held by the current WeightedValue. */ - V getValue() { - return get().value; + /** Drains the read buffers, each up to an amortized threshold. */ + void drainReadBuffers() { + final int start = (int) Thread.currentThread().getId(); + final int end = start + NUMBER_OF_READ_BUFFERS; + for (int i = start; i < end; i++) { + drainReadBuffer(i & READ_BUFFERS_MASK); + } } - } - /** An adapter to safely externalize the keys. */ - final class KeySet extends AbstractSet { - final ConcurrentLinkedHashMap map = ConcurrentLinkedHashMap.this; - - @Override - public int size() { - return map.size(); + /** Drains the read buffer up to an amortized threshold. */ + void drainReadBuffer(int bufferIndex) { + final long writeCount = readBufferWriteCount[bufferIndex].get(); + for (int i = 0; i < READ_BUFFER_DRAIN_THRESHOLD; i++) { + final int index = (int) (readBufferReadCount[bufferIndex] & READ_BUFFER_INDEX_MASK); + final AtomicReference> slot = readBuffers[bufferIndex][index]; + final Node node = slot.get(); + if (node == null) { + break; + } + + slot.lazySet(null); + applyRead(node); + readBufferReadCount[bufferIndex]++; + } + readBufferDrainAtWriteCount[bufferIndex].lazySet(writeCount); } - @Override - public void clear() { - map.clear(); + /** Updates the node's location in the page replacement policy. */ + void applyRead(Node node) { + // An entry may be scheduled for reordering despite having been removed. + // This can occur when the entry was concurrently read while a writer was + // removing it. If the entry is no longer linked then it does not need to + // be processed. + if (evictionDeque.contains(node)) { + evictionDeque.moveToBack(node); + } } - @Override - public Iterator iterator() { - return new KeyIterator(); + /** Drains the read buffer up to an amortized threshold. */ + void drainWriteBuffer() { + for (int i = 0; i < WRITE_BUFFER_DRAIN_THRESHOLD; i++) { + final Runnable task = writeBuffer.poll(); + if (task == null) { + break; + } + task.run(); + } } - @Override - public boolean contains(Object obj) { - return containsKey(obj); + /** + * Attempts to transition the node from the alive state to the + * retired state. + * + * @param node + * the entry in the page replacement policy + * @param expect + * the expected weighted value + * @return if successful + */ + boolean tryToRetire(Node node, WeightedValue expect) { + if (expect.isAlive()) { + final WeightedValue retired = new WeightedValue(expect.value, -expect.weight); + return node.compareAndSet(expect, retired); + } + return false; } - @Override - public boolean remove(Object obj) { - return (map.remove(obj) != null); + /** + * Atomically transitions the node from the alive state to the + * retired state, if a valid transition. + * + * @param node + * the entry in the page replacement policy + */ + void makeRetired(Node node) { + for (;;) { + final WeightedValue current = node.get(); + if (!current.isAlive()) { + return; + } + final WeightedValue retired = new WeightedValue(current.value, -current.weight); + if (node.compareAndSet(current, retired)) { + return; + } + } } - @Override - public Object[] toArray() { - return map.data.keySet().toArray(); + /** + * Atomically transitions the node to the dead state and decrements + * the weightedSize. + * + * @param node + * the entry in the page replacement policy + */ + void makeDead(Node node) { + for (;;) { + WeightedValue current = node.get(); + WeightedValue dead = new WeightedValue(current.value, 0); + if (node.compareAndSet(current, dead)) { + weightedSize.lazySet(weightedSize.get() - Math.abs(current.weight)); + return; + } + } } - @Override - public T[] toArray(T[] array) { - return map.data.keySet().toArray(array); + /** Notifies the listener of entries that were evicted. */ + void notifyListener() { + Node node; + while ((node = pendingNotifications.poll()) != null) { + listener.onEviction(node.key, node.getValue()); + } } - } - /** An adapter to safely externalize the key iterator. */ - final class KeyIterator implements Iterator { - final Iterator iterator = data.keySet().iterator(); - K current; + /** Adds the node to the page replacement policy. */ + final class AddTask implements Runnable { + final Node node; + final int weight; - @Override - public boolean hasNext() { - return iterator.hasNext(); - } + AddTask(Node node, int weight) { + this.weight = weight; + this.node = node; + } - @Override - public K next() { - current = iterator.next(); - return current; - } + @Override + public void run() { + weightedSize.lazySet(weightedSize.get() + weight); - @Override - public void remove() { - checkState(current != null); - ConcurrentLinkedHashMap.this.remove(current); - current = null; + // ignore out-of-order write operations + if (node.get().isAlive()) { + evictionDeque.add(node); + evict(); + } + } } - } - /** An adapter to safely externalize the values. */ - final class Values extends AbstractCollection { + /** Removes a node from the page replacement policy. */ + final class RemovalTask implements Runnable { + final Node node; - @Override - public int size() { - return ConcurrentLinkedHashMap.this.size(); + RemovalTask(Node node) { + this.node = node; + } + + @Override + public void run() { + // add may not have been processed yet + evictionDeque.remove(node); + makeDead(node); + } } - @Override - public void clear() { - ConcurrentLinkedHashMap.this.clear(); + /** Updates the weighted size and evicts an entry on overflow. */ + final class UpdateTask implements Runnable { + final int weightDifference; + final Node node; + + public UpdateTask(Node node, int weightDifference) { + this.weightDifference = weightDifference; + this.node = node; + } + + @Override + public void run() { + weightedSize.lazySet(weightedSize.get() + weightDifference); + applyRead(node); + evict(); + } } + /* ---------------- Concurrent Map Support -------------- */ + @Override - public Iterator iterator() { - return new ValueIterator(); + public boolean isEmpty() { + return data.isEmpty(); } @Override - public boolean contains(Object o) { - return containsValue(o); + public int size() { + return data.size(); } - } - /** An adapter to safely externalize the value iterator. */ - final class ValueIterator implements Iterator { - final Iterator> iterator = data.values().iterator(); - Node current; + /** + * Returns the weighted size of this map. + * + * @return the combined weight of the values in this map + */ + public long weightedSize() { + return Math.max(0, weightedSize.get()); + } @Override - public boolean hasNext() { - return iterator.hasNext(); + public void clear() { + evictionLock.lock(); + try { + // Discard all entries + Node node; + while ((node = evictionDeque.poll()) != null) { + data.remove(node.key, node); + makeDead(node); + } + + // Discard all pending reads + for (AtomicReference>[] buffer : readBuffers) { + for (AtomicReference> slot : buffer) { + slot.lazySet(null); + } + } + + // Apply all pending writes + Runnable task; + while ((task = writeBuffer.poll()) != null) { + task.run(); + } + } finally { + evictionLock.unlock(); + } } @Override - public V next() { - current = iterator.next(); - return current.getValue(); + public boolean containsKey(Object key) { + return data.containsKey(key); } @Override - public void remove() { - checkState(current != null); - ConcurrentLinkedHashMap.this.remove(current.key); - current = null; - } - } + public boolean containsValue(Object value) { + checkNotNull(value); - /** An adapter to safely externalize the entries. */ - final class EntrySet extends AbstractSet> { - final ConcurrentLinkedHashMap map = ConcurrentLinkedHashMap.this; + for (Node node : data.values()) { + if (node.getValue().equals(value)) { + return true; + } + } + return false; + } @Override - public int size() { - return map.size(); + public V get(Object key) { + final Node node = data.get(key); + if (node == null) { + return null; + } + afterRead(node); + return node.getValue(); } - @Override - public void clear() { - map.clear(); + /** + * Returns the value to which the specified key is mapped, or {@code null} + * if this map contains no mapping for the key. This method differs from + * {@link #get(Object)} in that it does not record the operation with the + * page replacement policy. + * + * @param key + * the key whose associated value is to be returned + * @return the value to which the specified key is mapped, or + * {@code null} if this map contains no mapping for the key + * @throws NullPointerException + * if the specified key is null + */ + public V getQuietly(Object key) { + final Node node = data.get(key); + return (node == null) ? null : node.getValue(); } @Override - public Iterator> iterator() { - return new EntryIterator(); + public V put(K key, V value) { + return put(key, value, false); } @Override - public boolean contains(Object obj) { - if (!(obj instanceof Entry)) { - return false; - } - Entry entry = (Entry) obj; - Node node = map.data.get(entry.getKey()); - return (node != null) && (node.getValue().equals(entry.getValue())); + public V putIfAbsent(K key, V value) { + return put(key, value, true); } - @Override - public boolean add(Entry entry) { - return (map.putIfAbsent(entry.getKey(), entry.getValue()) == null); + /** + * Adds a node to the list and the data store. If an existing node is found, + * then its value is updated if allowed. + * + * @param key + * key with which the specified value is to be associated + * @param value + * value to be associated with the specified key + * @param onlyIfAbsent + * a write is performed only if the key is not already + * associated with a value + * @return the prior value in the data store or null if no mapping was found + */ + V put(K key, V value, boolean onlyIfAbsent) { + checkNotNull(key); + checkNotNull(value); + + final int weight = weigher.weightOf(key, value); + final WeightedValue weightedValue = new WeightedValue(value, weight); + final Node node = new Node(key, weightedValue); + + for (;;) { + final Node prior = data.putIfAbsent(node.key, node); + if (prior == null) { + afterWrite(new AddTask(node, weight)); + return null; + } else if (onlyIfAbsent) { + afterRead(prior); + return prior.getValue(); + } + for (;;) { + final WeightedValue oldWeightedValue = prior.get(); + if (!oldWeightedValue.isAlive()) { + break; + } + + if (prior.compareAndSet(oldWeightedValue, weightedValue)) { + final int weightedDifference = weight - oldWeightedValue.weight; + if (weightedDifference == 0) { + afterRead(prior); + } else { + afterWrite(new UpdateTask(prior, weightedDifference)); + } + return oldWeightedValue.value; + } + } + } } @Override - public boolean remove(Object obj) { - if (!(obj instanceof Entry)) { - return false; - } - Entry entry = (Entry) obj; - return map.remove(entry.getKey(), entry.getValue()); - } - } + public V remove(Object key) { + final Node node = data.remove(key); + if (node == null) { + return null; + } - /** An adapter to safely externalize the entry iterator. */ - final class EntryIterator implements Iterator> { - final Iterator> iterator = data.values().iterator(); - Node current; + makeRetired(node); + afterWrite(new RemovalTask(node)); + return node.getValue(); + } @Override - public boolean hasNext() { - return iterator.hasNext(); + public boolean remove(Object key, Object value) { + final Node node = data.get(key); + if ((node == null) || (value == null)) { + return false; + } + + WeightedValue weightedValue = node.get(); + for (;;) { + if (weightedValue.contains(value)) { + if (tryToRetire(node, weightedValue)) { + if (data.remove(key, node)) { + afterWrite(new RemovalTask(node)); + return true; + } + } else { + weightedValue = node.get(); + if (weightedValue.isAlive()) { + // retry as an intermediate update may have replaced the value with + // an equal instance that has a different reference identity + continue; + } + } + } + return false; + } } @Override - public Entry next() { - current = iterator.next(); - return new WriteThroughEntry(current); + public V replace(K key, V value) { + checkNotNull(key); + checkNotNull(value); + + final int weight = weigher.weightOf(key, value); + final WeightedValue weightedValue = new WeightedValue(value, weight); + + final Node node = data.get(key); + if (node == null) { + return null; + } + for (;;) { + final WeightedValue oldWeightedValue = node.get(); + if (!oldWeightedValue.isAlive()) { + return null; + } + if (node.compareAndSet(oldWeightedValue, weightedValue)) { + final int weightedDifference = weight - oldWeightedValue.weight; + if (weightedDifference == 0) { + afterRead(node); + } else { + afterWrite(new UpdateTask(node, weightedDifference)); + } + return oldWeightedValue.value; + } + } } @Override - public void remove() { - checkState(current != null); - ConcurrentLinkedHashMap.this.remove(current.key); - current = null; - } - } + public boolean replace(K key, V oldValue, V newValue) { + checkNotNull(key); + checkNotNull(oldValue); + checkNotNull(newValue); - /** An entry that allows updates to write through to the map. */ - final class WriteThroughEntry extends SimpleEntry { - static final long serialVersionUID = 1; + final int weight = weigher.weightOf(key, newValue); + final WeightedValue newWeightedValue = new WeightedValue(newValue, weight); - WriteThroughEntry(Node node) { - super(node.key, node.getValue()); + final Node node = data.get(key); + if (node == null) { + return false; + } + for (;;) { + final WeightedValue weightedValue = node.get(); + if (!weightedValue.isAlive() || !weightedValue.contains(oldValue)) { + return false; + } + if (node.compareAndSet(weightedValue, newWeightedValue)) { + final int weightedDifference = weight - weightedValue.weight; + if (weightedDifference == 0) { + afterRead(node); + } else { + afterWrite(new UpdateTask(node, weightedDifference)); + } + return true; + } + } } @Override - public V setValue(V value) { - put(getKey(), value); - return super.setValue(value); + public Set keySet() { + final Set ks = keySet; + return (ks == null) ? (keySet = new KeySet()) : ks; } - Object writeReplace() { - return new SimpleEntry(this); + /** + * Returns a unmodifiable snapshot {@link Set} view of the keys contained in + * this map. The set's iterator returns the keys whose order of iteration is + * the ascending order in which its entries are considered eligible for + * retention, from the least-likely to be retained to the most-likely. + *

+ * Beware that, unlike in {@link #keySet()}, obtaining the set is NOT + * a constant-time operation. Because of the asynchronous nature of the page + * replacement policy, determining the retention ordering requires a traversal + * of the keys. + * + * @return an ascending snapshot view of the keys in this map + */ + public Set ascendingKeySet() { + return ascendingKeySetWithLimit(Integer.MAX_VALUE); } - } - /** A weigher that enforces that the weight falls within a valid range. */ - static final class BoundedEntryWeigher implements EntryWeigher, Serializable { - static final long serialVersionUID = 1; - final EntryWeigher weigher; - - BoundedEntryWeigher(EntryWeigher weigher) { - checkNotNull(weigher); - this.weigher = weigher; + /** + * Returns an unmodifiable snapshot {@link Set} view of the keys contained in + * this map. The set's iterator returns the keys whose order of iteration is + * the ascending order in which its entries are considered eligible for + * retention, from the least-likely to be retained to the most-likely. + *

+ * Beware that, unlike in {@link #keySet()}, obtaining the set is NOT + * a constant-time operation. Because of the asynchronous nature of the page + * replacement policy, determining the retention ordering requires a traversal + * of the keys. + * + * @param limit + * the maximum size of the returned set + * @return a ascending snapshot view of the keys in this map + * @throws IllegalArgumentException + * if the limit is negative + */ + public Set ascendingKeySetWithLimit(int limit) { + return orderedKeySet(true, limit); } - @Override - public int weightOf(K key, V value) { - int weight = weigher.weightOf(key, value); - checkArgument(weight >= 1); - return weight; + /** + * Returns an unmodifiable snapshot {@link Set} view of the keys contained in + * this map. The set's iterator returns the keys whose order of iteration is + * the descending order in which its entries are considered eligible for + * retention, from the most-likely to be retained to the least-likely. + *

+ * Beware that, unlike in {@link #keySet()}, obtaining the set is NOT + * a constant-time operation. Because of the asynchronous nature of the page + * replacement policy, determining the retention ordering requires a traversal + * of the keys. + * + * @return a descending snapshot view of the keys in this map + */ + public Set descendingKeySet() { + return descendingKeySetWithLimit(Integer.MAX_VALUE); } - Object writeReplace() { - return weigher; - } - } - - /** A queue that discards all additions and is always empty. */ - static final class DiscardingQueue extends AbstractQueue { - @Override public boolean add(Object e) { return true; } - @Override public boolean offer(Object e) { return true; } - @Override public Object poll() { return null; } - @Override public Object peek() { return null; } - @Override public int size() { return 0; } - @Override public Iterator iterator() { return emptyList().iterator(); } - } - - /** A listener that ignores all notifications. */ - enum DiscardingListener implements EvictionListener { - INSTANCE; - - @Override public void onEviction(Object key, Object value) {} - } - - /* ---------------- Serialization Support -------------- */ - - static final long serialVersionUID = 1; - - Object writeReplace() { - return new SerializationProxy(this); - } - - private void readObject(ObjectInputStream stream) throws InvalidObjectException { - throw new InvalidObjectException("Proxy required"); - } - - /** - * A proxy that is serialized instead of the map. The page-replacement - * algorithm's data structures are not serialized so the deserialized - * instance contains only the entries. This is acceptable as caches hold - * transient data that is recomputable and serialization would tend to be - * used as a fast warm-up process. - */ - static final class SerializationProxy implements Serializable { - final EntryWeigher weigher; - final EvictionListener listener; - final int concurrencyLevel; - final Map data; - final long capacity; - - SerializationProxy(ConcurrentLinkedHashMap map) { - concurrencyLevel = map.concurrencyLevel; - data = new HashMap(map); - capacity = map.capacity.get(); - listener = map.listener; - weigher = map.weigher; + /** + * Returns an unmodifiable snapshot {@link Set} view of the keys contained in + * this map. The set's iterator returns the keys whose order of iteration is + * the descending order in which its entries are considered eligible for + * retention, from the most-likely to be retained to the least-likely. + *

+ * Beware that, unlike in {@link #keySet()}, obtaining the set is NOT + * a constant-time operation. Because of the asynchronous nature of the page + * replacement policy, determining the retention ordering requires a traversal + * of the keys. + * + * @param limit + * the maximum size of the returned set + * @return a descending snapshot view of the keys in this map + * @throws IllegalArgumentException + * if the limit is negative + */ + public Set descendingKeySetWithLimit(int limit) { + return orderedKeySet(false, limit); + } + + Set orderedKeySet(boolean ascending, int limit) { + checkArgument(limit >= 0); + evictionLock.lock(); + try { + drainBuffers(); + + final int initialCapacity = (weigher == Weighers.entrySingleton()) ? Math.min(limit, (int) weightedSize()) + : 16; + final Set keys = new LinkedHashSet(initialCapacity); + final Iterator> iterator = ascending ? evictionDeque.iterator() + : evictionDeque.descendingIterator(); + while (iterator.hasNext() && (limit > keys.size())) { + keys.add(iterator.next().key); + } + return unmodifiableSet(keys); + } finally { + evictionLock.unlock(); + } } - Object readResolve() { - ConcurrentLinkedHashMap map = new Builder() - .concurrencyLevel(concurrencyLevel) - .maximumWeightedCapacity(capacity) - .listener(listener) - .weigher(weigher) - .build(); - map.putAll(data); - return map; + @Override + public Collection values() { + final Collection vs = values; + return (vs == null) ? (values = new Values()) : vs; } - static final long serialVersionUID = 1; - } - - /* ---------------- Builder -------------- */ - - /** - * A builder that creates {@link ConcurrentLinkedHashMap} instances. It - * provides a flexible approach for constructing customized instances with - * a named parameter syntax. It can be used in the following manner: - *

{@code
-   * ConcurrentMap> graph = new Builder>()
-   *     .maximumWeightedCapacity(5000)
-   *     .weigher(Weighers.set())
-   *     .build();
-   * }
- */ - public static final class Builder { - static final int DEFAULT_CONCURRENCY_LEVEL = 16; - static final int DEFAULT_INITIAL_CAPACITY = 16; - - EvictionListener listener; - EntryWeigher weigher; - - int concurrencyLevel; - int initialCapacity; - long capacity; - - @SuppressWarnings("unchecked") - public Builder() { - capacity = -1; - weigher = Weighers.entrySingleton(); - initialCapacity = DEFAULT_INITIAL_CAPACITY; - concurrencyLevel = DEFAULT_CONCURRENCY_LEVEL; - listener = (EvictionListener) DiscardingListener.INSTANCE; + @Override + public Set> entrySet() { + final Set> es = entrySet; + return (es == null) ? (entrySet = new EntrySet()) : es; } /** - * Specifies the initial capacity of the hash table (default 16). - * This is the number of key-value pairs that the hash table can hold - * before a resize operation is required. + * Returns an unmodifiable snapshot {@link Map} view of the mappings contained + * in this map. The map's collections return the mappings whose order of + * iteration is the ascending order in which its entries are considered + * eligible for retention, from the least-likely to be retained to the + * most-likely. + *

+ * Beware that obtaining the mappings is NOT a constant-time + * operation. Because of the asynchronous nature of the page replacement + * policy, determining the retention ordering requires a traversal of the + * entries. * - * @param initialCapacity the initial capacity used to size the hash table - * to accommodate this many entries. - * - * @return Builder - * @throws IllegalArgumentException if the initialCapacity is negative + * @return a ascending snapshot view of this map */ - public Builder initialCapacity(int initialCapacity) { - checkArgument(initialCapacity >= 0); - this.initialCapacity = initialCapacity; - return this; + public Map ascendingMap() { + return ascendingMapWithLimit(Integer.MAX_VALUE); } /** - * Specifies the maximum weighted capacity to coerce the map to and may - * exceed it temporarily. + * Returns an unmodifiable snapshot {@link Map} view of the mappings contained + * in this map. The map's collections return the mappings whose order of + * iteration is the ascending order in which its entries are considered + * eligible for retention, from the least-likely to be retained to the + * most-likely. + *

+ * Beware that obtaining the mappings is NOT a constant-time + * operation. Because of the asynchronous nature of the page replacement + * policy, determining the retention ordering requires a traversal of the + * entries. * - * @param capacity the weighted threshold to bound the map by - * @return Builder - * @throws IllegalArgumentException if the maximumWeightedCapacity is - * negative + * @param limit + * the maximum size of the returned map + * @return a ascending snapshot view of this map + * @throws IllegalArgumentException + * if the limit is negative */ - public Builder maximumWeightedCapacity(long capacity) { - checkArgument(capacity >= 0); - this.capacity = capacity; - return this; + public Map ascendingMapWithLimit(int limit) { + return orderedMap(true, limit); } /** - * Specifies the estimated number of concurrently updating threads. The - * implementation performs internal sizing to try to accommodate this many - * threads (default 16). + * Returns an unmodifiable snapshot {@link Map} view of the mappings contained + * in this map. The map's collections return the mappings whose order of + * iteration is the descending order in which its entries are considered + * eligible for retention, from the most-likely to be retained to the + * least-likely. + *

+ * Beware that obtaining the mappings is NOT a constant-time + * operation. Because of the asynchronous nature of the page replacement + * policy, determining the retention ordering requires a traversal of the + * entries. * - * @param concurrencyLevel the estimated number of concurrently updating - * threads - * @return Builder - * @throws IllegalArgumentException if the concurrencyLevel is less than or - * equal to zero + * @return a descending snapshot view of this map */ - public Builder concurrencyLevel(int concurrencyLevel) { - checkArgument(concurrencyLevel > 0); - this.concurrencyLevel = concurrencyLevel; - return this; + public Map descendingMap() { + return descendingMapWithLimit(Integer.MAX_VALUE); } /** - * Specifies an optional listener that is registered for notification when - * an entry is evicted. + * Returns an unmodifiable snapshot {@link Map} view of the mappings contained + * in this map. The map's collections return the mappings whose order of + * iteration is the descending order in which its entries are considered + * eligible for retention, from the most-likely to be retained to the + * least-likely. + *

+ * Beware that obtaining the mappings is NOT a constant-time + * operation. Because of the asynchronous nature of the page replacement + * policy, determining the retention ordering requires a traversal of the + * entries. * - * @param listener the object to forward evicted entries to - * @return Builder - * @throws NullPointerException if the listener is null + * @param limit + * the maximum size of the returned map + * @return a descending snapshot view of this map + * @throws IllegalArgumentException + * if the limit is negative */ - public Builder listener(EvictionListener listener) { - checkNotNull(listener); - this.listener = listener; - return this; + public Map descendingMapWithLimit(int limit) { + return orderedMap(false, limit); + } + + Map orderedMap(boolean ascending, int limit) { + checkArgument(limit >= 0); + evictionLock.lock(); + try { + drainBuffers(); + + final int initialCapacity = (weigher == Weighers.entrySingleton()) ? Math.min(limit, (int) weightedSize()) + : 16; + final Map map = new LinkedHashMap(initialCapacity); + final Iterator> iterator = ascending ? evictionDeque.iterator() + : evictionDeque.descendingIterator(); + while (iterator.hasNext() && (limit > map.size())) { + Node node = iterator.next(); + map.put(node.key, node.getValue()); + } + return unmodifiableMap(map); + } finally { + evictionLock.unlock(); + } + } + + /** The draining status of the buffers. */ + enum DrainStatus { + + /** A drain is not taking place. */ + IDLE { + @Override + boolean shouldDrainBuffers(boolean delayable) { + return !delayable; + } + }, + + /** A drain is required due to a pending write modification. */ + REQUIRED { + @Override + boolean shouldDrainBuffers(boolean delayable) { + return true; + } + }, + + /** A drain is in progress. */ + PROCESSING { + @Override + boolean shouldDrainBuffers(boolean delayable) { + return false; + } + }; + + /** + * Determines whether the buffers should be drained. + * + * @param delayable + * if a drain should be delayed until required + * @return if a drain should be attempted + */ + abstract boolean shouldDrainBuffers(boolean delayable); + } + + /** A value, its weight, and the entry's status. */ + static final class WeightedValue { + final int weight; + final V value; + + WeightedValue(V value, int weight) { + this.weight = weight; + this.value = value; + } + + boolean contains(Object o) { + return (o == value) || value.equals(o); + } + + /** + * If the entry is available in the hash-table and page replacement policy. + */ + boolean isAlive() { + return weight > 0; + } + + /** + * If the entry was removed from the hash-table and is awaiting removal from + * the page replacement policy. + */ + boolean isRetired() { + return weight < 0; + } + + /** + * If the entry was removed from the hash-table and the page replacement + * policy. + */ + boolean isDead() { + return weight == 0; + } } /** - * Specifies an algorithm to determine how many the units of capacity a - * value consumes. The default algorithm bounds the map by the number of - * key-value pairs by giving each entry a weight of 1. - * - * @param weigher the algorithm to determine a value's weight - * @return Builder - * @throws NullPointerException if the weigher is null + * A node contains the key, the weighted value, and the linkage pointers on + * the page-replacement algorithm's data structures. */ - public Builder weigher(Weigher weigher) { - this.weigher = (weigher == Weighers.singleton()) - ? Weighers.entrySingleton() - : new BoundedEntryWeigher(Weighers.asEntryWeigher(weigher)); - return this; + @SuppressWarnings("serial") + static final class Node extends AtomicReference> implements Linked> { + final K key; + Node prev; + Node next; + + /** Creates a new, unlinked node. */ + Node(K key, WeightedValue weightedValue) { + super(weightedValue); + this.key = key; + } + + @Override + public Node getPrevious() { + return prev; + } + + @Override + public void setPrevious(Node prev) { + this.prev = prev; + } + + @Override + public Node getNext() { + return next; + } + + @Override + public void setNext(Node next) { + this.next = next; + } + + /** Retrieves the value held by the current WeightedValue. */ + V getValue() { + return get().value; + } + } + + /** An adapter to safely externalize the keys. */ + final class KeySet extends AbstractSet { + final ConcurrentLinkedHashMap map = ConcurrentLinkedHashMap.this; + + @Override + public int size() { + return map.size(); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Iterator iterator() { + return new KeyIterator(); + } + + @Override + public boolean contains(Object obj) { + return containsKey(obj); + } + + @Override + public boolean remove(Object obj) { + return (map.remove(obj) != null); + } + + @Override + public Object[] toArray() { + return map.data.keySet().toArray(); + } + + @Override + public T[] toArray(T[] array) { + return map.data.keySet().toArray(array); + } + } + + /** An adapter to safely externalize the key iterator. */ + final class KeyIterator implements Iterator { + final Iterator iterator = data.keySet().iterator(); + K current; + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public K next() { + current = iterator.next(); + return current; + } + + @Override + public void remove() { + checkState(current != null); + ConcurrentLinkedHashMap.this.remove(current); + current = null; + } + } + + /** An adapter to safely externalize the values. */ + final class Values extends AbstractCollection { + + @Override + public int size() { + return ConcurrentLinkedHashMap.this.size(); + } + + @Override + public void clear() { + ConcurrentLinkedHashMap.this.clear(); + } + + @Override + public Iterator iterator() { + return new ValueIterator(); + } + + @Override + public boolean contains(Object o) { + return containsValue(o); + } + } + + /** An adapter to safely externalize the value iterator. */ + final class ValueIterator implements Iterator { + final Iterator> iterator = data.values().iterator(); + Node current; + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public V next() { + current = iterator.next(); + return current.getValue(); + } + + @Override + public void remove() { + checkState(current != null); + ConcurrentLinkedHashMap.this.remove(current.key); + current = null; + } + } + + /** An adapter to safely externalize the entries. */ + final class EntrySet extends AbstractSet> { + final ConcurrentLinkedHashMap map = ConcurrentLinkedHashMap.this; + + @Override + public int size() { + return map.size(); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(Object obj) { + if (!(obj instanceof Entry)) { + return false; + } + Entry entry = (Entry) obj; + Node node = map.data.get(entry.getKey()); + return (node != null) && (node.getValue().equals(entry.getValue())); + } + + @Override + public boolean add(Entry entry) { + return (map.putIfAbsent(entry.getKey(), entry.getValue()) == null); + } + + @Override + public boolean remove(Object obj) { + if (!(obj instanceof Entry)) { + return false; + } + Entry entry = (Entry) obj; + return map.remove(entry.getKey(), entry.getValue()); + } + } + + /** An adapter to safely externalize the entry iterator. */ + final class EntryIterator implements Iterator> { + final Iterator> iterator = data.values().iterator(); + Node current; + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Entry next() { + current = iterator.next(); + return new WriteThroughEntry(current); + } + + @Override + public void remove() { + checkState(current != null); + ConcurrentLinkedHashMap.this.remove(current.key); + current = null; + } + } + + /** An entry that allows updates to write through to the map. */ + final class WriteThroughEntry extends SimpleEntry { + static final long serialVersionUID = 1; + + WriteThroughEntry(Node node) { + super(node.key, node.getValue()); + } + + @Override + public V setValue(V value) { + put(getKey(), value); + return super.setValue(value); + } + + Object writeReplace() { + return new SimpleEntry(this); + } + } + + /** A weigher that enforces that the weight falls within a valid range. */ + static final class BoundedEntryWeigher implements EntryWeigher, Serializable { + static final long serialVersionUID = 1; + final EntryWeigher weigher; + + BoundedEntryWeigher(EntryWeigher weigher) { + checkNotNull(weigher); + this.weigher = weigher; + } + + @Override + public int weightOf(K key, V value) { + int weight = weigher.weightOf(key, value); + checkArgument(weight >= 1); + return weight; + } + + Object writeReplace() { + return weigher; + } + } + + /** A queue that discards all additions and is always empty. */ + static final class DiscardingQueue extends AbstractQueue { + @Override + public boolean add(Object e) { + return true; + } + + @Override + public boolean offer(Object e) { + return true; + } + + @Override + public Object poll() { + return null; + } + + @Override + public Object peek() { + return null; + } + + @Override + public int size() { + return 0; + } + + @Override + public Iterator iterator() { + return emptyList().iterator(); + } + } + + /** A listener that ignores all notifications. */ + enum DiscardingListener implements EvictionListener { + INSTANCE; + + @Override + public void onEviction(Object key, Object value) {} + } + + /* ---------------- Serialization Support -------------- */ + + static final long serialVersionUID = 1; + + Object writeReplace() { + return new SerializationProxy(this); + } + + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Proxy required"); } /** - * Specifies an algorithm to determine how many the units of capacity an - * entry consumes. The default algorithm bounds the map by the number of - * key-value pairs by giving each entry a weight of 1. - * - * @param weigher the algorithm to determine a entry's weight - * @return Builder - * @throws NullPointerException if the weigher is null + * A proxy that is serialized instead of the map. The page-replacement + * algorithm's data structures are not serialized so the deserialized + * instance contains only the entries. This is acceptable as caches hold + * transient data that is recomputable and serialization would tend to be + * used as a fast warm-up process. */ - public Builder weigher(EntryWeigher weigher) { - this.weigher = (weigher == Weighers.entrySingleton()) - ? Weighers.entrySingleton() - : new BoundedEntryWeigher(weigher); - return this; + static final class SerializationProxy implements Serializable { + final EntryWeigher weigher; + final EvictionListener listener; + final int concurrencyLevel; + final Map data; + final long capacity; + + SerializationProxy(ConcurrentLinkedHashMap map) { + concurrencyLevel = map.concurrencyLevel; + data = new HashMap(map); + capacity = map.capacity.get(); + listener = map.listener; + weigher = map.weigher; + } + + Object readResolve() { + ConcurrentLinkedHashMap map = new Builder().concurrencyLevel(concurrencyLevel) + .maximumWeightedCapacity(capacity).listener(listener).weigher(weigher).build(); + map.putAll(data); + return map; + } + + static final long serialVersionUID = 1; } + /* ---------------- Builder -------------- */ + /** - * Creates a new {@link ConcurrentLinkedHashMap} instance. - * - * @return ConcurrentLinkedHashMap - * @throws IllegalStateException if the maximum weighted capacity was - * not set + * A builder that creates {@link ConcurrentLinkedHashMap} instances. It + * provides a flexible approach for constructing customized instances with + * a named parameter syntax. It can be used in the following manner: + * + *
{@code
+     * ConcurrentMap> graph = new Builder>().maximumWeightedCapacity(5000)
+     *         .weigher(Weighers.set()).build();
+     * }
*/ - public ConcurrentLinkedHashMap build() { - checkState(capacity >= 0); - return new ConcurrentLinkedHashMap(this); + public static final class Builder { + static final int DEFAULT_CONCURRENCY_LEVEL = 16; + static final int DEFAULT_INITIAL_CAPACITY = 16; + + EvictionListener listener; + EntryWeigher weigher; + + int concurrencyLevel; + int initialCapacity; + long capacity; + + @SuppressWarnings("unchecked") + public Builder() { + capacity = -1; + weigher = Weighers.entrySingleton(); + initialCapacity = DEFAULT_INITIAL_CAPACITY; + concurrencyLevel = DEFAULT_CONCURRENCY_LEVEL; + listener = (EvictionListener) DiscardingListener.INSTANCE; + } + + /** + * Specifies the initial capacity of the hash table (default 16). + * This is the number of key-value pairs that the hash table can hold + * before a resize operation is required. + * + * @param initialCapacity + * the initial capacity used to size the hash table + * to accommodate this many entries. + * + * @return Builder + * @throws IllegalArgumentException + * if the initialCapacity is negative + */ + public Builder initialCapacity(int initialCapacity) { + checkArgument(initialCapacity >= 0); + this.initialCapacity = initialCapacity; + return this; + } + + /** + * Specifies the maximum weighted capacity to coerce the map to and may + * exceed it temporarily. + * + * @param capacity + * the weighted threshold to bound the map by + * @return Builder + * @throws IllegalArgumentException + * if the maximumWeightedCapacity is + * negative + */ + public Builder maximumWeightedCapacity(long capacity) { + checkArgument(capacity >= 0); + this.capacity = capacity; + return this; + } + + /** + * Specifies the estimated number of concurrently updating threads. The + * implementation performs internal sizing to try to accommodate this many + * threads (default 16). + * + * @param concurrencyLevel + * the estimated number of concurrently updating + * threads + * @return Builder + * @throws IllegalArgumentException + * if the concurrencyLevel is less than or + * equal to zero + */ + public Builder concurrencyLevel(int concurrencyLevel) { + checkArgument(concurrencyLevel > 0); + this.concurrencyLevel = concurrencyLevel; + return this; + } + + /** + * Specifies an optional listener that is registered for notification when + * an entry is evicted. + * + * @param listener + * the object to forward evicted entries to + * @return Builder + * @throws NullPointerException + * if the listener is null + */ + public Builder listener(EvictionListener listener) { + checkNotNull(listener); + this.listener = listener; + return this; + } + + /** + * Specifies an algorithm to determine how many the units of capacity a + * value consumes. The default algorithm bounds the map by the number of + * key-value pairs by giving each entry a weight of 1. + * + * @param weigher + * the algorithm to determine a value's weight + * @return Builder + * @throws NullPointerException + * if the weigher is null + */ + public Builder weigher(Weigher weigher) { + this.weigher = (weigher == Weighers.singleton()) ? Weighers + .entrySingleton() : new BoundedEntryWeigher(Weighers.asEntryWeigher(weigher)); + return this; + } + + /** + * Specifies an algorithm to determine how many the units of capacity an + * entry consumes. The default algorithm bounds the map by the number of + * key-value pairs by giving each entry a weight of 1. + * + * @param weigher + * the algorithm to determine a entry's weight + * @return Builder + * @throws NullPointerException + * if the weigher is null + */ + public Builder weigher(EntryWeigher weigher) { + this.weigher = (weigher == Weighers.entrySingleton()) ? Weighers.entrySingleton() + : new BoundedEntryWeigher(weigher); + return this; + } + + /** + * Creates a new {@link ConcurrentLinkedHashMap} instance. + * + * @return ConcurrentLinkedHashMap + * @throws IllegalStateException + * if the maximum weighted capacity was + * not set + */ + public ConcurrentLinkedHashMap build() { + checkState(capacity >= 0); + return new ConcurrentLinkedHashMap(this); + } } - } } diff --git a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/EntryWeigher.java b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/EntryWeigher.java index 9bf2a22b0..f5738ae47 100644 --- a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/EntryWeigher.java +++ b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/EntryWeigher.java @@ -1,12 +1,9 @@ /* * Copyright 2012 Google Inc. All Rights Reserved. - * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -25,13 +22,15 @@ */ public interface EntryWeigher { - /** - * Measures an entry's weight to determine how many units of capacity that - * the key and value consumes. An entry must consume a minimum of one unit. - * - * @param key the key to weigh - * @param value the value to weigh - * @return the entry's weight - */ - int weightOf(K key, V value); + /** + * Measures an entry's weight to determine how many units of capacity that + * the key and value consumes. An entry must consume a minimum of one unit. + * + * @param key + * the key to weigh + * @param value + * the value to weigh + * @return the entry's weight + */ + int weightOf(K key, V value); } diff --git a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/EvictionListener.java b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/EvictionListener.java index 65488587c..e9bf16751 100644 --- a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/EvictionListener.java +++ b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/EvictionListener.java @@ -1,12 +1,9 @@ /* * Copyright 2010 Google Inc. All Rights Reserved. - * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -35,11 +32,13 @@ */ public interface EvictionListener { - /** - * A call-back notification that the entry was evicted. - * - * @param key the entry's key - * @param value the entry's value - */ - void onEviction(K key, V value); + /** + * A call-back notification that the entry was evicted. + * + * @param key + * the entry's key + * @param value + * the entry's value + */ + void onEviction(K key, V value); } diff --git a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/Linked.java b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/Linked.java index 21fbc533f..9879994e9 100644 --- a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/Linked.java +++ b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/Linked.java @@ -2,26 +2,27 @@ import java.util.Deque; + /** * An element that is linked on the {@link Deque}. */ interface Linked> { - /** - * Retrieves the previous element or null if either the element is - * unlinked or the first element on the deque. - */ - T getPrevious(); + /** + * Retrieves the previous element or null if either the element is + * unlinked or the first element on the deque. + */ + T getPrevious(); - /** Sets the previous element or null if there is no link. */ - void setPrevious(T prev); + /** Sets the previous element or null if there is no link. */ + void setPrevious(T prev); - /** - * Retrieves the next element or null if either the element is - * unlinked or the last element on the deque. - */ - T getNext(); + /** + * Retrieves the next element or null if either the element is + * unlinked or the last element on the deque. + */ + T getNext(); - /** Sets the next element or null if there is no link. */ - void setNext(T next); + /** Sets the next element or null if there is no link. */ + void setNext(T next); } diff --git a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/LinkedDeque.java b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/LinkedDeque.java index ffa048c9f..a0bd17e65 100644 --- a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/LinkedDeque.java +++ b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/LinkedDeque.java @@ -1,12 +1,9 @@ /* * Copyright 2011 Google Inc. All Rights Reserved. - * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,6 +18,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; + /** * Linked list implementation of the {@link Deque} interface where the link * pointers are tightly integrated with the element. Linked deques have no @@ -39,398 +37,403 @@ * time in the future. * * @author ben.manes@gmail.com (Ben Manes) - * @param the type of elements held in this collection + * @param + * the type of elements held in this collection * @see * http://code.google.com/p/concurrentlinkedhashmap/ */ final class LinkedDeque> extends AbstractCollection implements Deque { - // This class provides a doubly-linked list that is optimized for the virtual - // machine. The first and last elements are manipulated instead of a slightly - // more convenient sentinel element to avoid the insertion of null checks with - // NullPointerException throws in the byte code. The links to a removed - // element are cleared to help a generational garbage collector if the - // discarded elements inhabit more than one generation. - - /** - * Pointer to first node. - * Invariant: (first == null && last == null) || - * (first.prev == null) - */ - E first; - - /** - * Pointer to last node. - * Invariant: (first == null && last == null) || - * (last.next == null) - */ - E last; - - /** - * Links the element to the front of the deque so that it becomes the first - * element. - * - * @param e the unlinked element - */ - void linkFirst(final E e) { - final E f = first; - first = e; - - if (f == null) { - last = e; - } else { - f.setPrevious(e); - e.setNext(f); - } - } - - /** - * Links the element to the back of the deque so that it becomes the last - * element. - * - * @param e the unlinked element - */ - void linkLast(final E e) { - final E l = last; - last = e; - - if (l == null) { - first = e; - } else { - l.setNext(e); - e.setPrevious(l); - } - } - - /** Unlinks the non-null first element. */ - E unlinkFirst() { - final E f = first; - final E next = f.getNext(); - f.setNext(null); - - first = next; - if (next == null) { - last = null; - } else { - next.setPrevious(null); - } - return f; - } - - /** Unlinks the non-null last element. */ - E unlinkLast() { - final E l = last; - final E prev = l.getPrevious(); - l.setPrevious(null); - last = prev; - if (prev == null) { - first = null; - } else { - prev.setNext(null); - } - return l; - } - - /** Unlinks the non-null element. */ - void unlink(E e) { - final E prev = e.getPrevious(); - final E next = e.getNext(); - - if (prev == null) { - first = next; - } else { - prev.setNext(next); - e.setPrevious(null); - } - - if (next == null) { - last = prev; - } else { - next.setPrevious(prev); - e.setNext(null); - } - } - - @Override - public boolean isEmpty() { - return (first == null); - } - - void checkNotEmpty() { - if (isEmpty()) { - throw new NoSuchElementException(); - } - } - - /** - * {@inheritDoc} - *

- * Beware that, unlike in most collections, this method is NOT a - * constant-time operation. - */ - @Override - public int size() { - int size = 0; - for (E e = first; e != null; e = e.getNext()) { - size++; - } - return size; - } - - @Override - public void clear() { - for (E e = first; e != null;) { - E next = e.getNext(); - e.setPrevious(null); - e.setNext(null); - e = next; - } - first = last = null; - } - - @Override - public boolean contains(Object o) { - return (o instanceof Linked) && contains((Linked) o); - } - - // A fast-path containment check - boolean contains(Linked e) { - return (e.getPrevious() != null) - || (e.getNext() != null) - || (e == first); - } - - /** - * Moves the element to the front of the deque so that it becomes the first - * element. - * - * @param e the linked element - */ - public void moveToFront(E e) { - if (e != first) { - unlink(e); - linkFirst(e); - } - } - - /** - * Moves the element to the back of the deque so that it becomes the last - * element. - * - * @param e the linked element - */ - public void moveToBack(E e) { - if (e != last) { - unlink(e); - linkLast(e); - } - } - - @Override - public E peek() { - return peekFirst(); - } - - @Override - public E peekFirst() { - return first; - } - - @Override - public E peekLast() { - return last; - } - - @Override - public E getFirst() { - checkNotEmpty(); - return peekFirst(); - } - - @Override - public E getLast() { - checkNotEmpty(); - return peekLast(); - } - - @Override - public E element() { - return getFirst(); - } - - @Override - public boolean offer(E e) { - return offerLast(e); - } - - @Override - public boolean offerFirst(E e) { - if (contains(e)) { - return false; - } - linkFirst(e); - return true; - } - - @Override - public boolean offerLast(E e) { - if (contains(e)) { - return false; - } - linkLast(e); - return true; - } - - @Override - public boolean add(E e) { - return offerLast(e); - } - - - @Override - public void addFirst(E e) { - if (!offerFirst(e)) { - throw new IllegalArgumentException(); - } - } - - @Override - public void addLast(E e) { - if (!offerLast(e)) { - throw new IllegalArgumentException(); - } - } - - @Override - public E poll() { - return pollFirst(); - } - - @Override - public E pollFirst() { - return isEmpty() ? null : unlinkFirst(); - } - - @Override - public E pollLast() { - return isEmpty() ? null : unlinkLast(); - } - - @Override - public E remove() { - return removeFirst(); - } - - @Override - @SuppressWarnings("unchecked") - public boolean remove(Object o) { - return (o instanceof Linked) && remove((E) o); - } - - // A fast-path removal - boolean remove(E e) { - if (contains(e)) { - unlink(e); - return true; - } - return false; - } - - @Override - public E removeFirst() { - checkNotEmpty(); - return pollFirst(); - } - - @Override - public boolean removeFirstOccurrence(Object o) { - return remove(o); - } - - @Override - public E removeLast() { - checkNotEmpty(); - return pollLast(); - } - - @Override - public boolean removeLastOccurrence(Object o) { - return remove(o); - } - - @Override - public boolean removeAll(Collection c) { - boolean modified = false; - for (Object o : c) { - modified |= remove(o); - } - return modified; - } - - @Override - public void push(E e) { - addFirst(e); - } - - @Override - public E pop() { - return removeFirst(); - } - - @Override - public Iterator iterator() { - return new AbstractLinkedIterator(first) { - @Override E computeNext() { - return cursor.getNext(); - } - }; - } - - @Override - public Iterator descendingIterator() { - return new AbstractLinkedIterator(last) { - @Override E computeNext() { - return cursor.getPrevious(); - } - }; - } - - abstract class AbstractLinkedIterator implements Iterator { - E cursor; + // This class provides a doubly-linked list that is optimized for the virtual + // machine. The first and last elements are manipulated instead of a slightly + // more convenient sentinel element to avoid the insertion of null checks with + // NullPointerException throws in the byte code. The links to a removed + // element are cleared to help a generational garbage collector if the + // discarded elements inhabit more than one generation. + + /** + * Pointer to first node. + * Invariant: (first == null && last == null) || + * (first.prev == null) + */ + E first; + + /** + * Pointer to last node. + * Invariant: (first == null && last == null) || + * (last.next == null) + */ + E last; + + /** + * Links the element to the front of the deque so that it becomes the first + * element. + * + * @param e + * the unlinked element + */ + void linkFirst(final E e) { + final E f = first; + first = e; + + if (f == null) { + last = e; + } else { + f.setPrevious(e); + e.setNext(f); + } + } /** - * Creates an iterator that can can traverse the deque. + * Links the element to the back of the deque so that it becomes the last + * element. * - * @param start the initial element to begin traversal from + * @param e + * the unlinked element */ - AbstractLinkedIterator(E start) { - cursor = start; + void linkLast(final E e) { + final E l = last; + last = e; + + if (l == null) { + first = e; + } else { + l.setNext(e); + e.setPrevious(l); + } + } + + /** Unlinks the non-null first element. */ + E unlinkFirst() { + final E f = first; + final E next = f.getNext(); + f.setNext(null); + + first = next; + if (next == null) { + last = null; + } else { + next.setPrevious(null); + } + return f; + } + + /** Unlinks the non-null last element. */ + E unlinkLast() { + final E l = last; + final E prev = l.getPrevious(); + l.setPrevious(null); + last = prev; + if (prev == null) { + first = null; + } else { + prev.setNext(null); + } + return l; + } + + /** Unlinks the non-null element. */ + void unlink(E e) { + final E prev = e.getPrevious(); + final E next = e.getNext(); + + if (prev == null) { + first = next; + } else { + prev.setNext(next); + e.setPrevious(null); + } + + if (next == null) { + last = prev; + } else { + next.setPrevious(prev); + e.setNext(null); + } } @Override - public boolean hasNext() { - return (cursor != null); + public boolean isEmpty() { + return (first == null); + } + + void checkNotEmpty() { + if (isEmpty()) { + throw new NoSuchElementException(); + } } + /** + * {@inheritDoc} + *

+ * Beware that, unlike in most collections, this method is NOT a + * constant-time operation. + */ @Override - public E next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - E e = cursor; - cursor = computeNext(); - return e; + public int size() { + int size = 0; + for (E e = first; e != null; e = e.getNext()) { + size++; + } + return size; } @Override - public void remove() { - throw new UnsupportedOperationException(); + public void clear() { + for (E e = first; e != null;) { + E next = e.getNext(); + e.setPrevious(null); + e.setNext(null); + e = next; + } + first = last = null; + } + + @Override + public boolean contains(Object o) { + return (o instanceof Linked) && contains((Linked) o); + } + + // A fast-path containment check + boolean contains(Linked e) { + return (e.getPrevious() != null) || (e.getNext() != null) || (e == first); } /** - * Retrieves the next element to traverse to or null if there are - * no more elements. + * Moves the element to the front of the deque so that it becomes the first + * element. + * + * @param e + * the linked element + */ + public void moveToFront(E e) { + if (e != first) { + unlink(e); + linkFirst(e); + } + } + + /** + * Moves the element to the back of the deque so that it becomes the last + * element. + * + * @param e + * the linked element */ - abstract E computeNext(); - } + public void moveToBack(E e) { + if (e != last) { + unlink(e); + linkLast(e); + } + } + + @Override + public E peek() { + return peekFirst(); + } + + @Override + public E peekFirst() { + return first; + } + + @Override + public E peekLast() { + return last; + } + + @Override + public E getFirst() { + checkNotEmpty(); + return peekFirst(); + } + + @Override + public E getLast() { + checkNotEmpty(); + return peekLast(); + } + + @Override + public E element() { + return getFirst(); + } + + @Override + public boolean offer(E e) { + return offerLast(e); + } + + @Override + public boolean offerFirst(E e) { + if (contains(e)) { + return false; + } + linkFirst(e); + return true; + } + + @Override + public boolean offerLast(E e) { + if (contains(e)) { + return false; + } + linkLast(e); + return true; + } + + @Override + public boolean add(E e) { + return offerLast(e); + } + + @Override + public void addFirst(E e) { + if (!offerFirst(e)) { + throw new IllegalArgumentException(); + } + } + + @Override + public void addLast(E e) { + if (!offerLast(e)) { + throw new IllegalArgumentException(); + } + } + + @Override + public E poll() { + return pollFirst(); + } + + @Override + public E pollFirst() { + return isEmpty() ? null : unlinkFirst(); + } + + @Override + public E pollLast() { + return isEmpty() ? null : unlinkLast(); + } + + @Override + public E remove() { + return removeFirst(); + } + + @Override + @SuppressWarnings("unchecked") + public boolean remove(Object o) { + return (o instanceof Linked) && remove((E) o); + } + + // A fast-path removal + boolean remove(E e) { + if (contains(e)) { + unlink(e); + return true; + } + return false; + } + + @Override + public E removeFirst() { + checkNotEmpty(); + return pollFirst(); + } + + @Override + public boolean removeFirstOccurrence(Object o) { + return remove(o); + } + + @Override + public E removeLast() { + checkNotEmpty(); + return pollLast(); + } + + @Override + public boolean removeLastOccurrence(Object o) { + return remove(o); + } + + @Override + public boolean removeAll(Collection c) { + boolean modified = false; + for (Object o : c) { + modified |= remove(o); + } + return modified; + } + + @Override + public void push(E e) { + addFirst(e); + } + + @Override + public E pop() { + return removeFirst(); + } + + @Override + public Iterator iterator() { + return new AbstractLinkedIterator(first) { + @Override + E computeNext() { + return cursor.getNext(); + } + }; + } + + @Override + public Iterator descendingIterator() { + return new AbstractLinkedIterator(last) { + @Override + E computeNext() { + return cursor.getPrevious(); + } + }; + } + + abstract class AbstractLinkedIterator implements Iterator { + E cursor; + + /** + * Creates an iterator that can can traverse the deque. + * + * @param start + * the initial element to begin traversal from + */ + AbstractLinkedIterator(E start) { + cursor = start; + } + + @Override + public boolean hasNext() { + return (cursor != null); + } + + @Override + public E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + E e = cursor; + cursor = computeNext(); + return e; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * Retrieves the next element to traverse to or null if there are + * no more elements. + */ + abstract E computeNext(); + } } diff --git a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/Weigher.java b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/Weigher.java index 529622c8e..cdf10c14d 100644 --- a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/Weigher.java +++ b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/Weigher.java @@ -1,12 +1,9 @@ /* * Copyright 2010 Google Inc. All Rights Reserved. - * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -25,12 +22,13 @@ */ public interface Weigher { - /** - * Measures an object's weight to determine how many units of capacity that - * the value consumes. A value must consume a minimum of one unit. - * - * @param value the object to weigh - * @return the object's weight - */ - int weightOf(V value); + /** + * Measures an object's weight to determine how many units of capacity that + * the value consumes. A value must consume a minimum of one unit. + * + * @param value + * the object to weigh + * @return the object's weight + */ + int weightOf(V value); } diff --git a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/Weighers.java b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/Weighers.java index b6de5f12b..2522af45f 100644 --- a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/Weighers.java +++ b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/Weighers.java @@ -1,12 +1,9 @@ /* * Copyright 2010 Google Inc. All Rights Reserved. - * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,6 +20,7 @@ import java.util.Map; import java.util.Set; + /** * A common set of {@link Weigher} and {@link EntryWeigher} implementations. * @@ -32,260 +30,269 @@ */ public final class Weighers { - private Weighers() { - throw new AssertionError(); - } - - /** - * A entry weigher backed by the specified weigher. The weight of the value - * determines the weight of the entry. - * - * @param K - * @param V - * @param weigher the weigher to be "wrapped" in a entry weigher. - * @return A entry weigher view of the specified weigher. - */ - public static EntryWeigher asEntryWeigher( - final Weigher weigher) { - return (weigher == singleton()) - ? Weighers.entrySingleton() - : new EntryWeigherView(weigher); - } - - /** - * A weigher where an entry has a weight of 1. A map bounded with - * this weigher will evict when the number of key-value pairs exceeds the - * capacity. - * - * @param K - * @param V - * @return A weigher where a value takes one unit of capacity. - */ - @SuppressWarnings({"cast", "unchecked"}) - public static EntryWeigher entrySingleton() { - return (EntryWeigher) SingletonEntryWeigher.INSTANCE; - } - - /** - * A weigher where a value has a weight of 1. A map bounded with - * this weigher will evict when the number of key-value pairs exceeds the - * capacity. - * - * @param V - * @return A weigher where a value takes one unit of capacity. - */ - @SuppressWarnings({"cast", "unchecked"}) - public static Weigher singleton() { - return (Weigher) SingletonWeigher.INSTANCE; - } - - /** - * A weigher where the value is a byte array and its weight is the number of - * bytes. A map bounded with this weigher will evict when the number of bytes - * exceeds the capacity rather than the number of key-value pairs in the map. - * This allows for restricting the capacity based on the memory-consumption - * and is primarily for usage by dedicated caching servers that hold the - * serialized data. - *

- * A value with a weight of 0 will be rejected by the map. If a value - * with this weight can occur then the caller should eagerly evaluate the - * value and treat it as a removal operation. Alternatively, a custom weigher - * may be specified on the map to assign an empty value a positive weight. - * - * @return A weigher where each byte takes one unit of capacity. - */ - public static Weigher byteArray() { - return ByteArrayWeigher.INSTANCE; - } - - /** - * A weigher where the value is a {@link Iterable} and its weight is the - * number of elements. This weigher only should be used when the alternative - * {@link #collection()} weigher cannot be, as evaluation takes O(n) time. A - * map bounded with this weigher will evict when the total number of elements - * exceeds the capacity rather than the number of key-value pairs in the map. - *

- * A value with a weight of 0 will be rejected by the map. If a value - * with this weight can occur then the caller should eagerly evaluate the - * value and treat it as a removal operation. Alternatively, a custom weigher - * may be specified on the map to assign an empty value a positive weight. - * - * @param E - * @return A weigher where each element takes one unit of capacity. - */ - @SuppressWarnings({"cast", "unchecked"}) - public static Weigher> iterable() { - return (Weigher>) (Weigher) IterableWeigher.INSTANCE; - } - - /** - * A weigher where the value is a {@link Collection} and its weight is the - * number of elements. A map bounded with this weigher will evict when the - * total number of elements exceeds the capacity rather than the number of - * key-value pairs in the map. - *

- * A value with a weight of 0 will be rejected by the map. If a value - * with this weight can occur then the caller should eagerly evaluate the - * value and treat it as a removal operation. Alternatively, a custom weigher - * may be specified on the map to assign an empty value a positive weight. - * - * @param E - * @return A weigher where each element takes one unit of capacity. - */ - @SuppressWarnings({"cast", "unchecked"}) - public static Weigher> collection() { - return (Weigher>) (Weigher) CollectionWeigher.INSTANCE; - } - - /** - * A weigher where the value is a {@link List} and its weight is the number - * of elements. A map bounded with this weigher will evict when the total - * number of elements exceeds the capacity rather than the number of - * key-value pairs in the map. - *

- * A value with a weight of 0 will be rejected by the map. If a value - * with this weight can occur then the caller should eagerly evaluate the - * value and treat it as a removal operation. Alternatively, a custom weigher - * may be specified on the map to assign an empty value a positive weight. - * - * @param E - * @return A weigher where each element takes one unit of capacity. - */ - @SuppressWarnings({"cast", "unchecked"}) - public static Weigher> list() { - return (Weigher>) (Weigher) ListWeigher.INSTANCE; - } - - /** - * A weigher where the value is a {@link Set} and its weight is the number - * of elements. A map bounded with this weigher will evict when the total - * number of elements exceeds the capacity rather than the number of - * key-value pairs in the map. - *

- * A value with a weight of 0 will be rejected by the map. If a value - * with this weight can occur then the caller should eagerly evaluate the - * value and treat it as a removal operation. Alternatively, a custom weigher - * may be specified on the map to assign an empty value a positive weight. - * - * @param E - * @return A weigher where each element takes one unit of capacity. - */ - @SuppressWarnings({"cast", "unchecked"}) - public static Weigher> set() { - return (Weigher>) (Weigher) SetWeigher.INSTANCE; - } - - /** - * A weigher where the value is a {@link Map} and its weight is the number of - * entries. A map bounded with this weigher will evict when the total number of - * entries across all values exceeds the capacity rather than the number of - * key-value pairs in the map. - *

- * A value with a weight of 0 will be rejected by the map. If a value - * with this weight can occur then the caller should eagerly evaluate the - * value and treat it as a removal operation. Alternatively, a custom weigher - * may be specified on the map to assign an empty value a positive weight. - * - * @param A - * @param B - * @return A weigher where each entry takes one unit of capacity. - */ - @SuppressWarnings({"cast", "unchecked"}) - public static Weigher> map() { - return (Weigher>) (Weigher) MapWeigher.INSTANCE; - } - - static final class EntryWeigherView implements EntryWeigher, Serializable { - static final long serialVersionUID = 1; - final Weigher weigher; - - EntryWeigherView(Weigher weigher) { - checkNotNull(weigher); - this.weigher = weigher; + private Weighers() { + throw new AssertionError(); + } + + /** + * A entry weigher backed by the specified weigher. The weight of the value + * determines the weight of the entry. + * + * @param + * K + * @param + * V + * @param weigher + * the weigher to be "wrapped" in a entry weigher. + * @return A entry weigher view of the specified weigher. + */ + public static EntryWeigher asEntryWeigher(final Weigher weigher) { + return (weigher == singleton()) ? Weighers.entrySingleton() : new EntryWeigherView(weigher); + } + + /** + * A weigher where an entry has a weight of 1. A map bounded with + * this weigher will evict when the number of key-value pairs exceeds the + * capacity. + * + * @param + * K + * @param + * V + * @return A weigher where a value takes one unit of capacity. + */ + @SuppressWarnings({"cast", "unchecked"}) + public static EntryWeigher entrySingleton() { + return (EntryWeigher) SingletonEntryWeigher.INSTANCE; + } + + /** + * A weigher where a value has a weight of 1. A map bounded with + * this weigher will evict when the number of key-value pairs exceeds the + * capacity. + * + * @param + * V + * @return A weigher where a value takes one unit of capacity. + */ + @SuppressWarnings({"cast", "unchecked"}) + public static Weigher singleton() { + return (Weigher) SingletonWeigher.INSTANCE; + } + + /** + * A weigher where the value is a byte array and its weight is the number of + * bytes. A map bounded with this weigher will evict when the number of bytes + * exceeds the capacity rather than the number of key-value pairs in the map. + * This allows for restricting the capacity based on the memory-consumption + * and is primarily for usage by dedicated caching servers that hold the + * serialized data. + *

+ * A value with a weight of 0 will be rejected by the map. If a value + * with this weight can occur then the caller should eagerly evaluate the + * value and treat it as a removal operation. Alternatively, a custom weigher + * may be specified on the map to assign an empty value a positive weight. + * + * @return A weigher where each byte takes one unit of capacity. + */ + public static Weigher byteArray() { + return ByteArrayWeigher.INSTANCE; + } + + /** + * A weigher where the value is a {@link Iterable} and its weight is the + * number of elements. This weigher only should be used when the alternative + * {@link #collection()} weigher cannot be, as evaluation takes O(n) time. A + * map bounded with this weigher will evict when the total number of elements + * exceeds the capacity rather than the number of key-value pairs in the map. + *

+ * A value with a weight of 0 will be rejected by the map. If a value + * with this weight can occur then the caller should eagerly evaluate the + * value and treat it as a removal operation. Alternatively, a custom weigher + * may be specified on the map to assign an empty value a positive weight. + * + * @param + * E + * @return A weigher where each element takes one unit of capacity. + */ + @SuppressWarnings({"cast", "unchecked"}) + public static Weigher> iterable() { + return (Weigher>) (Weigher) IterableWeigher.INSTANCE; + } + + /** + * A weigher where the value is a {@link Collection} and its weight is the + * number of elements. A map bounded with this weigher will evict when the + * total number of elements exceeds the capacity rather than the number of + * key-value pairs in the map. + *

+ * A value with a weight of 0 will be rejected by the map. If a value + * with this weight can occur then the caller should eagerly evaluate the + * value and treat it as a removal operation. Alternatively, a custom weigher + * may be specified on the map to assign an empty value a positive weight. + * + * @param + * E + * @return A weigher where each element takes one unit of capacity. + */ + @SuppressWarnings({"cast", "unchecked"}) + public static Weigher> collection() { + return (Weigher>) (Weigher) CollectionWeigher.INSTANCE; + } + + /** + * A weigher where the value is a {@link List} and its weight is the number + * of elements. A map bounded with this weigher will evict when the total + * number of elements exceeds the capacity rather than the number of + * key-value pairs in the map. + *

+ * A value with a weight of 0 will be rejected by the map. If a value + * with this weight can occur then the caller should eagerly evaluate the + * value and treat it as a removal operation. Alternatively, a custom weigher + * may be specified on the map to assign an empty value a positive weight. + * + * @param + * E + * @return A weigher where each element takes one unit of capacity. + */ + @SuppressWarnings({"cast", "unchecked"}) + public static Weigher> list() { + return (Weigher>) (Weigher) ListWeigher.INSTANCE; + } + + /** + * A weigher where the value is a {@link Set} and its weight is the number + * of elements. A map bounded with this weigher will evict when the total + * number of elements exceeds the capacity rather than the number of + * key-value pairs in the map. + *

+ * A value with a weight of 0 will be rejected by the map. If a value + * with this weight can occur then the caller should eagerly evaluate the + * value and treat it as a removal operation. Alternatively, a custom weigher + * may be specified on the map to assign an empty value a positive weight. + * + * @param + * E + * @return A weigher where each element takes one unit of capacity. + */ + @SuppressWarnings({"cast", "unchecked"}) + public static Weigher> set() { + return (Weigher>) (Weigher) SetWeigher.INSTANCE; } - @Override - public int weightOf(K key, V value) { - return weigher.weightOf(value); + /** + * A weigher where the value is a {@link Map} and its weight is the number of + * entries. A map bounded with this weigher will evict when the total number of + * entries across all values exceeds the capacity rather than the number of + * key-value pairs in the map. + *

+ * A value with a weight of 0 will be rejected by the map. If a value + * with this weight can occur then the caller should eagerly evaluate the + * value and treat it as a removal operation. Alternatively, a custom weigher + * may be specified on the map to assign an empty value a positive weight. + * + * @param + * A + * @param + * B + * @return A weigher where each entry takes one unit of capacity. + */ + @SuppressWarnings({"cast", "unchecked"}) + public static Weigher> map() { + return (Weigher>) (Weigher) MapWeigher.INSTANCE; } - } - enum SingletonEntryWeigher implements EntryWeigher { - INSTANCE; + static final class EntryWeigherView implements EntryWeigher, Serializable { + static final long serialVersionUID = 1; + final Weigher weigher; + + EntryWeigherView(Weigher weigher) { + checkNotNull(weigher); + this.weigher = weigher; + } - @Override - public int weightOf(Object key, Object value) { - return 1; + @Override + public int weightOf(K key, V value) { + return weigher.weightOf(value); + } } - } - enum SingletonWeigher implements Weigher { - INSTANCE; + enum SingletonEntryWeigher implements EntryWeigher { + INSTANCE; - @Override - public int weightOf(Object value) { - return 1; + @Override + public int weightOf(Object key, Object value) { + return 1; + } } - } - enum ByteArrayWeigher implements Weigher { - INSTANCE; + enum SingletonWeigher implements Weigher { + INSTANCE; - @Override - public int weightOf(byte[] value) { - return value.length; + @Override + public int weightOf(Object value) { + return 1; + } } - } - - enum IterableWeigher implements Weigher> { - INSTANCE; - - @Override - public int weightOf(Iterable values) { - if (values instanceof Collection) { - return ((Collection) values).size(); - } - int size = 0; - for (Object value : values) { - size++; + + enum ByteArrayWeigher implements Weigher { + INSTANCE; + + @Override + public int weightOf(byte[] value) { + return value.length; + } + } + + enum IterableWeigher implements Weigher> { + INSTANCE; + + @Override + public int weightOf(Iterable values) { + if (values instanceof Collection) { + return ((Collection) values).size(); + } + int size = 0; + for (Object value : values) { + size++; + } + return size; } - return size; } - } - enum CollectionWeigher implements Weigher> { - INSTANCE; + enum CollectionWeigher implements Weigher> { + INSTANCE; - @Override - public int weightOf(Collection values) { - return values.size(); + @Override + public int weightOf(Collection values) { + return values.size(); + } } - } - enum ListWeigher implements Weigher> { - INSTANCE; + enum ListWeigher implements Weigher> { + INSTANCE; - @Override - public int weightOf(List values) { - return values.size(); + @Override + public int weightOf(List values) { + return values.size(); + } } - } - enum SetWeigher implements Weigher> { - INSTANCE; + enum SetWeigher implements Weigher> { + INSTANCE; - @Override - public int weightOf(Set values) { - return values.size(); + @Override + public int weightOf(Set values) { + return values.size(); + } } - } - enum MapWeigher implements Weigher> { - INSTANCE; + enum MapWeigher implements Weigher> { + INSTANCE; - @Override - public int weightOf(Map values) { - return values.size(); + @Override + public int weightOf(Map values) { + return values.size(); + } } - } } diff --git a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/package-info.java b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/package-info.java index 5e0e55095..23f26480e 100644 --- a/src/main/java/mssql/googlecode/concurrentlinkedhashmap/package-info.java +++ b/src/main/java/mssql/googlecode/concurrentlinkedhashmap/package-info.java @@ -1,11 +1,8 @@ /* * Copyright 2011 Google Inc. All Rights Reserved. - * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at - * * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations * under the License. diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java index 82ec693e0..d8725cdbe 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java @@ -44,8 +44,8 @@ public class RegressionAlwaysEncryptedTest extends AESetup { @MethodSource("enclaveParams") public void alwaysEncrypted1(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); - try (Connection connection = PrepUtil.getConnection( - AETestConnectionString + ";columnEncryptionSetting=enabled;", AEInfo); + try (Connection connection = PrepUtil + .getConnection(AETestConnectionString + ";columnEncryptionSetting=enabled;", AEInfo); Statement stmt = connection.createStatement()) { dropTables(stmt); @@ -74,8 +74,8 @@ public void alwaysEncrypted1(String serverName, String url, String protocol) thr @MethodSource("enclaveParams") public void alwaysEncrypted2(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); - try (Connection connection = PrepUtil.getConnection( - AETestConnectionString + ";columnEncryptionSetting=enabled;", AEInfo); + try (Connection connection = PrepUtil + .getConnection(AETestConnectionString + ";columnEncryptionSetting=enabled;", AEInfo); Statement stmt = connection.createStatement()) { dropTables(stmt); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/KerberosTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/KerberosTest.java index 212839558..47f0a9252 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/KerberosTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/KerberosTest.java @@ -16,6 +16,7 @@ import java.util.HashMap; import java.util.Map; + @Tag(Constants.kerberos) @RunWith(JUnitPlatform.class) public class KerberosTest extends AbstractTest { @@ -59,8 +60,8 @@ public void testUseDefaultJaasConfigConnectionStringPropertyFalse() throws Excep createKerberosConnection(connectionStringKerberos); Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (SQLServerException e) { - Assertions.assertTrue(e.getMessage() - .contains(TestResource.getResource("R_noLoginModulesConfiguredForJdbcDriver"))); + Assertions.assertTrue( + e.getMessage().contains(TestResource.getResource("R_noLoginModulesConfiguredForJdbcDriver"))); } } @@ -96,12 +97,10 @@ private static void createKerberosConnection(String connectionString) throws Exc */ private static void overwriteJaasConfig() { AppConfigurationEntry kafkaClientConfigurationEntry = new AppConfigurationEntry( - "com.sun.security.auth.module.Krb5LoginModule", - AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, + "com.sun.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, new HashMap<>()); Map configurationEntries = new HashMap<>(); - configurationEntries.put("CLIENT_CONTEXT_NAME", - new AppConfigurationEntry[] { kafkaClientConfigurationEntry }); + configurationEntries.put("CLIENT_CONTEXT_NAME", new AppConfigurationEntry[] {kafkaClientConfigurationEntry}); Configuration.setConfiguration(new InternalConfiguration(configurationEntries)); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/MaxResultBufferParserTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/MaxResultBufferParserTest.java index ebfea27eb..b46ddd206 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/MaxResultBufferParserTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/MaxResultBufferParserTest.java @@ -25,41 +25,25 @@ class MaxResultBufferParserTest { * Method with input data for testValidateMaxResultBuffer Tests */ public static Iterable data() { - return Arrays.asList(new Object[][] { - {"10p", (long) (0.1 * getMaxMemory())}, - {"010p", (long) (0.1 * getMaxMemory())}, - {"10pct", (long) (0.1 * getMaxMemory())}, - {"10percent", (long) (0.1 * getMaxMemory())}, - {"100", 100}, - {"0100", 100}, - {"100k", 100 * 1000}, - {"0100k", 100 * 1000}, - {"100K", 100 * 1000}, - {"0100K", 100 * 1000}, - {"100m", 100 * 1000 * 1000}, - {"100M", 100 * 1000 * 1000}, - // these values are too big (assuming heap size is 4GB) - {"200p", (long) (0.9 * getMaxMemory())}, - {"0200p", (long) (0.9 * getMaxMemory())}, - {"200pct", (long) (0.9 * getMaxMemory())}, - {"200percent", (long) (0.9 * getMaxMemory())}, - {"100g", (long) (0.9 * getMaxMemory())}, - {"100G", (long) (0.9 * getMaxMemory())}, - {"100t", (long) (0.9 * getMaxMemory())}, - {"100T", (long) (0.9 * getMaxMemory())}, - //when maxResultBuffer property is not supplied, assume -1 - {"", -1}, - {null, -1}, - {"-1", -1}, - }); + return Arrays + .asList(new Object[][] {{"10p", (long) (0.1 * getMaxMemory())}, {"010p", (long) (0.1 * getMaxMemory())}, + {"10pct", (long) (0.1 * getMaxMemory())}, {"10percent", (long) (0.1 * getMaxMemory())}, + {"100", 100}, {"0100", 100}, {"100k", 100 * 1000}, {"0100k", 100 * 1000}, {"100K", 100 * 1000}, + {"0100K", 100 * 1000}, {"100m", 100 * 1000 * 1000}, {"100M", 100 * 1000 * 1000}, + // these values are too big (assuming heap size is 4GB) + {"200p", (long) (0.9 * getMaxMemory())}, {"0200p", (long) (0.9 * getMaxMemory())}, + {"200pct", (long) (0.9 * getMaxMemory())}, {"200percent", (long) (0.9 * getMaxMemory())}, + {"100g", (long) (0.9 * getMaxMemory())}, {"100G", (long) (0.9 * getMaxMemory())}, + {"100t", (long) (0.9 * getMaxMemory())}, {"100T", (long) (0.9 * getMaxMemory())}, + // when maxResultBuffer property is not supplied, assume -1 + {"", -1}, {null, -1}, {"-1", -1},}); } /** * Method with input data for testValidateMaxResultBufferException Tests */ public static Iterable exceptionData() { - return Arrays.asList(new Object[][] { - {"-123p"}, {"-423pct"}, {"-100m"}, {"-500K"}, {"-123"},// values are correctly formatted, but they're negative + return Arrays.asList(new Object[][] {{"-123p"}, {"-423pct"}, {"-100m"}, {"-500K"}, {"-123"}, // values are correctly formatted, but they're negative {"123precd"}, {"456pc"}, // percent phrases are misspelled {"32P"}, {"-456PCT"}, {"150PERCENT"}, // percent phrases are correct, but they're in upper Case also middle one is negative {"0101D"}, {"100l"}, {"-100L"}, // incorrect prefixes, last value is also negative diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java index 9736254d4..418c1332d 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java @@ -489,7 +489,7 @@ public void testConnectCountInLoginAndCorrectRetryCount() { // Maximum is unknown, but is needs to be less than longLoginTimeout or else this is an issue. assertTrue(totalTime > expectedMinimumTimeInMillis, TestResource.getResource("R_executionNotLong") + " totalTime: " + totalTime + " expectedTime: " + expectedMinimumTimeInMillis); - assertTrue( totalTime < (longLoginTimeout * 1000L), TestResource.getResource("R_executionTooLong") + assertTrue(totalTime < (longLoginTimeout * 1000L), TestResource.getResource("R_executionTooLong") + "totalTime: " + totalTime + " expectedTime: " + expectedMinimumTimeInMillis); } } @@ -526,7 +526,7 @@ public void testConnectCountInLoginAndCorrectRetryCountWithZeroRetry() { assertTrue(totalTime < (longLoginTimeout * 1000L), TestResource.getResource("R_executionTooLong")); } } - + @Test @Tag(Constants.xAzureSQLDW) @Tag(Constants.xAzureSQLDB) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SSLCertificateValidationTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SSLCertificateValidationTest.java index 1f0de5d97..25e5aac14 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SSLCertificateValidationTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SSLCertificateValidationTest.java @@ -37,7 +37,8 @@ public void testValidateServerName() throws Exception { Constructor constructor = hsoClass.getDeclaredConstructors()[0]; constructor.setAccessible(true); Object hsoObject = constructor.newInstance(tdsc, null, serverName); - Method method = SQLServerCertificateUtils.class.getDeclaredMethod("validateServerName", String.class, String.class); + Method method = SQLServerCertificateUtils.class.getDeclaredMethod("validateServerName", String.class, + String.class); method.setAccessible(true); /* diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SharedTimerTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SharedTimerTest.java index d7a8c5ce8..6aa8bff2e 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SharedTimerTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SharedTimerTest.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.concurrent.*; + class SharedTimerTest { @Test @@ -23,4 +24,4 @@ void getTimer() throws InterruptedException, ExecutionException, TimeoutExceptio executor.shutdown(); } } -} \ No newline at end of file +} diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java index 449ea1f0b..cce9a3a89 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java @@ -56,8 +56,10 @@ protected Object[][] getContents() { {"R_createDropAlterTableFailed", "Create/drop/alter table with preparedStatement failed!"}, {"R_grantFailed", "grant table with preparedStatement failed!"}, {"R_connectionIsClosed", "The connection is closed."}, - {"R_noNamedAndIndexedParameters", "Detected uncompliant use of both named and indexed parameters while 'useFlexibleCallableStatements=false'. It is suggested to either exclusively use named parameters or indexed parameters."}, - {"R_unknownOutputParameter", "Cannot acquire output parameter value by name. No parameter index was associated with the output parameter name. If acquiring output parameter by name, verify that the output parameter was initially registered by name."}, + {"R_noNamedAndIndexedParameters", + "Detected uncompliant use of both named and indexed parameters while 'useFlexibleCallableStatements=false'. It is suggested to either exclusively use named parameters or indexed parameters."}, + {"R_unknownOutputParameter", + "Cannot acquire output parameter value by name. No parameter index was associated with the output parameter name. If acquiring output parameter by name, verify that the output parameter was initially registered by name."}, {"R_ConnectionURLNull", "The connection URL is null."}, {"R_connectionIsNotClosed", "The connection is not closed."}, {"R_invalidExceptionMessage", "Invalid exception message"}, diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java index 835bab8ef..ef855eef8 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java @@ -40,7 +40,7 @@ public void testLongConversions() { writeAndReadLong(Long.MAX_VALUE / 2); writeAndReadLong(Long.MAX_VALUE); } - + @Test public void testparseUrl() throws SQLException { java.util.logging.Logger drLogger = java.util.logging.Logger @@ -51,15 +51,15 @@ public void testparseUrl() throws SQLException { assertEquals(prt.getProperty("serverName"), "localhost"); assertEquals(prt.getProperty("user"), "username"); assertEquals(prt.getProperty("databaseName"), "database"); - + constr = "jdbc:sqlserver://localhost;password={pasS}}}"; prt = Util.parseUrl(constr, drLogger); assertEquals(prt.getProperty("password"), "pasS}"); - + constr = "jdbc:sqlserver://localhost;password={pasS}}} "; prt = Util.parseUrl(constr, drLogger); assertEquals(prt.getProperty("password"), "pasS}"); - + constr = "jdbc:sqlserver://localhost;password={pasS}}} ;"; prt = Util.parseUrl(constr, drLogger); assertEquals(prt.getProperty("password"), "pasS}"); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java index a7e69b014..660406ce5 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java @@ -124,15 +124,15 @@ private void terminateVariation() throws SQLException { private static final int DATETIME_COL_COUNT = 2; private static final int DATETIME_ROW_COUNT = 1; - private static final String dateTimeTestTable = - AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("bulkCopyTimestampTest")); + private static final String dateTimeTestTable = AbstractSQLGenerator + .escapeIdentifier(RandomUtil.getIdentifier("bulkCopyTimestampTest")); @Test public void testBulkCopyTimestamp() throws SQLException { List timeStamps = new ArrayList<>(); try (Connection con = getConnection(); Statement stmt = connection.createStatement()) { - String colSpec = IntStream.range(1, DATETIME_COL_COUNT + 1).mapToObj(x -> String.format("c%d datetime", x)).collect( - Collectors.joining(",")); + String colSpec = IntStream.range(1, DATETIME_COL_COUNT + 1).mapToObj(x -> String.format("c%d datetime", x)) + .collect(Collectors.joining(",")); String sql1 = String.format("create table %s (%s)", dateTimeTestTable, colSpec); stmt.execute(sql1); @@ -155,7 +155,7 @@ public void testBulkCopyTimestamp() throws SQLException { crs.moveToInsertRow(); for (int i = 1; i <= DATETIME_COL_COUNT; i++) { - crs.updateTimestamp(i, timeStamps.get(i - 1)); + crs.updateTimestamp(i, timeStamps.get(i - 1)); } crs.insertRow(); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyColumnMappingTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyColumnMappingTest.java index f34c55861..a8bd8e3be 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyColumnMappingTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyColumnMappingTest.java @@ -519,7 +519,7 @@ private void validateNMapping(String sourceType, String destType, sourceStmt.executeUpdate("INSERT INTO " + sourceTable + " VALUES('" + data + "');"); destStmt.executeUpdate("CREATE TABLE " + destTable + " (col " + destType + ");"); - + ResultSet sourceRs = sourceStmt.executeQuery("SELECT * FROM " + sourceTable); bulkCopy.writeToServer(sourceRs); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java index cc0115107..56ee5c1dd 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java @@ -78,7 +78,7 @@ public void testISQLServerBulkRecord() throws SQLException { } } } - + @Test public void testBulkCopyDateTimePrecision() throws SQLException { String dstTable = TestUtils @@ -255,7 +255,7 @@ public void reset() { counter = 0; } } - + private static class BulkRecordDT implements ISQLServerBulkData { boolean anyMoreData = true; Object[] data; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyRowSetTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyRowSetTest.java index c3e55cb8f..b69706d65 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyRowSetTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyRowSetTest.java @@ -80,7 +80,7 @@ public void testBulkCopyJapaneseCollation() throws SQLException { RowSetFactory rsf = RowSetProvider.newFactory(); CachedRowSet crs = rsf.createCachedRowSet(); RowSetMetaData rsmd = new RowSetMetaDataImpl(); - String unicodeData = "ああ"; + String unicodeData = "ああ"; rsmd.setColumnCount(1); rsmd.setColumnName(1, "c1"); rsmd.setColumnType(1, java.sql.Types.VARCHAR); @@ -94,7 +94,7 @@ public void testBulkCopyJapaneseCollation() throws SQLException { bulkCopy.setDestinationTableName(tableName2); bulkCopy.writeToServer(crs); - + try (ResultSet rs = stmt.executeQuery("select * from " + tableName2)) { rs.next(); assertEquals(unicodeData, (String) rs.getString(1)); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/ISQLServerBulkRecordIssuesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/ISQLServerBulkRecordIssuesTest.java index 22dab1005..e387e1403 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/ISQLServerBulkRecordIssuesTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/ISQLServerBulkRecordIssuesTest.java @@ -367,6 +367,7 @@ public void afterEachTests() throws SQLException { } } + class BulkData implements ISQLServerBulkData { private static final long serialVersionUID = 1L; boolean isStringData = false; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java index e9d73b795..c93f3b1c3 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java @@ -439,7 +439,8 @@ public void testNonOrderedRegisteringAndSettingOfParams() throws SQLException { @Test public void testNamedParametersUseFlexibleCallableStatementFalse() throws SQLException { String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; - String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), + "useFlexibleCallableStatements", "false"); // useFlexibleCallableStatement=false and using all named parameters try (Connection conn = DriverManager.getConnection(connectionString)) { @@ -490,7 +491,8 @@ public void testNamedParametersUseFlexibleCallableStatementFalse() throws SQLExc @Test public void testNamedParametersAcquireOutputParamValuesByIndexUseFlexibleCallableStatementFalse() throws SQLException { String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; - String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), + "useFlexibleCallableStatements", "false"); // useFlexibleCallableStatement=false // Setting parameters by name @@ -544,7 +546,8 @@ public void testNamedParametersAcquireOutputParamValuesByIndexUseFlexibleCallabl @Test public void testNamedParametersAndOutParamByIndexUseFlexibleCallableStatementFalse() throws SQLException { String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; - String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), + "useFlexibleCallableStatements", "false"); // useFlexibleCallableStatement=false // Setting parameters by name @@ -598,7 +601,8 @@ public void testNamedParametersAndOutParamByIndexUseFlexibleCallableStatementFal @Test public void testNamedParameterOutputParameterErrorFlexibleCallableStatementFalse() throws SQLException { String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; - String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), + "useFlexibleCallableStatements", "false"); // useFlexibleCallableStatement=false // Setting parameters by name @@ -656,7 +660,8 @@ public void testNamedParameterOutputParameterErrorFlexibleCallableStatementFalse @Test public void testNamedParametersAndByIndexErrorUseFlexibleCallableStatementFalse() throws SQLException { String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; - String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), + "useFlexibleCallableStatements", "false"); // useFlexibleCallableStatement=false // Using majority named parameters and setting parameter by index @@ -712,7 +717,8 @@ public void testNamedParametersAndByIndexErrorUseFlexibleCallableStatementFalse( @Test public void testIndexedParametersUseFlexibleCallableStatementFalse() throws SQLException { String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; - String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), + "useFlexibleCallableStatements", "false"); // useFlexibleCallableStatement=false and using all index parameters try (Connection conn = DriverManager.getConnection(connectionString)) { @@ -763,7 +769,8 @@ public void testIndexedParametersUseFlexibleCallableStatementFalse() throws SQLE @Test public void testIndexedParametersAcquireOutputValueByNameErrorUseFlexibleCallableStatementFalse() throws SQLException { String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; - String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), + "useFlexibleCallableStatements", "false"); try (Connection conn = DriverManager.getConnection(connectionString)) { try (CallableStatement cstmt = conn.prepareCall(call)) { @@ -816,7 +823,8 @@ public void testIndexedParametersAcquireOutputValueByNameErrorUseFlexibleCallabl @Test public void testIndexedParametersRegisterOutputParamByNameUseFlexibleCallableStatementFalse() throws SQLException { String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; - String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), + "useFlexibleCallableStatements", "false"); try (Connection conn = DriverManager.getConnection(connectionString)) { try (CallableStatement cstmt = conn.prepareCall(call)) { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/DBMetadataTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/DBMetadataTest.java index dc18afcac..071578d67 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/DBMetadataTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/DBMetadataTest.java @@ -58,7 +58,8 @@ public void testDatabaseMetaData() throws SQLException { DatabaseMetaData md = con.getMetaData(); try (ResultSet arguments = md.getProcedureColumns(null, null, functionName, "@TABLE_RETURN_VALUE")) { if (arguments.next()) { - assertTrue(arguments.getString("PROCEDURE_NAME").startsWith(functionName), "Expected Stored Procedure was not retrieved."); + assertTrue(arguments.getString("PROCEDURE_NAME").startsWith(functionName), + "Expected Stored Procedure was not retrieved."); } else { assertTrue(false, "Expected Stored Procedure was not found."); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/ReplicationTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/ReplicationTest.java index afbf560aa..bceee3516 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/ReplicationTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/ReplicationTest.java @@ -25,6 +25,7 @@ import com.microsoft.sqlserver.testframework.AbstractTest; import com.microsoft.sqlserver.testframework.Constants; + /* * This test is for testing the replication connection property */ @@ -49,11 +50,8 @@ public void testReplication() throws SQLException { String sqlCreateTable = "CREATE TABLE " + escapedTableName + " ([TestReplication] [varchar](50) NULL)"; String sqlCreateTrigger = "CREATE TRIGGER " + escapedTriggerName + " ON " + escapedTableName + " " - + "INSTEAD OF INSERT NOT FOR REPLICATION AS " - + "BEGIN " - + " INSERT INTO " + escapedTableName + " (TestReplication) " - + " SELECT TestReplication + ' - REPLICATION IS OFF' " - + " FROM INSERTED " + + "INSTEAD OF INSERT NOT FOR REPLICATION AS " + "BEGIN " + " INSERT INTO " + escapedTableName + + " (TestReplication) " + " SELECT TestReplication + ' - REPLICATION IS OFF' " + " FROM INSERTED " + "END"; String sqlInsert = "INSERT INTO " + escapedTableName + " (TestReplication) values ('Replication test')"; String sqlSelect = "SELECT TestReplication FROM " + escapedTableName; @@ -87,5 +85,5 @@ public void testReplication() throws SQLException { } TestUtils.dropTableIfExists(escapedTableName, stmt); } - } + } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java index e02be6e4c..a4b964ecd 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java @@ -100,53 +100,62 @@ public void testModifiableConnectionProperties() throws SQLException { setConnectionFields(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1, sendTimeAsDatetime1, statementPoolingCacheSize1, disableStatementPooling1, serverPreparedStatementDiscardThreshold1, enablePrepareOnFirstPreparedStatementCall1, sCatalog1, - useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, ignoreOffsetOnDateTimeOffsetConversion1); + useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, + ignoreOffsetOnDateTimeOffsetConversion1); con.beginRequest(); // Call setters with the second set of values inside beginRequest()/endRequest() block. setConnectionFields(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2, - useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2); + useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, + ignoreOffsetOnDateTimeOffsetConversion2); con.endRequest(); // Test if endRequest() resets the SQLServerConnection properties back to the first set of values. compareValuesAgainstConnection(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1, sendTimeAsDatetime1, statementPoolingCacheSize1, disableStatementPooling1, serverPreparedStatementDiscardThreshold1, enablePrepareOnFirstPreparedStatementCall1, sCatalog1, - useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, ignoreOffsetOnDateTimeOffsetConversion1); + useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, + ignoreOffsetOnDateTimeOffsetConversion1); // Multiple calls to beginRequest() without an intervening call to endRequest() are no-op. setConnectionFields(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2, - useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2); + useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, + ignoreOffsetOnDateTimeOffsetConversion2); con.beginRequest(); setConnectionFields(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1, sendTimeAsDatetime1, statementPoolingCacheSize1, disableStatementPooling1, serverPreparedStatementDiscardThreshold1, enablePrepareOnFirstPreparedStatementCall1, sCatalog1, - useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, ignoreOffsetOnDateTimeOffsetConversion1); + useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, + ignoreOffsetOnDateTimeOffsetConversion1); con.beginRequest(); con.endRequest(); // Same values as before the first beginRequest() compareValuesAgainstConnection(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2, - useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2); + useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, + ignoreOffsetOnDateTimeOffsetConversion2); // A call to endRequest() without an intervening call to beginRequest() is no-op. setConnectionFields(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1, sendTimeAsDatetime1, statementPoolingCacheSize1, disableStatementPooling1, serverPreparedStatementDiscardThreshold1, enablePrepareOnFirstPreparedStatementCall1, sCatalog1, - useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, ignoreOffsetOnDateTimeOffsetConversion1); + useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, + ignoreOffsetOnDateTimeOffsetConversion1); setConnectionFields(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2, - useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2); + useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, + ignoreOffsetOnDateTimeOffsetConversion2); con.endRequest(); // No change. compareValuesAgainstConnection(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2, sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2, serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2, - useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2); + useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, + ignoreOffsetOnDateTimeOffsetConversion2); } } finally { TestUtils.dropDatabaseIfExists(sCatalog2, connectionString); @@ -399,7 +408,8 @@ private void setConnectionFields(SQLServerConnection con, boolean autoCommitMode int networkTimeout, int holdability, boolean sendTimeAsDatetime, int statementPoolingCacheSize, boolean disableStatementPooling, int serverPreparedStatementDiscardThreshold, boolean enablePrepareOnFirstPreparedStatementCall, String sCatalog, boolean useBulkCopyForBatchInsert, - boolean useFmtOnly, boolean delayLoadingLobs, boolean ignoreOffsetOnDateTimeOffsetConversion) throws SQLException { + boolean useFmtOnly, boolean delayLoadingLobs, + boolean ignoreOffsetOnDateTimeOffsetConversion) throws SQLException { con.setAutoCommit(autoCommitMode); con.setTransactionIsolation(transactionIsolationLevel); con.setNetworkTimeout(null, networkTimeout); @@ -420,7 +430,8 @@ private void compareValuesAgainstConnection(SQLServerConnection con, boolean aut int transactionIsolationLevel, int networkTimeout, int holdability, boolean sendTimeAsDatetime, int statementPoolingCacheSize, boolean disableStatementPooling, int serverPreparedStatementDiscardThreshold, boolean enablePrepareOnFirstPreparedStatementCall, String sCatalog, boolean useBulkCopyForBatchInsert, - boolean useFmtOnly, boolean delayLoadingLobs, boolean ignoreOffsetOnDateTimeOffsetConversion) throws SQLException { + boolean useFmtOnly, boolean delayLoadingLobs, + boolean ignoreOffsetOnDateTimeOffsetConversion) throws SQLException { final String description = " values do not match."; assertEquals(autoCommitMode, con.getAutoCommit(), "autoCommitmode" + description); assertEquals(transactionIsolationLevel, con.getTransactionIsolation(), @@ -441,7 +452,8 @@ private void compareValuesAgainstConnection(SQLServerConnection con, boolean aut "useBulkCopyForBatchInsert" + description); assertEquals(useFmtOnly, con.getUseFmtOnly(), "useFmtOnly" + description); assertEquals(delayLoadingLobs, con.getDelayLoadingLobs(), "delayLoadingLobs" + description); - assertEquals(ignoreOffsetOnDateTimeOffsetConversion, con.getIgnoreOffsetOnDateTimeOffsetConversion(), "ignoreOffsetOnDateTimeOffsetConversion" + description); + assertEquals(ignoreOffsetOnDateTimeOffsetConversion, con.getIgnoreOffsetOnDateTimeOffsetConversion(), + "ignoreOffsetOnDateTimeOffsetConversion" + description); } private void generateWarning(Connection con) throws SQLException { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java index de1d089bc..222be1cde 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java @@ -289,18 +289,18 @@ public void testFOInstanceResolution2() throws SQLException { /** * Tests that failover is correctly used after a socket timeout, by confirming total time includes socketTimeout - * for both primary and failover server. + * for both primary and failover server. */ @Test public void testFailoverInstanceResolutionWithSocketTimeout() { long timerEnd; long timerStart = System.currentTimeMillis(); - try (Connection con = PrepUtil.getConnection("jdbc:sqlserver://" + randomServer - + ";databaseName=FailoverDB;failoverPartner=" + randomServer + "\\foo;user=sa;password=pwd;" - + ";socketTimeout=" + waitForDelaySeconds)) { + try (Connection con = PrepUtil + .getConnection("jdbc:sqlserver://" + randomServer + ";databaseName=FailoverDB;failoverPartner=" + + randomServer + "\\foo;user=sa;password=pwd;" + ";socketTimeout=" + waitForDelaySeconds)) { fail(TestResource.getResource("R_shouldNotConnect")); - } catch (Exception e) { + } catch (Exception e) { timerEnd = System.currentTimeMillis(); if (!(e instanceof SQLException)) { fail(TestResource.getResource("R_unexpectedErrorMessage") + e.getMessage()); @@ -310,8 +310,8 @@ public void testFailoverInstanceResolutionWithSocketTimeout() { // failover, and then have another socketTimeout. So, expected total time is 2 x socketTimeout. long totalTime = timerEnd - timerStart; long totalExpectedTime = waitForDelaySeconds * 1000L * 2; // We expect 2 * socketTimeout - assertTrue( totalTime >= totalExpectedTime, TestResource.getResource("R_executionNotLong") - + "totalTime: " + totalTime + " expectedTime: " + totalExpectedTime); + assertTrue(totalTime >= totalExpectedTime, TestResource.getResource("R_executionNotLong") + "totalTime: " + + totalTime + " expectedTime: " + totalExpectedTime); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java index d4bab3996..ff2f2fc73 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java @@ -385,7 +385,8 @@ public void testDBSchemasForSchemaPatternWithWildcards() throws SQLException { assertEquals(catalogName, catalogMsgArgs[0]); } - MessageFormat atLeastOneFoundFormat = new MessageFormat(TestResource.getResource("R_atLeastOneFound")); + MessageFormat atLeastOneFoundFormat = new MessageFormat( + TestResource.getResource("R_atLeastOneFound")); assertTrue(hasResults, atLeastOneFoundFormat.format(schemaMsgArgs)); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DataTypesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DataTypesTest.java index de8e160a2..350f7cfc3 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DataTypesTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DataTypesTest.java @@ -1626,7 +1626,6 @@ public void testUpdateMisc() throws Exception { fail(TestResource.getResource("R_expectedExceptionNotThrown")); } - exceptionThrown = true; // Verify SQLState 22005 is in exception for conversion errors try { @@ -1896,10 +1895,10 @@ public void testGetLocalDateTimeTypes() throws Exception { assertEquals(valueWithOffsetConversion.toLocalDate(), rs.getObject(1, LocalDate.class)); assertEquals(valueWithOffsetConversion.toLocalTime(), rs.getObject(1, LocalTime.class)); } - + // change the behavior to be compatible with java.time conversion methods conn.setIgnoreOffsetOnDateTimeOffsetConversion(true); - + try (PreparedStatement stmt = conn.prepareStatement("SELECT ?")) { stmt.setObject(1, value); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLVariantResultSetTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLVariantResultSetTest.java index e404ded61..4d22b104b 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLVariantResultSetTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLVariantResultSetTest.java @@ -1058,7 +1058,7 @@ public void testTimeClassAsSqlVariant() throws SQLException { assertEquals(object.getClass(), java.sql.Time.class);; } } - + @Test public void testCastThenGetNumeric() throws SQLException { try (Connection con = getConnection(); Statement stmt = con.createStatement(); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fmtOnly/InsertTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fmtOnly/InsertTest.java index 721c5ebe0..6e4be3fab 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fmtOnly/InsertTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fmtOnly/InsertTest.java @@ -215,8 +215,9 @@ public void insertExamplesTest() { "HumanResources . Department,OPENROWSET(BULK 'C:SQLFilesDepartmentData.txt' , FORMATFILE = 'C:SQLFilesBulkloadFormatFile.xml' , ROWS_PER_BATCH = 15000 ) AS b"); // S. Using the TABLOCK hint to specify a locking method - ParserUtils.compareTableName("INSERT INTO Production.Location WITH (XLOCK) \r\n" + "(Name, CostRate, Availability) \r\n" - + "VALUES ( N'Final Inventory', 15.00, 80.00); ", "Production . Location"); + ParserUtils.compareTableName("INSERT INTO Production.Location WITH (XLOCK) \r\n" + + "(Name, CostRate, Availability) \r\n" + "VALUES ( N'Final Inventory', 15.00, 80.00); ", + "Production . Location"); // T. Using OUTPUT with an INSERT statement TODO: FIX ParserUtils.compareTableName( diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/osgi/DataFactoryTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/osgi/DataFactoryTest.java index 62731692f..71dfe6ef5 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/osgi/DataFactoryTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/osgi/DataFactoryTest.java @@ -89,8 +89,7 @@ public S getService(ServiceReference reference) { boolean correctClass = false; boolean correctName = false; boolean correctVersion = false; - String[] required_capabilities=new String[] { - DataSourceFactory.OSGI_JDBC_CAPABILITY_DRIVER, + String[] required_capabilities = new String[] {DataSourceFactory.OSGI_JDBC_CAPABILITY_DRIVER, DataSourceFactory.OSGI_JDBC_CAPABILITY_DATASOURCE, DataSourceFactory.OSGI_JDBC_CAPABILITY_CONNECTIONPOOLDATASOURCE, DataSourceFactory.OSGI_JDBC_CAPABILITY_XADATASOURCE}; @@ -118,15 +117,15 @@ public S getService(ServiceReference reference) { "Driver version mismatch. Expected: " + bundleDriverVer + ", Actual: " + actualDriverVer + ".", bundleDriverVer.equals(actualDriverVer)); correctVersion = true; - }else if (key.equals(DataSourceFactory.OSGI_JDBC_CAPABILITY)) { + } else if (key.equals(DataSourceFactory.OSGI_JDBC_CAPABILITY)) { Object property = sr.getProperty(key); if (property instanceof String[]) { - Set set = new HashSet<>(Arrays.asList( (String[]) property)); + Set set = new HashSet<>(Arrays.asList((String[]) property)); for (String cap : required_capabilities) { - assertTrue("Capability "+cap+" is missing!", set.contains(cap)); + assertTrue("Capability " + cap + " is missing!", set.contains(cap)); } } - driverCap=true; + driverCap = true; } } assertTrue("Not all properties were checked.", correctClass && correctName && correctVersion && driverCap); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java index 353648dc6..17f46df23 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java @@ -342,13 +342,14 @@ public void testNullGuid() throws Exception { pstmt.setNull(1, microsoft.sql.Types.GUID); pstmt.addBatch(); pstmt.executeBatch(); - - try (ResultSet rs = stmt.executeQuery("select c24 from " + AbstractSQLGenerator.escapeIdentifier(tableName))) { + + try (ResultSet rs = stmt + .executeQuery("select c24 from " + AbstractSQLGenerator.escapeIdentifier(tableName))) { Object[] expected = new Object[1]; - + expected[0] = null; rs.next(); - + assertEquals(expected[0], rs.getObject(1)); } } @@ -724,8 +725,8 @@ public void testNonSupportedColumns() throws Exception { @Test public void testReverseColumnOrder() throws Exception { - String valid = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableName) + " (c2, c1) values " - + "(" + "?, " + "? " + ")"; + String valid = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableName) + " (c2, c1) values " + "(" + + "?, " + "? " + ")"; try (Connection connection = PrepUtil.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(valid); @@ -770,8 +771,8 @@ public void testComputedCols() throws Exception { + " values (?, ?)"; try (Connection connection = PrepUtil.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); - SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(valid); - Statement stmt = (SQLServerStatement) connection.createStatement();) { + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(valid); + Statement stmt = (SQLServerStatement) connection.createStatement();) { Field f1 = SQLServerConnection.class.getDeclaredField("isAzureDW"); f1.setAccessible(true); f1.set(connection, true); @@ -782,9 +783,8 @@ public void testComputedCols() throws Exception { + " vcol1 as json_value([json], '$.vcol1'), vcol2 as json_value([json], '$.vcol2'))"; stmt.execute(createTable); - String jsonValue = - "{\"vcol1\":\"" + UUID.randomUUID().toString() + "\",\"vcol2\":\"" + UUID.randomUUID().toString() - + "\" }"; + String jsonValue = "{\"vcol1\":\"" + UUID.randomUUID().toString() + "\",\"vcol2\":\"" + + UUID.randomUUID().toString() + "\" }"; String idValue = UUID.randomUUID().toString(); pstmt.setString(1, idValue); pstmt.setString(2, jsonValue); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/PropertyTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/PropertyTest.java index 851c43500..fd79a9845 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/PropertyTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/PropertyTest.java @@ -43,7 +43,8 @@ private void testInvalidPropertyOverBrokenConnection(String prop, String val, fail(TestResource.getResource("R_expectedExceptionNotThrown") + prop + "=" + val); } } catch (SQLException e) { - assertTrue(TestResource.getResource("R_unexpectedErrorMessage") + e.getMessage(), e.getMessage().matches(expectedErrMsg)); + assertTrue(TestResource.getResource("R_unexpectedErrorMessage") + e.getMessage(), + e.getMessage().matches(expectedErrMsg)); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ReflectiveTests.java b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ReflectiveTests.java index 56d4279a1..3a10c625f 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ReflectiveTests.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ReflectiveTests.java @@ -51,7 +51,7 @@ private void timeoutVariations(Map props, long expectedDuration, fail("Successfully executed query on a blocked connection."); } catch (SQLException e) { double elapsedTime = System.currentTimeMillis() - startTime; - + // Timeout should occur after query timeout and not login timeout assertTrue("Exception: " + e.getMessage() + ": Query did not timeout in " + expectedDuration + "ms, elapsed time(ms): " + elapsedTime, elapsedTime < expectedDuration); @@ -83,7 +83,7 @@ public void testDefaultTimeout() throws SQLException { public void testDefaultRetry() throws SQLException { Map m = new HashMap<>(); m.put("loginTimeout", "5"); - + // ensure count is not set to something else as this test assumes exactly just 1 retry m.put("connectRetryCount", "1"); timeoutVariations(m, 6000, Optional.empty()); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/BatchExecuteWithErrorsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/BatchExecuteWithErrorsTest.java index 5744b9773..0e48f4253 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/BatchExecuteWithErrorsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/BatchExecuteWithErrorsTest.java @@ -31,6 +31,7 @@ import com.microsoft.sqlserver.testframework.AbstractTest; import com.microsoft.sqlserver.testframework.Constants; + /** * Tests batch execution with errors * @@ -38,437 +39,447 @@ @RunWith(JUnitPlatform.class) public class BatchExecuteWithErrorsTest extends AbstractTest { - public static final Logger log = Logger.getLogger("BatchExecuteWithErrors"); - - final String tableName = RandomUtil.getIdentifier("t_Repro47239"); - final String insertStmt = "INSERT INTO " + AbstractSQLGenerator.escapeIdentifier(tableName) + " VALUES (999, 'HELLO', '4/12/1994')"; - final String error16 = "RAISERROR ('raiserror level 16',16,42)"; - final String select = "SELECT 1"; - final String dateConversionError = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableName) - + " values (999999, 'Hello again', 'asdfasdf')"; - - String warning; - String error; - String severe; - - @BeforeAll - public static void setupTests() throws Exception { - setConnection(); - } - - /** - * Batch test - * - * @throws Exception - */ - @Test - @DisplayName("Batch Test") - public void Repro47239() throws Exception { - Repro47239Internal("BatchInsert"); - } - - @Test - @DisplayName("Batch Test using bulk copy API") - public void Repro47239UseBulkCopyAPI() throws Exception { - Repro47239Internal("BulkCopy"); - } - - /** - * Tests large methods - * - * @throws Exception - */ - @Test - @DisplayName("Regression test for using 'large' methods") - @Tag(Constants.xAzureSQLDW) - public void Repro47239large() throws Exception { - Repro47239largeInternal("BatchInsert"); - } - - @Test - @DisplayName("Regression test for using 'large' methods using bulk copy API") - @Tag(Constants.xAzureSQLDW) - public void Repro47239largeUseBulkCopyAPI() throws Exception { - Repro47239largeInternal("BulkCopy"); - } - - private void Repro47239Internal(String mode) throws Exception { - try (Connection con = getConnection()) { - if (isSqlAzure()) { - // SQL Azure will throw exception for "raiserror WITH LOG", so the following RAISERROR statements have - // not "with log" option - warning = "RAISERROR ('raiserror level 4',4,1)"; - error = "RAISERROR ('raiserror level 11',11,1)"; - // On SQL Azure, raising FATAL error by RAISERROR() is not supported and there is no way to - // cut the current connection by a statement inside a SQL batch. - // Details: Although one can simulate a fatal error (that cuts the connections) by dropping the - // database, - // this simulation cannot be written entirely in TSQL (because it needs a new connection), - // and thus it cannot be put into a TSQL batch and it is useless here. - // So we have to skip the last scenario of this test case, i.e. "Test Severe (connection-closing) - // errors" - // It is worthwhile to still execute the first 5 test scenarios of this test case, in order to have best - // test coverage. - severe = "--Not executed when testing against SQL Azure"; // this is a dummy statement that never being - // executed on SQL Azure - } else { - warning = "RAISERROR ('raiserror level 4',4,1) WITH LOG"; - error = "RAISERROR ('raiserror level 11',11,1) WITH LOG"; - severe = "RAISERROR ('raiserror level 20',20,1) WITH LOG"; - } - } - - int[] actualUpdateCounts; - int[] expectedUpdateCounts; - String actualExceptionText; - - try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) { - - if (mode.equalsIgnoreCase("bulkcopy")) { - modifyConnectionForBulkCopyAPI((SQLServerConnection) conn); - } - TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt); - stmt.executeUpdate("create table " + AbstractSQLGenerator.escapeIdentifier(tableName) - + " (c1_int int, c2_varchar varchar(20), c3_date datetime, c4_int int identity(1,1))"); - - // Regular Statement batch update - expectedUpdateCounts = new int[] { 1, -2, 1, -2, 1, -2 }; - try (Statement batchStmt = conn.createStatement()) { - batchStmt.addBatch(insertStmt); - batchStmt.addBatch(warning); - batchStmt.addBatch(insertStmt); - batchStmt.addBatch(warning); - batchStmt.addBatch(insertStmt); - batchStmt.addBatch(warning); - try { - actualUpdateCounts = batchStmt.executeBatch(); - actualExceptionText = ""; - } catch (BatchUpdateException bue) { - actualUpdateCounts = bue.getUpdateCounts(); - actualExceptionText = bue.getMessage(); - if (log.isLoggable(Level.FINE)) { - log.fine("BatchUpdateException occurred. Message:" + actualExceptionText); - } - } finally { - batchStmt.close(); - } - } - if (log.isLoggable(Level.FINE)) { - log.fine("UpdateCounts:"); - } - for (int updateCount : actualUpdateCounts) { - log.fine("" + updateCount + ","); - } - - assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), TestResource.getResource("R_testInterleaved")); - - expectedUpdateCounts = new int[] { -3, 1, 1, 1 }; - stmt.addBatch(error); - stmt.addBatch(insertStmt); - stmt.addBatch(insertStmt); - stmt.addBatch(insertStmt); - try { - actualUpdateCounts = stmt.executeBatch(); - actualExceptionText = ""; - } catch (BatchUpdateException bue) { - actualUpdateCounts = bue.getUpdateCounts(); - actualExceptionText = bue.getMessage(); - } - log.fine("UpdateCounts:"); - for (int updateCount : actualUpdateCounts) { - log.fine("" + updateCount + ","); - } - - assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), TestResource.getResource("R_errorFollowInserts")); - // 50280 - expectedUpdateCounts = new int[] { 1, -3 }; - stmt.addBatch(insertStmt); - stmt.addBatch(error16); - try { - actualUpdateCounts = stmt.executeBatch(); - actualExceptionText = ""; - } catch (BatchUpdateException bue) { - actualUpdateCounts = bue.getUpdateCounts(); - actualExceptionText = bue.getMessage(); - } - for (int updateCount : actualUpdateCounts) { - log.fine("" + updateCount + ","); - } - - assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), TestResource.getResource("R_errorFollow50280")); - - // Test "soft" errors - conn.setAutoCommit(false); - stmt.addBatch(select); - stmt.addBatch(insertStmt); - stmt.addBatch(select); - stmt.addBatch(insertStmt); - try { - stmt.executeBatch(); - // Soft error test: executeBatch unexpectedly succeeded - assertEquals(true, false, TestResource.getResource("R_shouldThrowException")); - } catch (BatchUpdateException bue) { - assertEquals("A result set was generated for update.", bue.getMessage(), TestResource.getResource("R_unexpectedExceptionContent")); - assertEquals(Arrays.equals(bue.getUpdateCounts(), new int[] { -3, 1, -3, 1 }), true, - TestResource.getResource("R_incorrectUpdateCount")); - } - conn.rollback(); - stmt.addBatch(dateConversionError); - stmt.addBatch(insertStmt); - stmt.addBatch(insertStmt); - stmt.addBatch(insertStmt); - try { - stmt.executeBatch(); - } catch (BatchUpdateException bue) { - if (isSqlAzureDW()) { - assert (bue.getMessage().contains(TestResource.getResource("R_syntaxErrorDateConvertDW"))); - } else { - assert (bue.getMessage().contains(TestResource.getResource("R_syntaxErrorDateConvert"))); - } - } catch (SQLException e) { - assert (e.getMessage().contains(TestResource.getResource("R_dateConvertError"))); - } - - conn.setAutoCommit(true); - - // On SQL Azure, raising FATAL error by RAISERROR() is not supported and there is no way to - // cut the current connection by a statement inside a SQL batch. - // Details: Although one can simulate a fatal error (that cuts the connections) by dropping the - // database, - // this simulation cannot be written entirely in TSQL (because it needs a new connection), - // and thus it cannot be put into a TSQL batch and it is useless here. - // So we have to skip the last scenario of this test case, i.e. "Test Severe (connection-closing) - // errors" - // It is worthwhile to still execute the first 5 test scenarios of this test case, in order to have best - // test coverage. - if (!isSqlAzure()) { - // Test Severe (connection-closing) errors - stmt.addBatch(error); - stmt.addBatch(insertStmt); - stmt.addBatch(warning); - stmt.addBatch(select); - stmt.addBatch(insertStmt); - stmt.addBatch(severe); - stmt.addBatch(insertStmt); - stmt.addBatch(insertStmt); - try { - stmt.executeBatch(); - // Test fatal errors batch execution succeeded (should have failed) - assertEquals(false, true, TestResource.getResource("R_shouldThrowException")); - } catch (BatchUpdateException bue) { - // Test fatal errors returned BatchUpdateException rather than SQLException - assertEquals(false, true, TestResource.getResource("R_unexpectedException") + bue.getMessage()); - - } catch (SQLException e) { - actualExceptionText = e.getMessage(); - - if (actualExceptionText.endsWith("reset")) { - assertTrue(actualExceptionText.equalsIgnoreCase("Connection reset"), - TestResource.getResource("R_unexpectedExceptionContent") + ": " + actualExceptionText); - } else { - assertTrue(actualExceptionText.equalsIgnoreCase("raiserror level 20"), - TestResource.getResource("R_unexpectedExceptionContent") + ": " + actualExceptionText); - } - } - } - } finally { - try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) { - TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt); - } - } - } - - private void Repro47239largeInternal(String mode) throws Exception { - // the DBConnection for detecting whether the server is SQL Azure or SQL Server. - try (Connection con = getConnection()) { - if (isSqlAzure()) { - // SQL Azure will throw exception for "raiserror WITH LOG", so the following RAISERROR statements have - // not - // "with log" option - warning = "RAISERROR ('raiserror level 4',4,1)"; - error = "RAISERROR ('raiserror level 11',11,1)"; - // On SQL Azure, raising FATAL error by RAISERROR() is not supported and there is no way to - // cut the current connection by a statement inside a SQL batch. - // Details: Although one can simulate a fatal error (that cuts the connections) by dropping the - // database, - // this simulation cannot be written entirely in TSQL (because it needs a new connection), - // and thus it cannot be put into a TSQL batch and it is useless here. - // So we have to skip the last scenario of this test case, i.e. "Test Severe (connection-closing) - // errors" - // It is worthwhile to still execute the first 5 test scenarios of this test case, in order to have best - // test coverage. - severe = "--Not executed when testing against SQL Azure"; // this is a dummy statement that never being - // executed on SQL Azure - } else { - warning = "RAISERROR ('raiserror level 4',4,1) WITH LOG"; - error = "RAISERROR ('raiserror level 11',11,1) WITH LOG"; - severe = "RAISERROR ('raiserror level 20',20,1) WITH LOG"; - } - con.close(); - - long[] actualUpdateCounts; - long[] expectedUpdateCounts; - String actualExceptionText; - - try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) { - - if (mode.equalsIgnoreCase("bulkcopy")) { - modifyConnectionForBulkCopyAPI((SQLServerConnection) conn); - } - TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt); - stmt.executeLargeUpdate("create table " + AbstractSQLGenerator.escapeIdentifier(tableName) - + " (c1_int int, c2_varchar varchar(20), c3_date datetime, c4_int int identity(1,1) primary key)"); - // Regular Statement batch update - expectedUpdateCounts = new long[] { 1, -2, 1, -2, 1, -2 }; - try (Statement batchStmt = conn.createStatement()) { - batchStmt.addBatch(insertStmt); - batchStmt.addBatch(warning); - batchStmt.addBatch(insertStmt); - batchStmt.addBatch(warning); - batchStmt.addBatch(insertStmt); - batchStmt.addBatch(warning); - try { - actualUpdateCounts = batchStmt.executeLargeBatch(); - actualExceptionText = ""; - } catch (BatchUpdateException bue) { - actualUpdateCounts = bue.getLargeUpdateCounts(); - actualExceptionText = bue.getMessage(); - log.fine("BatchUpdateException occurred. Message:" + actualExceptionText); - } - } - - log.fine("UpdateCounts:"); - for (long updateCount : actualUpdateCounts) { - log.fine("" + updateCount + ","); - } - - assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), TestResource.getResource("R_testInterleaved")); - - expectedUpdateCounts = new long[] { -3, 1, 1, 1 }; - stmt.addBatch(error); - stmt.addBatch(insertStmt); - stmt.addBatch(insertStmt); - stmt.addBatch(insertStmt); - try { - actualUpdateCounts = stmt.executeLargeBatch(); - actualExceptionText = ""; - } catch (BatchUpdateException bue) { - actualUpdateCounts = bue.getLargeUpdateCounts(); - actualExceptionText = bue.getMessage(); - } - log.fine("UpdateCounts:"); - for (long updateCount : actualUpdateCounts) { - log.fine("" + updateCount + ","); - } - - assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), TestResource.getResource("R_errorFollowInserts")); - - // 50280 - expectedUpdateCounts = new long[] { 1, -3 }; - stmt.addBatch(insertStmt); - stmt.addBatch(error16); - try { - actualUpdateCounts = stmt.executeLargeBatch(); - actualExceptionText = ""; - } catch (BatchUpdateException bue) { - actualUpdateCounts = bue.getLargeUpdateCounts(); - actualExceptionText = bue.getMessage(); - } - for (long updateCount : actualUpdateCounts) { - log.fine("" + updateCount + ","); - } - - assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), TestResource.getResource("R_errorFollow50280")); - - // Test "soft" errors - conn.setAutoCommit(false); - stmt.addBatch(select); - stmt.addBatch(insertStmt); - stmt.addBatch(select); - stmt.addBatch(insertStmt); - try { - stmt.executeLargeBatch(); - // Soft error test: executeLargeBatch unexpectedly succeeded - assertEquals(false, true, TestResource.getResource("R_shouldThrowException")); - } catch (BatchUpdateException bue) { - // Soft error test: wrong error message in BatchUpdateException - assertEquals("A result set was generated for update.", bue.getMessage(), - TestResource.getResource("R_unexpectedExceptionContent")); - // Soft error test: wrong update counts in BatchUpdateException - assertEquals(Arrays.equals(bue.getLargeUpdateCounts(), new long[] { -3, 1, -3, 1 }), true, - TestResource.getResource("R_incorrectUpdateCount")); - } - conn.rollback(); - - // Defect 128801: Rollback (with conversion error) should throw SQLException - stmt.addBatch(dateConversionError); - stmt.addBatch(insertStmt); - stmt.addBatch(insertStmt); - stmt.addBatch(insertStmt); - try { - stmt.executeLargeBatch(); - } catch (BatchUpdateException bue) { - assert (bue.getMessage().contains(TestResource.getResource("R_syntaxErrorDateConvert"))); - } catch (SQLException e) { - assert (e.getMessage().contains(TestResource.getResource("R_dateConvertError"))); - } - - conn.setAutoCommit(true); - - // On SQL Azure, raising FATAL error by RAISERROR() is not supported and there is no way to - // cut the current connection by a statement inside a SQL batch. - // Details: Although one can simulate a fatal error (that cuts the connections) by dropping the - // database, - // this simulation cannot be written entirely in TSQL (because it needs a new connection), - // and thus it cannot be put into a TSQL batch and it is useless here. - // So we have to skip the last scenario of this test case, i.e. "Test Severe (connection-closing) - // errors" - // It is worthwhile to still execute the first 5 test scenarios of this test case, in order to have - // best - // test coverage. - if (!isSqlAzure()) { - // Test Severe (connection-closing) errors - stmt.addBatch(error); - stmt.addBatch(insertStmt); - stmt.addBatch(warning); - - stmt.addBatch(insertStmt); - stmt.addBatch(severe); - stmt.addBatch(insertStmt); - stmt.addBatch(insertStmt); - try { - stmt.executeLargeBatch(); - // Test fatal errors batch execution succeeded (should have failed) - assertEquals(false, true, TestResource.getResource("R_shouldThrowException")); - } catch (BatchUpdateException bue) { - // Test fatal errors returned BatchUpdateException rather than SQLException - assertEquals(false, true, TestResource.getResource("R_unexpectedException") + bue.getMessage()); - } catch (SQLException e) { - actualExceptionText = e.getMessage(); - - if (actualExceptionText.endsWith("reset")) { - assertTrue(actualExceptionText.equalsIgnoreCase("Connection reset"), - TestResource.getResource("R_unexpectedExceptionContent") + ": " + actualExceptionText); - } else { - assertTrue(actualExceptionText.equalsIgnoreCase("raiserror level 20"), - TestResource.getResource("R_unexpectedExceptionContent") + ": " + actualExceptionText); - - } - } - } - } finally { - try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) { - TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt); - } - } - } - } - - private void modifyConnectionForBulkCopyAPI(SQLServerConnection con) throws Exception { - Field f1 = SQLServerConnection.class.getDeclaredField("isAzureDW"); - f1.setAccessible(true); - f1.set(con, true); - - con.setUseBulkCopyForBatchInsert(true); - } + public static final Logger log = Logger.getLogger("BatchExecuteWithErrors"); + + final String tableName = RandomUtil.getIdentifier("t_Repro47239"); + final String insertStmt = "INSERT INTO " + AbstractSQLGenerator.escapeIdentifier(tableName) + + " VALUES (999, 'HELLO', '4/12/1994')"; + final String error16 = "RAISERROR ('raiserror level 16',16,42)"; + final String select = "SELECT 1"; + final String dateConversionError = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableName) + + " values (999999, 'Hello again', 'asdfasdf')"; + + String warning; + String error; + String severe; + + @BeforeAll + public static void setupTests() throws Exception { + setConnection(); + } + + /** + * Batch test + * + * @throws Exception + */ + @Test + @DisplayName("Batch Test") + public void Repro47239() throws Exception { + Repro47239Internal("BatchInsert"); + } + + @Test + @DisplayName("Batch Test using bulk copy API") + public void Repro47239UseBulkCopyAPI() throws Exception { + Repro47239Internal("BulkCopy"); + } + + /** + * Tests large methods + * + * @throws Exception + */ + @Test + @DisplayName("Regression test for using 'large' methods") + @Tag(Constants.xAzureSQLDW) + public void Repro47239large() throws Exception { + Repro47239largeInternal("BatchInsert"); + } + + @Test + @DisplayName("Regression test for using 'large' methods using bulk copy API") + @Tag(Constants.xAzureSQLDW) + public void Repro47239largeUseBulkCopyAPI() throws Exception { + Repro47239largeInternal("BulkCopy"); + } + + private void Repro47239Internal(String mode) throws Exception { + try (Connection con = getConnection()) { + if (isSqlAzure()) { + // SQL Azure will throw exception for "raiserror WITH LOG", so the following RAISERROR statements have + // not "with log" option + warning = "RAISERROR ('raiserror level 4',4,1)"; + error = "RAISERROR ('raiserror level 11',11,1)"; + // On SQL Azure, raising FATAL error by RAISERROR() is not supported and there is no way to + // cut the current connection by a statement inside a SQL batch. + // Details: Although one can simulate a fatal error (that cuts the connections) by dropping the + // database, + // this simulation cannot be written entirely in TSQL (because it needs a new connection), + // and thus it cannot be put into a TSQL batch and it is useless here. + // So we have to skip the last scenario of this test case, i.e. "Test Severe (connection-closing) + // errors" + // It is worthwhile to still execute the first 5 test scenarios of this test case, in order to have best + // test coverage. + severe = "--Not executed when testing against SQL Azure"; // this is a dummy statement that never being + // executed on SQL Azure + } else { + warning = "RAISERROR ('raiserror level 4',4,1) WITH LOG"; + error = "RAISERROR ('raiserror level 11',11,1) WITH LOG"; + severe = "RAISERROR ('raiserror level 20',20,1) WITH LOG"; + } + } + + int[] actualUpdateCounts; + int[] expectedUpdateCounts; + String actualExceptionText; + + try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) { + + if (mode.equalsIgnoreCase("bulkcopy")) { + modifyConnectionForBulkCopyAPI((SQLServerConnection) conn); + } + TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt); + stmt.executeUpdate("create table " + AbstractSQLGenerator.escapeIdentifier(tableName) + + " (c1_int int, c2_varchar varchar(20), c3_date datetime, c4_int int identity(1,1))"); + + // Regular Statement batch update + expectedUpdateCounts = new int[] {1, -2, 1, -2, 1, -2}; + try (Statement batchStmt = conn.createStatement()) { + batchStmt.addBatch(insertStmt); + batchStmt.addBatch(warning); + batchStmt.addBatch(insertStmt); + batchStmt.addBatch(warning); + batchStmt.addBatch(insertStmt); + batchStmt.addBatch(warning); + try { + actualUpdateCounts = batchStmt.executeBatch(); + actualExceptionText = ""; + } catch (BatchUpdateException bue) { + actualUpdateCounts = bue.getUpdateCounts(); + actualExceptionText = bue.getMessage(); + if (log.isLoggable(Level.FINE)) { + log.fine("BatchUpdateException occurred. Message:" + actualExceptionText); + } + } finally { + batchStmt.close(); + } + } + if (log.isLoggable(Level.FINE)) { + log.fine("UpdateCounts:"); + } + for (int updateCount : actualUpdateCounts) { + log.fine("" + updateCount + ","); + } + + assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), + TestResource.getResource("R_testInterleaved")); + + expectedUpdateCounts = new int[] {-3, 1, 1, 1}; + stmt.addBatch(error); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + try { + actualUpdateCounts = stmt.executeBatch(); + actualExceptionText = ""; + } catch (BatchUpdateException bue) { + actualUpdateCounts = bue.getUpdateCounts(); + actualExceptionText = bue.getMessage(); + } + log.fine("UpdateCounts:"); + for (int updateCount : actualUpdateCounts) { + log.fine("" + updateCount + ","); + } + + assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), + TestResource.getResource("R_errorFollowInserts")); + // 50280 + expectedUpdateCounts = new int[] {1, -3}; + stmt.addBatch(insertStmt); + stmt.addBatch(error16); + try { + actualUpdateCounts = stmt.executeBatch(); + actualExceptionText = ""; + } catch (BatchUpdateException bue) { + actualUpdateCounts = bue.getUpdateCounts(); + actualExceptionText = bue.getMessage(); + } + for (int updateCount : actualUpdateCounts) { + log.fine("" + updateCount + ","); + } + + assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), + TestResource.getResource("R_errorFollow50280")); + + // Test "soft" errors + conn.setAutoCommit(false); + stmt.addBatch(select); + stmt.addBatch(insertStmt); + stmt.addBatch(select); + stmt.addBatch(insertStmt); + try { + stmt.executeBatch(); + // Soft error test: executeBatch unexpectedly succeeded + assertEquals(true, false, TestResource.getResource("R_shouldThrowException")); + } catch (BatchUpdateException bue) { + assertEquals("A result set was generated for update.", bue.getMessage(), + TestResource.getResource("R_unexpectedExceptionContent")); + assertEquals(Arrays.equals(bue.getUpdateCounts(), new int[] {-3, 1, -3, 1}), true, + TestResource.getResource("R_incorrectUpdateCount")); + } + conn.rollback(); + stmt.addBatch(dateConversionError); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + try { + stmt.executeBatch(); + } catch (BatchUpdateException bue) { + if (isSqlAzureDW()) { + assert (bue.getMessage().contains(TestResource.getResource("R_syntaxErrorDateConvertDW"))); + } else { + assert (bue.getMessage().contains(TestResource.getResource("R_syntaxErrorDateConvert"))); + } + } catch (SQLException e) { + assert (e.getMessage().contains(TestResource.getResource("R_dateConvertError"))); + } + + conn.setAutoCommit(true); + + // On SQL Azure, raising FATAL error by RAISERROR() is not supported and there is no way to + // cut the current connection by a statement inside a SQL batch. + // Details: Although one can simulate a fatal error (that cuts the connections) by dropping the + // database, + // this simulation cannot be written entirely in TSQL (because it needs a new connection), + // and thus it cannot be put into a TSQL batch and it is useless here. + // So we have to skip the last scenario of this test case, i.e. "Test Severe (connection-closing) + // errors" + // It is worthwhile to still execute the first 5 test scenarios of this test case, in order to have best + // test coverage. + if (!isSqlAzure()) { + // Test Severe (connection-closing) errors + stmt.addBatch(error); + stmt.addBatch(insertStmt); + stmt.addBatch(warning); + stmt.addBatch(select); + stmt.addBatch(insertStmt); + stmt.addBatch(severe); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + try { + stmt.executeBatch(); + // Test fatal errors batch execution succeeded (should have failed) + assertEquals(false, true, TestResource.getResource("R_shouldThrowException")); + } catch (BatchUpdateException bue) { + // Test fatal errors returned BatchUpdateException rather than SQLException + assertEquals(false, true, TestResource.getResource("R_unexpectedException") + bue.getMessage()); + + } catch (SQLException e) { + actualExceptionText = e.getMessage(); + + if (actualExceptionText.endsWith("reset")) { + assertTrue(actualExceptionText.equalsIgnoreCase("Connection reset"), + TestResource.getResource("R_unexpectedExceptionContent") + ": " + actualExceptionText); + } else { + assertTrue(actualExceptionText.equalsIgnoreCase("raiserror level 20"), + TestResource.getResource("R_unexpectedExceptionContent") + ": " + actualExceptionText); + } + } + } + } finally { + try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) { + TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt); + } + } + } + + private void Repro47239largeInternal(String mode) throws Exception { + // the DBConnection for detecting whether the server is SQL Azure or SQL Server. + try (Connection con = getConnection()) { + if (isSqlAzure()) { + // SQL Azure will throw exception for "raiserror WITH LOG", so the following RAISERROR statements have + // not + // "with log" option + warning = "RAISERROR ('raiserror level 4',4,1)"; + error = "RAISERROR ('raiserror level 11',11,1)"; + // On SQL Azure, raising FATAL error by RAISERROR() is not supported and there is no way to + // cut the current connection by a statement inside a SQL batch. + // Details: Although one can simulate a fatal error (that cuts the connections) by dropping the + // database, + // this simulation cannot be written entirely in TSQL (because it needs a new connection), + // and thus it cannot be put into a TSQL batch and it is useless here. + // So we have to skip the last scenario of this test case, i.e. "Test Severe (connection-closing) + // errors" + // It is worthwhile to still execute the first 5 test scenarios of this test case, in order to have best + // test coverage. + severe = "--Not executed when testing against SQL Azure"; // this is a dummy statement that never being + // executed on SQL Azure + } else { + warning = "RAISERROR ('raiserror level 4',4,1) WITH LOG"; + error = "RAISERROR ('raiserror level 11',11,1) WITH LOG"; + severe = "RAISERROR ('raiserror level 20',20,1) WITH LOG"; + } + con.close(); + + long[] actualUpdateCounts; + long[] expectedUpdateCounts; + String actualExceptionText; + + try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) { + + if (mode.equalsIgnoreCase("bulkcopy")) { + modifyConnectionForBulkCopyAPI((SQLServerConnection) conn); + } + TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt); + stmt.executeLargeUpdate("create table " + AbstractSQLGenerator.escapeIdentifier(tableName) + + " (c1_int int, c2_varchar varchar(20), c3_date datetime, c4_int int identity(1,1) primary key)"); + // Regular Statement batch update + expectedUpdateCounts = new long[] {1, -2, 1, -2, 1, -2}; + try (Statement batchStmt = conn.createStatement()) { + batchStmt.addBatch(insertStmt); + batchStmt.addBatch(warning); + batchStmt.addBatch(insertStmt); + batchStmt.addBatch(warning); + batchStmt.addBatch(insertStmt); + batchStmt.addBatch(warning); + try { + actualUpdateCounts = batchStmt.executeLargeBatch(); + actualExceptionText = ""; + } catch (BatchUpdateException bue) { + actualUpdateCounts = bue.getLargeUpdateCounts(); + actualExceptionText = bue.getMessage(); + log.fine("BatchUpdateException occurred. Message:" + actualExceptionText); + } + } + + log.fine("UpdateCounts:"); + for (long updateCount : actualUpdateCounts) { + log.fine("" + updateCount + ","); + } + + assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), + TestResource.getResource("R_testInterleaved")); + + expectedUpdateCounts = new long[] {-3, 1, 1, 1}; + stmt.addBatch(error); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + try { + actualUpdateCounts = stmt.executeLargeBatch(); + actualExceptionText = ""; + } catch (BatchUpdateException bue) { + actualUpdateCounts = bue.getLargeUpdateCounts(); + actualExceptionText = bue.getMessage(); + } + log.fine("UpdateCounts:"); + for (long updateCount : actualUpdateCounts) { + log.fine("" + updateCount + ","); + } + + assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), + TestResource.getResource("R_errorFollowInserts")); + + // 50280 + expectedUpdateCounts = new long[] {1, -3}; + stmt.addBatch(insertStmt); + stmt.addBatch(error16); + try { + actualUpdateCounts = stmt.executeLargeBatch(); + actualExceptionText = ""; + } catch (BatchUpdateException bue) { + actualUpdateCounts = bue.getLargeUpdateCounts(); + actualExceptionText = bue.getMessage(); + } + for (long updateCount : actualUpdateCounts) { + log.fine("" + updateCount + ","); + } + + assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), + TestResource.getResource("R_errorFollow50280")); + + // Test "soft" errors + conn.setAutoCommit(false); + stmt.addBatch(select); + stmt.addBatch(insertStmt); + stmt.addBatch(select); + stmt.addBatch(insertStmt); + try { + stmt.executeLargeBatch(); + // Soft error test: executeLargeBatch unexpectedly succeeded + assertEquals(false, true, TestResource.getResource("R_shouldThrowException")); + } catch (BatchUpdateException bue) { + // Soft error test: wrong error message in BatchUpdateException + assertEquals("A result set was generated for update.", bue.getMessage(), + TestResource.getResource("R_unexpectedExceptionContent")); + // Soft error test: wrong update counts in BatchUpdateException + assertEquals(Arrays.equals(bue.getLargeUpdateCounts(), new long[] {-3, 1, -3, 1}), true, + TestResource.getResource("R_incorrectUpdateCount")); + } + conn.rollback(); + + // Defect 128801: Rollback (with conversion error) should throw SQLException + stmt.addBatch(dateConversionError); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + try { + stmt.executeLargeBatch(); + } catch (BatchUpdateException bue) { + assert (bue.getMessage().contains(TestResource.getResource("R_syntaxErrorDateConvert"))); + } catch (SQLException e) { + assert (e.getMessage().contains(TestResource.getResource("R_dateConvertError"))); + } + + conn.setAutoCommit(true); + + // On SQL Azure, raising FATAL error by RAISERROR() is not supported and there is no way to + // cut the current connection by a statement inside a SQL batch. + // Details: Although one can simulate a fatal error (that cuts the connections) by dropping the + // database, + // this simulation cannot be written entirely in TSQL (because it needs a new connection), + // and thus it cannot be put into a TSQL batch and it is useless here. + // So we have to skip the last scenario of this test case, i.e. "Test Severe (connection-closing) + // errors" + // It is worthwhile to still execute the first 5 test scenarios of this test case, in order to have + // best + // test coverage. + if (!isSqlAzure()) { + // Test Severe (connection-closing) errors + stmt.addBatch(error); + stmt.addBatch(insertStmt); + stmt.addBatch(warning); + + stmt.addBatch(insertStmt); + stmt.addBatch(severe); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + try { + stmt.executeLargeBatch(); + // Test fatal errors batch execution succeeded (should have failed) + assertEquals(false, true, TestResource.getResource("R_shouldThrowException")); + } catch (BatchUpdateException bue) { + // Test fatal errors returned BatchUpdateException rather than SQLException + assertEquals(false, true, TestResource.getResource("R_unexpectedException") + bue.getMessage()); + } catch (SQLException e) { + actualExceptionText = e.getMessage(); + + if (actualExceptionText.endsWith("reset")) { + assertTrue(actualExceptionText.equalsIgnoreCase("Connection reset"), + TestResource.getResource("R_unexpectedExceptionContent") + ": " + + actualExceptionText); + } else { + assertTrue(actualExceptionText.equalsIgnoreCase("raiserror level 20"), + TestResource.getResource("R_unexpectedExceptionContent") + ": " + + actualExceptionText); + + } + } + } + } finally { + try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) { + TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt); + } + } + } + } + + private void modifyConnectionForBulkCopyAPI(SQLServerConnection con) throws Exception { + Field f1 = SQLServerConnection.class.getDeclaredField("isAzureDW"); + f1.setAccessible(true); + f1.set(con, true); + + con.setUseBulkCopyForBatchInsert(true); + } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PQImpsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PQImpsTest.java index 22080f486..fb1aa8f7e 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PQImpsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PQImpsTest.java @@ -831,7 +831,7 @@ public void testJoin() throws SQLException { @DisplayName("Merge Queries") public void testMerge() throws SQLException { // FMTOnly currently only supports SELECT/INSERT/DELETE/UPDATE - if (version >= SQL_SERVER_2012_VERSION && !((SQLServerConnection)connection).getUseFmtOnly()) { + if (version >= SQL_SERVER_2012_VERSION && !((SQLServerConnection) connection).getUseFmtOnly()) { String sql = "merge " + AbstractSQLGenerator.escapeIdentifier(mergeNameDesTable) + " as T" + " using " + AbstractSQLGenerator.escapeIdentifier(nameTable) + " as S" + " on T.PlainID=S.PlainID" + " when matched" + " then update set T.firstName = ?, T.lastName = ?;"; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java index c7bd10414..79862dfcd 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java @@ -122,8 +122,8 @@ public void testPreparedStatementWithSpPrepare() throws SQLException { public void testPreparedStatementParamNameSpacingWithMultipleParams() throws SQLException { int paramNameCount = 105; - StringBuilder insertSql = new StringBuilder("insert into " + AbstractSQLGenerator.escapeIdentifier(tableName4) - + "values("); + StringBuilder insertSql = new StringBuilder( + "insert into " + AbstractSQLGenerator.escapeIdentifier(tableName4) + "values("); for (int i = 1; i <= paramNameCount; i++) { insertSql.append(i % 10); @@ -135,7 +135,8 @@ public void testPreparedStatementParamNameSpacingWithMultipleParams() throws SQL } } - StringBuilder createTableSql = new StringBuilder("create table " + AbstractSQLGenerator.escapeIdentifier(tableName4) + "("); + StringBuilder createTableSql = new StringBuilder( + "create table " + AbstractSQLGenerator.escapeIdentifier(tableName4) + "("); for (int i = 1; i <= paramNameCount; i++) { createTableSql.append("c" + i + " char(1)"); @@ -162,8 +163,8 @@ public void testPreparedStatementParamNameSpacingWithMultipleParams() throws SQL + " where c1=?and c2=? and c3=?and c4=? and c5=? and c6=? and c7=? and c8=? and c9=? and c10=? and c11=? and c12=?"; // Testing with more than 100 params - StringBuilder sql3 = new StringBuilder("select * from " + AbstractSQLGenerator.escapeIdentifier(tableName4) - + " where c1=?and "); + StringBuilder sql3 = new StringBuilder( + "select * from " + AbstractSQLGenerator.escapeIdentifier(tableName4) + " where c1=?and "); for (int i = 2; i <= paramNameCount; i++) { sql3.append("c" + i + "=?"); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java index f6215479f..6f53b3dd8 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java @@ -1437,7 +1437,7 @@ public void testFailedToResumeTransaction() throws Exception { @Test public void testSmallBigDecimalValuesForLossOfPrecision() throws SQLException { try (SQLServerConnection con = getConnection(); - Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE)) { + Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE)) { con.setCalcBigDecimalScale(true); double bigDecimalLessThanOne = 0.1235; double bigDecimalGreaterThanOne = 1.1235; @@ -1453,10 +1453,10 @@ public void testSmallBigDecimalValuesForLossOfPrecision() throws SQLException { // Previously, the leading 0 would be counted as part of the precision. This would lead to the actual // value being stored as 0.123. - assertEquals(0, - cstmt.getObject("col1Value", BigDecimal.class).compareTo(BigDecimal.valueOf(bigDecimalLessThanOne))); - assertEquals(0, - cstmt.getObject("col2Value", BigDecimal.class).compareTo(BigDecimal.valueOf(bigDecimalGreaterThanOne))); + assertEquals(0, cstmt.getObject("col1Value", BigDecimal.class) + .compareTo(BigDecimal.valueOf(bigDecimalLessThanOne))); + assertEquals(0, cstmt.getObject("col2Value", BigDecimal.class) + .compareTo(BigDecimal.valueOf(bigDecimalGreaterThanOne))); } } } @@ -1476,7 +1476,8 @@ public void testLongBigDecimalValuesForLossOfPrecision() throws SQLException { // col1 has maximum scale (38) with a leading zero, for a precision of 38. col2 has maximum scale (37) when // using a lead integer other than zero, also resulting in a precision of 38. - stmt.executeUpdate("INSERT INTO " + tableName + " VALUES(0.98432319763138435186412316842316874322, 1.9843231976313843518641231684231687432)"); + stmt.executeUpdate("INSERT INTO " + tableName + + " VALUES(0.98432319763138435186412316842316874322, 1.9843231976313843518641231684231687432)"); try (PreparedStatement pstmt = con.prepareStatement("SELECT * FROM " + tableName)) { diff --git a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java index 9113ca192..d17fa1469 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java @@ -66,7 +66,6 @@ public abstract class AbstractTest { protected static String[] enclaveAttestationUrl = null; protected static String[] enclaveAttestationProtocol = null; - protected static String kerberosServer = null; protected static String kerberosServerPort = null; @@ -283,9 +282,12 @@ protected static void setupConnectionString() { if (null != kerberosServer && null != kerberosServerPort) { connectionStringKerberos = "jdbc:sqlserver://" + kerberosServer + ":" + kerberosServerPort + ";"; - connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, "authenticationScheme", "JavaKerberos"); - connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, "integratedSecurity", "true"); - connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, "trustServerCertificate", "true"); + connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, "authenticationScheme", + "JavaKerberos"); + connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, "integratedSecurity", + "true"); + connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, + "trustServerCertificate", "true"); connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, "encrypt", "false"); }