diff --git a/artio-codecs/src/main/java/uk/co/real_logic/artio/builder/CommonDecoderImpl.java b/artio-codecs/src/main/java/uk/co/real_logic/artio/builder/CommonDecoderImpl.java index aa952aba01..4391bd1bd9 100644 --- a/artio-codecs/src/main/java/uk/co/real_logic/artio/builder/CommonDecoderImpl.java +++ b/artio-codecs/src/main/java/uk/co/real_logic/artio/builder/CommonDecoderImpl.java @@ -17,6 +17,7 @@ import uk.co.real_logic.artio.fields.DecimalFloat; import uk.co.real_logic.artio.util.AsciiBuffer; +import uk.co.real_logic.artio.util.float_parsing.DecimalFloatOverflowHandler; import static uk.co.real_logic.artio.dictionary.generation.CodecUtil.MISSING_INT; import static uk.co.real_logic.artio.dictionary.generation.CodecUtil.MISSING_LONG; @@ -31,6 +32,7 @@ public abstract class CommonDecoderImpl protected int invalidTagId = Decoder.NO_ERROR; protected int rejectReason = Decoder.NO_ERROR; protected AsciiBuffer buffer; + protected DecimalFloatOverflowHandler decimalFloatOverflowHandler; public int invalidTagId() { @@ -82,11 +84,12 @@ public long getLong( public DecimalFloat getFloat( final AsciiBuffer buffer, - final DecimalFloat number, final int offset, final int length, final int tag, final boolean validation) + final DecimalFloat number, final int offset, final int length, final int tag, final boolean validation, + final DecimalFloatOverflowHandler decimalFloatOverflowHandler) { try { - return buffer.getFloat(number, offset, length); + return buffer.getFloat(number, offset, length, tag, decimalFloatOverflowHandler); } catch (final NumberFormatException | ArithmeticException e) { @@ -104,6 +107,14 @@ public DecimalFloat getFloat( } } + public DecimalFloat getFloat( + final AsciiBuffer buffer, + final DecimalFloat number, final int offset, final int length, final int tag, final boolean validation) + { + return getFloat(buffer, number, offset, length, tag, validation, null); + } + + public int getIntFlyweight( final AsciiBuffer buffer, final int offset, final int length, final int tag, final boolean validation) { @@ -168,11 +179,12 @@ public DecimalFloat getFloatFlyweight( final int offset, final int length, final int tag, - final boolean codecValidationEnabled) + final boolean codecValidationEnabled, + final DecimalFloatOverflowHandler decimalFloatOverflowHandler) { try { - return buffer.getFloat(number, offset, length); + return buffer.getFloat(number, offset, length, tag, decimalFloatOverflowHandler); } catch (final NumberFormatException e) { @@ -187,4 +199,15 @@ public DecimalFloat getFloatFlyweight( } } } + + public DecimalFloat getFloatFlyweight( + final AsciiBuffer buffer, + final DecimalFloat number, + final int offset, + final int length, + final int tag, + final boolean codecValidationEnabled) + { + return getFloatFlyweight(buffer, number, offset, length, tag, codecValidationEnabled, null); + } } diff --git a/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/CodecConfiguration.java b/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/CodecConfiguration.java index 6870d3173d..ed8a831393 100644 --- a/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/CodecConfiguration.java +++ b/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/CodecConfiguration.java @@ -21,6 +21,8 @@ import java.io.InputStream; import java.util.function.BiFunction; +import uk.co.real_logic.artio.util.float_parsing.DecimalFloatOverflowHandler; + public final class CodecConfiguration { /** @@ -55,6 +57,7 @@ public final class CodecConfiguration public static final String PARENT_PACKAGE_PROPERTY = "fix.codecs.parent_package"; public static final String FLYWEIGHTS_ENABLED_PROPERTY = "fix.codecs.flyweight"; public static final String REJECT_UNKNOWN_ENUM_VALUE_PROPERTY = "reject.unknown.enum.value"; + public static final String FLOAT_OVERFLOW_HANDLER_PROPERTY = "float.overflow.handler"; public static final String FIX_TAGS_IN_JAVADOC = "fix.codecs.tags_in_javadoc"; public static final String DEFAULT_PARENT_PACKAGE = "uk.co.real_logic.artio"; @@ -74,6 +77,7 @@ public final class CodecConfiguration private final GeneratorDictionaryConfiguration nonSharedDictionary = new GeneratorDictionaryConfiguration(null, null, null, Boolean.getBoolean(FIX_CODECS_ALLOW_DUPLICATE_FIELDS_PROPERTY)); + private Class decimalFloatOverflowHandler = null; public CodecConfiguration() { @@ -94,6 +98,18 @@ public CodecConfiguration outputPath(final String outputPath) return this; } + public CodecConfiguration decimalFloatOverflowHandler( + final Class decimalFloatOverflowHandler) + { + this.decimalFloatOverflowHandler = decimalFloatOverflowHandler; + return this; + } + + public Class getDecimalFloatOverflowHandler() + { + return decimalFloatOverflowHandler; + } + /** * Sets the parent package where classes are generated. Optional, defaults to {@link #DEFAULT_PARENT_PACKAGE}. * Different parent packages can be used to use multiple different fix dictionary versions, see the @@ -305,5 +321,24 @@ void conclude() "Please provide a path to the XML files either through the fileNames() or fileStreams() option."); } } + if (decimalFloatOverflowHandler == null) + { + + final String floatOverflowHandler = System.getProperty(FLOAT_OVERFLOW_HANDLER_PROPERTY); + if (floatOverflowHandler != null) + { + try + { + //noinspection unchecked + decimalFloatOverflowHandler = (Class)Class.forName( + floatOverflowHandler); + } + catch (final ClassNotFoundException e) + { + throw new IllegalArgumentException("Unable to load DecimalFloatOverflowHandler: " + + floatOverflowHandler, e); + } + } + } } } diff --git a/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/CodecGenerator.java b/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/CodecGenerator.java index 6964b4e895..b190002b6d 100644 --- a/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/CodecGenerator.java +++ b/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/CodecGenerator.java @@ -181,7 +181,8 @@ private static void generateDictionary( false, configuration.wrapEmptyBuffer(), codecRejectUnknownEnumValueEnabled, - configuration.fixTagsInJavadoc()).generate(); + configuration.fixTagsInJavadoc(), + configuration.getDecimalFloatOverflowHandler()).generate(); new PrinterGenerator(dictionary, decoderPackage, decoderOutput).generate(); new AcceptorGenerator(dictionary, decoderPackage, decoderOutput).generate(); @@ -203,7 +204,8 @@ private static void generateDictionary( true, configuration.wrapEmptyBuffer(), codecRejectUnknownEnumValueEnabled, - configuration.fixTagsInJavadoc()).generate(); + configuration.fixTagsInJavadoc(), + configuration.getDecimalFloatOverflowHandler()).generate(); } } } diff --git a/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/DecoderGenerator.java b/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/DecoderGenerator.java index f5bac6b87d..68af5b78c1 100644 --- a/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/DecoderGenerator.java +++ b/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/DecoderGenerator.java @@ -29,6 +29,7 @@ import uk.co.real_logic.artio.dictionary.ir.Field.Type; import uk.co.real_logic.artio.fields.*; import uk.co.real_logic.artio.util.MessageTypeEncoding; +import uk.co.real_logic.artio.util.float_parsing.DecimalFloatOverflowHandler; import java.io.IOException; import java.io.Writer; @@ -133,10 +134,12 @@ static String decoderClassName(final String name) final boolean flyweightsEnabled, final boolean wrapEmptyBuffer, final String codecRejectUnknownEnumValueEnabled, - final boolean fixTagsInJavadoc) + final boolean fixTagsInJavadoc, + final Class decimalFloatOverflowHandler) { super(dictionary, thisPackage, commonPackage, outputManager, validationClass, rejectUnknownFieldClass, - rejectUnknownEnumValueClass, flyweightsEnabled, codecRejectUnknownEnumValueEnabled, fixTagsInJavadoc); + rejectUnknownEnumValueClass, flyweightsEnabled, codecRejectUnknownEnumValueEnabled, fixTagsInJavadoc, + decimalFloatOverflowHandler); this.initialBufferSize = initialBufferSize; this.encoderPackage = encoderPackage; this.wrapEmptyBuffer = wrapEmptyBuffer; @@ -192,6 +195,11 @@ else if (type == HEADER) importEncoders(aggregate, out); + if (decimalFloatOverflowHandler != null) + { + out.append(importFor(decimalFloatOverflowHandler)); + } + generateAggregateClass(aggregate, type, className, out); }); } @@ -255,6 +263,13 @@ else if (type == HEADER) } out.append(classDeclaration(className, interfaces, false, aggregate.isInParent(), isGroup)); + if (decimalFloatOverflowHandler != null && type != HEADER) + { + out.append(String.format(" public %s() {\n\n", className)); + out.append(String.format(" decimalFloatOverflowHandler = new %s", + decimalFloatOverflowHandler.getSimpleName() + "();\n\n")); + out.append(" }\n\n"); + } generateValidation(out, aggregate, type); if (isMessage) { @@ -2041,7 +2056,8 @@ private String fieldDecodeMethod(final Field field, final String fieldName) return ""; } decodeMethod = String.format( - "getFloat(buffer, %s, valueOffset, valueLength, %d, " + CODEC_VALIDATION_ENABLED + ")", + "getFloat(buffer, %s, valueOffset, valueLength, %d, " + CODEC_VALIDATION_ENABLED + + ", decimalFloatOverflowHandler)", fieldName, field.number()); break; case CHAR: diff --git a/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/EncoderGenerator.java b/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/EncoderGenerator.java index df593a067c..2fed8b6eca 100644 --- a/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/EncoderGenerator.java +++ b/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/EncoderGenerator.java @@ -169,7 +169,7 @@ static String encoderClassName(final String name) final boolean fixTagsInJavadoc) { super(dictionary, builderPackage, builderCommonPackage, outputManager, validationClass, rejectUnknownFieldClass, - rejectUnknownEnumValueClass, false, codecRejectUnknownEnumValueEnabled, fixTagsInJavadoc); + rejectUnknownEnumValueClass, false, codecRejectUnknownEnumValueEnabled, fixTagsInJavadoc, null); final Component header = dictionary.header(); validateHasField(header, BEGIN_STRING); diff --git a/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/Generator.java b/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/Generator.java index 8b1cd51c4f..e9a6c07d3a 100644 --- a/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/Generator.java +++ b/artio-codecs/src/main/java/uk/co/real_logic/artio/dictionary/generation/Generator.java @@ -32,6 +32,7 @@ import uk.co.real_logic.artio.fields.UtcTimestampEncoder; import uk.co.real_logic.artio.util.AsciiBuffer; import uk.co.real_logic.artio.util.MutableAsciiBuffer; +import uk.co.real_logic.artio.util.float_parsing.DecimalFloatOverflowHandler; import java.io.IOException; import java.io.Writer; @@ -97,6 +98,7 @@ protected String commonCompoundImports(final String form, final boolean headerWr protected final boolean flyweightsEnabled; protected final String codecRejectUnknownEnumValueEnabled; protected final String scope; + protected final Class decimalFloatOverflowHandler; protected final boolean fixTagsInJavadoc; protected final Deque aggregateStack = new ArrayDeque<>(); @@ -111,7 +113,8 @@ protected Generator( final Class rejectUnknownEnumValueClass, final boolean flyweightsEnabled, final String codecRejectUnknownEnumValueEnabled, - final boolean fixTagsInJavadoc) + final boolean fixTagsInJavadoc, + final Class decimalFloatOverflowHandler) { this.dictionary = dictionary; this.thisPackage = thisPackage; @@ -125,6 +128,7 @@ protected Generator( this.fixTagsInJavadoc = fixTagsInJavadoc; scope = dictionary.shared() ? "protected" : "private"; + this.decimalFloatOverflowHandler = decimalFloatOverflowHandler; } public void generate() diff --git a/artio-codecs/src/main/java/uk/co/real_logic/artio/fields/ReadOnlyDecimalFloat.java b/artio-codecs/src/main/java/uk/co/real_logic/artio/fields/ReadOnlyDecimalFloat.java index 288adc4d57..785fda404f 100644 --- a/artio-codecs/src/main/java/uk/co/real_logic/artio/fields/ReadOnlyDecimalFloat.java +++ b/artio-codecs/src/main/java/uk/co/real_logic/artio/fields/ReadOnlyDecimalFloat.java @@ -41,7 +41,7 @@ public class ReadOnlyDecimalFloat implements Comparable private static final long VALUE_NAN_VALUE = Long.MIN_VALUE; private static final double DOUBLE_NAN_VALUE = Double.NaN; - private static final long VALUE_MAX_VAL = 999_999_999_999_999_999L; + public static final long VALUE_MAX_VAL = 999_999_999_999_999_999L; private static final long VALUE_MIN_VAL = -VALUE_MAX_VAL; private static final int SCALE_MAX_VAL = 127; private static final int SCALE_MIN_VAL = 0; diff --git a/artio-codecs/src/main/java/uk/co/real_logic/artio/util/AsciiBuffer.java b/artio-codecs/src/main/java/uk/co/real_logic/artio/util/AsciiBuffer.java index 6f762b813c..e4631e903e 100644 --- a/artio-codecs/src/main/java/uk/co/real_logic/artio/util/AsciiBuffer.java +++ b/artio-codecs/src/main/java/uk/co/real_logic/artio/util/AsciiBuffer.java @@ -17,6 +17,7 @@ import org.agrona.DirectBuffer; import uk.co.real_logic.artio.fields.DecimalFloat; +import uk.co.real_logic.artio.util.float_parsing.DecimalFloatOverflowHandler; /** * Mutable String class that flyweights a data buffer. This assumes a US-ASCII encoding @@ -67,7 +68,18 @@ public interface AsciiBuffer extends DirectBuffer long getMessageType(int offset, int length); - DecimalFloat getFloat(DecimalFloat number, int offset, int length); + DecimalFloat getFloat(DecimalFloat number, + int offset, + int length, + int tagId, + DecimalFloatOverflowHandler decimalFloatOverflowHandler); + + default DecimalFloat getFloat(DecimalFloat number, + int offset, + int length) + { + return getFloat(number, offset, length, -1, null); + } int getLocalMktDate(int offset, int length); diff --git a/artio-codecs/src/main/java/uk/co/real_logic/artio/util/MutableAsciiBuffer.java b/artio-codecs/src/main/java/uk/co/real_logic/artio/util/MutableAsciiBuffer.java index 3b048d25a2..26afafd2f2 100644 --- a/artio-codecs/src/main/java/uk/co/real_logic/artio/util/MutableAsciiBuffer.java +++ b/artio-codecs/src/main/java/uk/co/real_logic/artio/util/MutableAsciiBuffer.java @@ -21,6 +21,7 @@ import org.agrona.concurrent.UnsafeBuffer; import uk.co.real_logic.artio.fields.*; import uk.co.real_logic.artio.util.float_parsing.AsciiBufferCharReader; +import uk.co.real_logic.artio.util.float_parsing.DecimalFloatOverflowHandler; import uk.co.real_logic.artio.util.float_parsing.DecimalFloatParser; import java.nio.ByteBuffer; @@ -167,9 +168,19 @@ public long getMessageType(final int offset, final int length) } @SuppressWarnings("FinalParameters") - public DecimalFloat getFloat(final DecimalFloat number, int offset, int length) - { - return DecimalFloatParser.extract(number, AsciiBufferCharReader.INSTANCE, this, offset, length); + public DecimalFloat getFloat(final DecimalFloat number, + int offset, + int length, + int tagId, + DecimalFloatOverflowHandler decimalFloatOverflowHandler) + { + return DecimalFloatParser.extract(number, + AsciiBufferCharReader.INSTANCE, + this, + offset, + length, + tagId, + decimalFloatOverflowHandler); } public int getLocalMktDate(final int offset, final int length) diff --git a/artio-codecs/src/main/java/uk/co/real_logic/artio/util/float_parsing/DecimalFloatOverflowHandler.java b/artio-codecs/src/main/java/uk/co/real_logic/artio/util/float_parsing/DecimalFloatOverflowHandler.java new file mode 100644 index 0000000000..419f1c52f9 --- /dev/null +++ b/artio-codecs/src/main/java/uk/co/real_logic/artio/util/float_parsing/DecimalFloatOverflowHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015-2025 Real Logic Limited. + * + * 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 + * + * https://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. + */ +package uk.co.real_logic.artio.util.float_parsing; + +import uk.co.real_logic.artio.fields.DecimalFloat; + +public interface DecimalFloatOverflowHandler +{ + + /** + * + * @param charReader object that knows how to create a String or to fetch a char our of the data + * @param data buffer containing the decimal float value bytes + * @param offset offset within the buffer where the float number starts + * @param length length of the float number in bytes + * @param positionOfOverflow position within the decimal float bytes that corresponds to the digit where the overflow occurred + * @param positionOfDecimalPoint position within the decimal float bytes that corresponds to the decimal point + * @param tagId tag id where the float overflow happened + * @return instance of DecimalFloat to be use + * @param generic buffer + */ + DecimalFloat handleOverflow( + CharReader charReader, + Data data, + int offset, + int length, + int positionOfOverflow, + int positionOfDecimalPoint, + int tagId); +} diff --git a/artio-codecs/src/main/java/uk/co/real_logic/artio/util/float_parsing/DecimalFloatParser.java b/artio-codecs/src/main/java/uk/co/real_logic/artio/util/float_parsing/DecimalFloatParser.java index f4a33b2419..a3c1bfbb8b 100644 --- a/artio-codecs/src/main/java/uk/co/real_logic/artio/util/float_parsing/DecimalFloatParser.java +++ b/artio-codecs/src/main/java/uk/co/real_logic/artio/util/float_parsing/DecimalFloatParser.java @@ -1,3 +1,18 @@ +/* + * Copyright 2015-2025 Real Logic Limited. + * + * 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 + * + * https://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. + */ package uk.co.real_logic.artio.util.float_parsing; @@ -19,6 +34,18 @@ public static DecimalFloat extract( final Data data, final int offset, final int length) + { + return extract(number, charReader, data, offset, length, -1, null); + } + + public static DecimalFloat extract( + final DecimalFloat number, + final CharReader charReader, + final Data data, + final int offset, + final int length, + final int tagId, + final DecimalFloatOverflowHandler overflowHandler) { // Throw away trailing spaces int workingOffset = offset; @@ -61,6 +88,9 @@ public static DecimalFloat extract( int workingScale = 0; long value = 0; + long aux = 0; + final int timesNeg = 0; + int dotIndex = 0; for (int index = workingOffset; index < endOfSignificand; index++) { final char charValue = charReader.charAt(data, index); @@ -68,15 +98,34 @@ public static DecimalFloat extract( { // number of digits after the dot workingScale = endOfSignificand - (index + 1); + dotIndex = index; } else { final int digit = charReader.getDigit(data, index, charValue); - value = value * 10 + digit; - if (value < 0) + aux = value * 10 + digit; + if (aux < 0) + { + if (overflowHandler != null) + { + return getDecimalFloat(charReader, + data, + offset, + length, + overflowHandler, + index, + dotIndex, + tagId); + } + else + { + throw new ArithmeticException( + "Out of range: when parsing " + charReader.asString(data, offset, length)); + } + } + else { - throw new ArithmeticException( - "Out of range: when parsing " + charReader.asString(data, offset, length)); + value = aux; } } } @@ -93,14 +142,45 @@ else if (exponentLength == 0) throw new NumberFormatException(charReader.asString(data, offset, length).toString()); } - final int scale = workingScale - exponent; + return updateValue(number, workingScale, exponent, timesNeg, negative, value); + } + + private static DecimalFloat updateValue( + final DecimalFloat number, + final int workingScale, + final int exponent, + final int timesNeg, + final boolean negative, + final long value) + { + final int scale = workingScale - exponent - timesNeg; final long signedValue = negative ? -1 * value : value; return number.set( - (scale >= 0) ? signedValue : signedValue * pow10(-scale), - Math.max(scale, 0) + (scale >= 0) ? signedValue : signedValue * pow10(-scale), + Math.max(scale, 0) ); } + private static DecimalFloat getDecimalFloat( + final CharReader charReader, + final Data data, + final int offset, + final int length, + final DecimalFloatOverflowHandler overflowHandler, + final int index, + final int dotIndex, + final int tagId) + { + return overflowHandler.handleOverflow( + charReader, + data, + offset, + length, + index - offset, + dotIndex - offset, + tagId); + } + private static int parseExponent( final CharReader charReader, final Data data, diff --git a/artio-codecs/src/test/java/uk/co/real_logic/artio/builder/CommonDecoderImplTest.java b/artio-codecs/src/test/java/uk/co/real_logic/artio/builder/CommonDecoderImplTest.java new file mode 100644 index 0000000000..34ee9bf6a8 --- /dev/null +++ b/artio-codecs/src/test/java/uk/co/real_logic/artio/builder/CommonDecoderImplTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015-2025 Real Logic Limited. + * + * 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 + * + * https://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. + */ +package uk.co.real_logic.artio.builder; + +import java.util.stream.Stream; +import uk.co.real_logic.artio.fields.DecimalFloat; +import uk.co.real_logic.artio.util.MutableAsciiBuffer; +import uk.co.real_logic.artio.util.float_parsing.CharReader; +import uk.co.real_logic.artio.util.float_parsing.DecimalFloatOverflowHandler; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class CommonDecoderImplTest +{ + + public static Stream decimalFloatCodecData() + { + return Stream.of( + Arguments.of("922337203.6854775807", 19, 9, 9223372036854775807L, 10), + Arguments.of("922337203.6854775808", 19, 9, 999, 1), + Arguments.of("9.223372036854775808", 19, 1, 999, 1), + Arguments.of("922337203685477580.8", 19, 18, 999, 1), + Arguments.of("0.009223372036854775808", 22, 1, 999, 1) + ); + } + + static class SimpleDecoderImpl extends CommonDecoderImpl + { + } + + + + + @ParameterizedTest(name = "{index}: {0} => {1},{2},{3},{4}") + @MethodSource("decimalFloatCodecData") + void testGetFloat(final String valueWithOverflow, + final int positionOfOverflow, + final int positionOfDecimalPoint, + final long finalValue, + final int finalScale) + { + final SimpleDecoderImpl decoder = new SimpleDecoderImpl(); + final DecimalFloat value = decoder.getFloat( + new MutableAsciiBuffer(valueWithOverflow.getBytes(), 0, valueWithOverflow.length()), + new DecimalFloat(), + 0, + valueWithOverflow.length(), + 21, + true, + new DecimalFloatOverflowHandler() + { + @Override + public DecimalFloat handleOverflow( + final CharReader charReader, + final Data data, + final int offset, + final int length, + final int posOverflow, + final int posDecimal, + final int tagId) + { + + assertEquals(valueWithOverflow, charReader.asString(data, offset, length)); + assertEquals(positionOfOverflow, posOverflow); + assertEquals(positionOfDecimalPoint, posDecimal); + assertEquals(21, tagId); + return new DecimalFloat(999L, 1); + } + }); + } +} \ No newline at end of file diff --git a/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/AbstractDecoderGeneratorTest.java b/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/AbstractDecoderGeneratorTest.java index 95d9e1ae77..54c22efa58 100644 --- a/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/AbstractDecoderGeneratorTest.java +++ b/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/AbstractDecoderGeneratorTest.java @@ -133,7 +133,7 @@ private static Map generateSources( MESSAGE_EXAMPLE, 1, TEST_PACKAGE, TEST_PARENT_PACKAGE, TEST_PACKAGE, outputManager, validationClass, rejectUnknownField, rejectUnknownEnumValue, flyweightStringsEnabled, wrapEmptyBuffer, - String.valueOf(rejectingUnknownEnumValue), true); + String.valueOf(rejectingUnknownEnumValue), true, null); final EncoderGenerator encoderGenerator = new EncoderGenerator(MESSAGE_EXAMPLE, TEST_PACKAGE, TEST_PARENT_PACKAGE, outputManager, ValidationOn.class, RejectUnknownFieldOn.class, RejectUnknownEnumValueOn.class, RUNTIME_REJECT_UNKNOWN_ENUM_VALUE_PROPERTY, true); diff --git a/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/AcceptorGeneratorTest.java b/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/AcceptorGeneratorTest.java index ba1505a3b8..92c11d005c 100644 --- a/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/AcceptorGeneratorTest.java +++ b/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/AcceptorGeneratorTest.java @@ -45,7 +45,7 @@ public class AcceptorGeneratorTest private static final DecoderGenerator DECODER_GENERATOR = new DecoderGenerator( MESSAGE_EXAMPLE, 1, TEST_PACKAGE, TEST_PARENT_PACKAGE, TEST_PACKAGE, OUTPUT_MANAGER, ValidationOn.class, RejectUnknownFieldOff.class, RejectUnknownEnumValueOn.class, false, false, - Generator.RUNTIME_REJECT_UNKNOWN_ENUM_VALUE_PROPERTY, true); + Generator.RUNTIME_REJECT_UNKNOWN_ENUM_VALUE_PROPERTY, true, null); private static final AcceptorGenerator ACCEPTOR_GENERATOR = new AcceptorGenerator( MESSAGE_EXAMPLE, TEST_PACKAGE, OUTPUT_MANAGER); private static Class acceptor; diff --git a/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/CopyToEncoderGeneratorTest.java b/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/CopyToEncoderGeneratorTest.java index c89436301a..f09f060e26 100644 --- a/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/CopyToEncoderGeneratorTest.java +++ b/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/CopyToEncoderGeneratorTest.java @@ -77,7 +77,7 @@ private static Map generateClasses() MESSAGE_EXAMPLE, 1, TEST_PACKAGE, TEST_PARENT_PACKAGE, TEST_PACKAGE, outputManager, ValidationOn.class, RejectUnknownFieldOn.class, RejectUnknownEnumValueOn.class, false, false, - RUNTIME_REJECT_UNKNOWN_ENUM_VALUE_PROPERTY, true); + RUNTIME_REJECT_UNKNOWN_ENUM_VALUE_PROPERTY, true, null); final EncoderGenerator encoderGenerator = new EncoderGenerator(MESSAGE_EXAMPLE, TEST_PACKAGE, TEST_PARENT_PACKAGE, outputManager, ValidationOn.class, RejectUnknownFieldOn.class, RejectUnknownEnumValueOn.class, RUNTIME_REJECT_UNKNOWN_ENUM_VALUE_PROPERTY, true); diff --git a/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/FloatOverflowHandlerDecoderTest.java b/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/FloatOverflowHandlerDecoderTest.java new file mode 100644 index 0000000000..c0f98c0bd9 --- /dev/null +++ b/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/FloatOverflowHandlerDecoderTest.java @@ -0,0 +1,97 @@ +/* + * Copyright 2025 Adaptive Financial Consulting Ltd. + * + * 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 + * + * https://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. + */ +package uk.co.real_logic.artio.dictionary.generation; + +import static uk.co.real_logic.artio.dictionary.generation.CodecGenerationWrapper.dictionaryStream; +import static uk.co.real_logic.artio.util.Reflection.get; + + +import uk.co.real_logic.artio.builder.Decoder; +import uk.co.real_logic.artio.util.FloatOverflowHandlerSample; +import uk.co.real_logic.artio.fields.DecimalFloat; +import uk.co.real_logic.artio.fields.ReadOnlyDecimalFloat; +import uk.co.real_logic.artio.util.float_parsing.DecimalFloatOverflowHandler; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * This test ensures that for a given dictionary, the generated decoder uses the provided handler when an overflow + * occurs. + * It also ensures that the default (existing before this change) behavior is used when no handler is provided. + */ +public class FloatOverflowHandlerDecoderTest +{ + public static final String MESSAGE_WITH_OVERFLOW = + "8=FIX.4.4\u00019=122\u000135=D\u000149=initiator\u000156=acceptor\u000134=1" + + "\u000152=20231220-13:12:16.020\u000144=922337203.6854775807999\u000110=0\u0001"; + private static final CodecGenerationWrapper WRAPPER = new CodecGenerationWrapper(); + + private static Class nosDecoder; + + private Decoder decoder; + + public static void withHandler(final Class handler) throws Exception + { + WRAPPER.generate(config -> + { + config + .fileStreams(dictionaryStream("float_overflow_dictionary")) + .decimalFloatOverflowHandler(handler); + }); + + WRAPPER.compile(WRAPPER.encoder(null, "NewOrderSingle")); + nosDecoder = WRAPPER.decoder("NewOrderSingle"); + } + + public void initWithHandler() throws Exception + { + withHandler(FloatOverflowHandlerSample.class); + decoder = (Decoder)nosDecoder.getDeclaredConstructor().newInstance(); + } + + public void initWithoutHandler() throws Exception + { + withHandler(null); + decoder = (Decoder)nosDecoder.getDeclaredConstructor().newInstance(); + } + + @Test + public void shouldUseHandlerWhenOverflowOccurs() throws Exception + { + initWithHandler(); + WRAPPER.decode(decoder, MESSAGE_WITH_OVERFLOW); + + Assertions.assertTrue(decoder.validate()); + assertPrice(decoder, new DecimalFloat(999L, 1)); + } + + @Test + public void shouldUseDefaultWhenOverflowOccurs() throws Exception + { + initWithoutHandler(); + WRAPPER.decode(decoder, MESSAGE_WITH_OVERFLOW); + + assertPrice(decoder, ReadOnlyDecimalFloat.MISSING_FLOAT); + Assertions.assertFalse(decoder.validate()); + } + + private void assertPrice(final Object decoder, final Object priceValue) throws Exception + { + Assertions.assertEquals(priceValue, get(decoder, "price")); + } + +} diff --git a/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/PrinterGeneratorTest.java b/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/PrinterGeneratorTest.java index 9d7c917a6f..778e78278d 100644 --- a/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/PrinterGeneratorTest.java +++ b/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/PrinterGeneratorTest.java @@ -42,7 +42,7 @@ public class PrinterGeneratorTest MESSAGE_EXAMPLE, 1, TEST_PACKAGE, TEST_PARENT_PACKAGE, TEST_PACKAGE, OUTPUT_MANAGER, ValidationOn.class, RejectUnknownFieldOff.class, RejectUnknownEnumValueOn.class, false, false, - Generator.RUNTIME_REJECT_UNKNOWN_ENUM_VALUE_PROPERTY, true); + Generator.RUNTIME_REJECT_UNKNOWN_ENUM_VALUE_PROPERTY, true, null); private static final EncoderGenerator ENCODER_GENERATOR = new EncoderGenerator( MESSAGE_EXAMPLE, TEST_PACKAGE, TEST_PARENT_PACKAGE, OUTPUT_MANAGER, ValidationOn.class, RejectUnknownFieldOn.class, RejectUnknownEnumValueOn.class, RUNTIME_REJECT_UNKNOWN_ENUM_VALUE_PROPERTY, diff --git a/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/ToEncoderDecoderGeneratorTest.java b/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/ToEncoderDecoderGeneratorTest.java index ddb4516ad2..80d62ee074 100644 --- a/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/ToEncoderDecoderGeneratorTest.java +++ b/artio-codecs/src/test/java/uk/co/real_logic/artio/dictionary/generation/ToEncoderDecoderGeneratorTest.java @@ -94,7 +94,7 @@ private static Map generateClasses(final boolean flyweight MESSAGE_EXAMPLE, 1, TEST_PACKAGE, TEST_PARENT_PACKAGE, TEST_PACKAGE, outputManager, ValidationOn.class, RejectUnknownFieldOn.class, RejectUnknownEnumValueOn.class, flyweightStringsEnabled, false, - RUNTIME_REJECT_UNKNOWN_ENUM_VALUE_PROPERTY, true); + RUNTIME_REJECT_UNKNOWN_ENUM_VALUE_PROPERTY, true, null); final EncoderGenerator encoderGenerator = new EncoderGenerator(MESSAGE_EXAMPLE, TEST_PACKAGE, TEST_PARENT_PACKAGE, outputManager, ValidationOn.class, RejectUnknownFieldOn.class, RejectUnknownEnumValueOn.class, RUNTIME_REJECT_UNKNOWN_ENUM_VALUE_PROPERTY, true); diff --git a/artio-codecs/src/test/java/uk/co/real_logic/artio/fields/DecimalFloatDecodingTest.java b/artio-codecs/src/test/java/uk/co/real_logic/artio/fields/DecimalFloatDecodingTest.java index 38c9794844..60386406eb 100644 --- a/artio-codecs/src/test/java/uk/co/real_logic/artio/fields/DecimalFloatDecodingTest.java +++ b/artio-codecs/src/test/java/uk/co/real_logic/artio/fields/DecimalFloatDecodingTest.java @@ -29,11 +29,15 @@ @RunWith(Parameterized.class) public class DecimalFloatDecodingTest { + + @Parameters(name = "{index}: {0} => {1},{2}") public static Iterable decimalFloatCodecData() { return Arrays.asList(new Object[][] { + {"9.99999999999999999", ReadOnlyDecimalFloat.VALUE_MAX_VAL, 17}, + {"99999999999999999.9", ReadOnlyDecimalFloat.VALUE_MAX_VAL, 1}, {"55.36", 5536L, 2}, {"5.536e0", 5536L, 3}, {"5.536e1", 5536L, 2}, diff --git a/artio-codecs/src/test/java/uk/co/real_logic/artio/util/FloatOverflowHandlerSample.java b/artio-codecs/src/test/java/uk/co/real_logic/artio/util/FloatOverflowHandlerSample.java new file mode 100644 index 0000000000..93d6b7b132 --- /dev/null +++ b/artio-codecs/src/test/java/uk/co/real_logic/artio/util/FloatOverflowHandlerSample.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2025 Real Logic Limited. + * + * 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 + * + * https://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. + */ +package uk.co.real_logic.artio.util; + +import uk.co.real_logic.artio.fields.DecimalFloat; +import uk.co.real_logic.artio.util.float_parsing.CharReader; +import uk.co.real_logic.artio.util.float_parsing.DecimalFloatOverflowHandler; + +public class FloatOverflowHandlerSample implements DecimalFloatOverflowHandler +{ + @Override + public DecimalFloat handleOverflow( + final CharReader charReader, + final Data data, + final int offset, + final int length, + final int positionOfOverflow, + final int positionOfDecimalPoint, + final int tagId) + { + return new DecimalFloat(999L, 1); + } +} diff --git a/artio-codecs/src/test/resources/uk/co/real_logic/artio/dictionary/float_overflow_dictionary.xml b/artio-codecs/src/test/resources/uk/co/real_logic/artio/dictionary/float_overflow_dictionary.xml new file mode 100644 index 0000000000..c1bc75b05b --- /dev/null +++ b/artio-codecs/src/test/resources/uk/co/real_logic/artio/dictionary/float_overflow_dictionary.xml @@ -0,0 +1,188 @@ + +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +