diff --git a/src/main/java/net/openhft/chronicle/wire/AbstractWire.java b/src/main/java/net/openhft/chronicle/wire/AbstractWire.java index b8717a56b2..c196709dc0 100644 --- a/src/main/java/net/openhft/chronicle/wire/AbstractWire.java +++ b/src/main/java/net/openhft/chronicle/wire/AbstractWire.java @@ -41,8 +41,16 @@ import static net.openhft.chronicle.core.UnsafeMemory.MEMORY; import static net.openhft.chronicle.wire.Wires.*; +/** + * Represents the AbstractWire class which serves as a base for all Wire implementations. + * This class provides fundamental shared behaviors, configurations, and initializations for Wire types. + */ public abstract class AbstractWire implements Wire { + + // Default padding configuration loaded from the system properties. public static final boolean DEFAULT_USE_PADDING = Jvm.getBoolean("wire.usePadding", false); + + // Message used when a header is detected inside another header. private static final String INSIDE_HEADER_MESSAGE = "you cant put a header inside a header, check that " + "you have not nested the documents. If you are using Chronicle-Queue please " + "ensure that you have a unique instance of the Appender per thread, in " + @@ -68,6 +76,7 @@ public abstract class AbstractWire implements Wire { private boolean insideHeader; private HeadNumberChecker headNumberChecker; private boolean usePadding = DEFAULT_USE_PADDING; + private boolean generateTuples = GENERATE_TUPLES; @SuppressWarnings("rawtypes") protected AbstractWire(@NotNull Bytes bytes, boolean use8bit) { @@ -76,6 +85,38 @@ protected AbstractWire(@NotNull Bytes bytes, boolean use8bit) { notCompleteIsNotPresent = bytes.sharedMemory(); } + /** + * Sets the flag to determine whether this Wire should generate tuples. When enabled, + * this feature allows the Wire to create and handle tuple data structures dynamically. + *

+ * Usage of this feature should be aligned with the specific requirements of the Wire's + * operational context. Enabling tuple generation may impact how data is processed and + * represented within the Wire. + * + * @param generateTuples A boolean value indicating whether to enable or disable + * tuple generation. + */ + @Override + public void generateTuples(boolean generateTuples) { + this.generateTuples = generateTuples; + } + + /** + * Retrieves the current status of the tuple generation feature within this Wire. + * When enabled, this feature allows for the dynamic creation and handling of tuple + * data structures within the Wire. + *

+ * The return value of this method indicates whether the Wire is currently configured + * to generate and handle tuples, which can be crucial for understanding the Wire's + * current data processing behavior. + * + * @return A boolean value indicating whether tuple generation is currently enabled. + */ + @Override + public boolean generateTuples() { + return generateTuples; + } + private static long throwNotEnoughSpace(long maxlen, @NotNull Bytes bytes) { throw new IllegalStateException("not enough space to write " + maxlen + " was " + bytes.writeRemaining() + " limit " + bytes.writeLimit() + " type " + bytes.getClass()); } diff --git a/src/main/java/net/openhft/chronicle/wire/BinaryWire.java b/src/main/java/net/openhft/chronicle/wire/BinaryWire.java index 9841330d71..22c6241d2b 100644 --- a/src/main/java/net/openhft/chronicle/wire/BinaryWire.java +++ b/src/main/java/net/openhft/chronicle/wire/BinaryWire.java @@ -3561,7 +3561,7 @@ public Object typePrefixOrObject(Class tClass) { try { return sb == null ? null : classLookup().forName(sb); } catch (ClassNotFoundRuntimeException e) { - if (Wires.dtoInterface(tClass) && GENERATE_TUPLES) { + if (Wires.dtoInterface(tClass) && generateTuples()) { return Wires.tupleFor(tClass, sb.toString()); } if (THROW_CNFRE) diff --git a/src/main/java/net/openhft/chronicle/wire/TextWire.java b/src/main/java/net/openhft/chronicle/wire/TextWire.java index 703ffcd01d..7b2bf7a45c 100644 --- a/src/main/java/net/openhft/chronicle/wire/TextWire.java +++ b/src/main/java/net/openhft/chronicle/wire/TextWire.java @@ -1881,7 +1881,7 @@ public Object typePrefixOrObject(Class tClass) { return o; } } - if (Wires.dtoInterface(tClass) && GENERATE_TUPLES && ObjectUtils.implementationToUse(tClass) == tClass) + if (Wires.dtoInterface(tClass) && generateTuples() && ObjectUtils.implementationToUse(tClass) == tClass) return Wires.tupleFor(tClass, null); return null; } @@ -1889,7 +1889,7 @@ public Object typePrefixOrObject(Class tClass) { @Nullable private Object handleCNFE(Class tClass, ClassNotFoundRuntimeException e, StringBuilder stringBuilder) { if (tClass == null) { - if (GENERATE_TUPLES) { + if (generateTuples()) { return Wires.tupleFor(null, stringBuilder.toString()); } String message = "Unable to load " + stringBuilder + ", is a class alias missing."; @@ -1916,7 +1916,7 @@ private Object handleCNFE(Class tClass, ClassNotFoundRuntimeException e, StringB } } - } else if (GENERATE_TUPLES && tClass.getClassLoader() != null && tClass.isInterface()) { + } else if (generateTuples() && tClass.getClassLoader() != null && tClass.isInterface()) { return Wires.tupleFor(tClass, stringBuilder.toString()); } diff --git a/src/main/java/net/openhft/chronicle/wire/WireIn.java b/src/main/java/net/openhft/chronicle/wire/WireIn.java index fd7f39cd93..6c402621ba 100644 --- a/src/main/java/net/openhft/chronicle/wire/WireIn.java +++ b/src/main/java/net/openhft/chronicle/wire/WireIn.java @@ -235,6 +235,35 @@ default boolean hasMetaDataPrefix() { return false; } + /** + * Sets the state of tuple generation for the implementing class. + *

+ * Implementing classes are expected to override this method to provide meaningful functionality + * if they support dynamic tuple generation. + * + * @param generateTuples A boolean value indicating the desired state of tuple generation. + * Setting this to true in the default implementation will result in an exception. + * @throws UnsupportedOperationException if the method is invoked with true in its default implementation, + * indicating that tuple generation is not supported. + */ + default void generateTuples(boolean generateTuples) { + if (generateTuples) + throw new UnsupportedOperationException(); + } + + /** + * Retrieves the current state of tuple generation in the implementing class. + *

+ * Implementing classes should override this method to return the actual state of tuple generation + * if they support this feature. + * + * @return A boolean value indicating whether tuple generation is currently enabled. The default + * implementation always returns false. + */ + default boolean generateTuples() { + return false; + } + enum HeaderType { NONE, DATA, META_DATA, EOF } diff --git a/src/main/java/net/openhft/chronicle/wire/WireTypeConverter.java b/src/main/java/net/openhft/chronicle/wire/WireTypeConverter.java index c2cbff82bb..c34beefc5f 100644 --- a/src/main/java/net/openhft/chronicle/wire/WireTypeConverter.java +++ b/src/main/java/net/openhft/chronicle/wire/WireTypeConverter.java @@ -18,7 +18,7 @@ public CharSequence yamlToJson(CharSequence yaml) { return delegate.yamlToJson(yaml); } - public void addAlias(Class newClass, String oldTypeName) { + public void addAlias(Class newClass, String oldTypeName) { delegate.addAlias(newClass, oldTypeName); } } diff --git a/src/main/java/net/openhft/chronicle/wire/Wires.java b/src/main/java/net/openhft/chronicle/wire/Wires.java index 071d20f8c0..ec82f4fbe6 100644 --- a/src/main/java/net/openhft/chronicle/wire/Wires.java +++ b/src/main/java/net/openhft/chronicle/wire/Wires.java @@ -803,19 +803,38 @@ static Marshallable newInstance(Constructor constructor, String typeName) { } } + /** + * Generates and returns a tuple instance for the specified class and type name. + * This method is particularly useful for creating tuples for data types or classes + * that are not available locally, enabling data to be read from a WireIn and passed on. + *

+ * This method ensures the class type specified is an interface and leverages the + * Marshallable function to generate the tuple. If the specified class is null or + * is the Object class, it defaults to using the Marshallable class. + *

+ * Note: If the provided class type is not an interface or if the required tuple + * cannot be generated for the given type name, the method returns null and logs a + * warning. + * + * @param The type parameter representing the parent type of tuple to be returned. + * @param tClass The class type for which the tuple is required. Must be an interface. + * If null or Object.class, defaults to Marshallable.class. + * @param typeName The type name used in tuple generation. + * It's used to identify the specific tuple type to be created. + * @return A tuple of type T, or null if it's not possible to create a tuple + * for the given class type and type name. + */ @Nullable public static T tupleFor(Class tClass, String typeName) { - if (!GENERATE_TUPLES) { - Jvm.warn().on(Wires.class, "Cannot find a class for " + typeName + " are you missing an alias?"); - return null; - } - if (tClass == null || tClass == Object.class) tClass = (Class) Marshallable.class; + // Ensure the provided class is an interface. if (!tClass.isInterface()) { Jvm.warn().on(Wires.class, "Cannot generate a class for " + typeName + " are you missing an alias?"); return null; } + + // Use the appropriate function to generate the tuple. return (T) MARSHALLABLE_FUNCTION.get(tClass).apply(typeName); } diff --git a/src/test/java/net/openhft/chronicle/wire/UnknownEnumTest.java b/src/test/java/net/openhft/chronicle/wire/UnknownEnumTest.java index 3644e79eff..2da33693c2 100644 --- a/src/test/java/net/openhft/chronicle/wire/UnknownEnumTest.java +++ b/src/test/java/net/openhft/chronicle/wire/UnknownEnumTest.java @@ -91,7 +91,6 @@ public void shouldConvertEnumValueToStringWhenTypeIsNotKnownInBinaryWireThrows() @Test public void shouldGenerateFriendlyErrorMessageWhenTypeIsNotKnownInTextWire() { - Wires.GENERATE_TUPLES = true; try { final TextWire textWire = TextWire.from("enumField: !UnknownEnum QUX"); textWire.valueIn.wireIn().read("enumField").object(); @@ -100,9 +99,7 @@ public void shouldGenerateFriendlyErrorMessageWhenTypeIsNotKnownInTextWire() { } catch (Exception e) { String message = e.getMessage().replaceAll(" [a-z0-9.]+.Proxy\\d+", " ProxyXX"); assertThat(message, - is(equalTo("Trying to read marshallable class ProxyXX at [pos: 23, rlim: 27, wlim: 27, cap: 27 ] QUX expected to find a {"))); - } finally { - Wires.GENERATE_TUPLES = false; + is(equalTo("java.lang.ClassNotFoundException: Unable to load UnknownEnum, is a class alias missing."))); } } diff --git a/src/test/java/net/openhft/chronicle/wire/WireTestCommon.java b/src/test/java/net/openhft/chronicle/wire/WireTestCommon.java index 3d5922be22..915ca64913 100644 --- a/src/test/java/net/openhft/chronicle/wire/WireTestCommon.java +++ b/src/test/java/net/openhft/chronicle/wire/WireTestCommon.java @@ -44,8 +44,6 @@ public class WireTestCommon { private final Map, String> ignoredExceptions = new LinkedHashMap<>(); private final Map, String> expectedExceptions = new LinkedHashMap<>(); - private boolean gt; - public WireTestCommon() { ignoreException("The incubating features are subject to change"); ignoreException("NamedThreadFactory created here"); @@ -157,14 +155,9 @@ public void afterChecks() { protected void preAfter() { } - @Before - public void rememberGenerateTuples() { - gt = Wires.GENERATE_TUPLES; - } - @After public void restoreGenerateTuples() { - Wires.GENERATE_TUPLES = gt; + Wires.GENERATE_TUPLES = false; } @Before diff --git a/src/test/java/net/openhft/chronicle/wire/marshallable/CNFREOnMissingClassTest.java b/src/test/java/net/openhft/chronicle/wire/marshallable/CNFREOnMissingClassTest.java index aa5f0c6a09..13b2d10bb4 100644 --- a/src/test/java/net/openhft/chronicle/wire/marshallable/CNFREOnMissingClassTest.java +++ b/src/test/java/net/openhft/chronicle/wire/marshallable/CNFREOnMissingClassTest.java @@ -19,9 +19,9 @@ public class CNFREOnMissingClassTest extends WireTestCommon { @Test(expected = ClassNotFoundRuntimeException.class) public void throwClassNotFoundRuntimeExceptionOnMissingClassAlias() { WiresTest.wiresThrowCNFRE(true); - Wires.GENERATE_TUPLES = false; Wire wire = new TextWire(Bytes.from("" + "a: !Aaa { hi: bye }")); + wire.generateTuples(false); Object object = wire.read("a").object(); System.out.println(object); assertNotNull(object); diff --git a/src/test/java/net/openhft/chronicle/wire/method/MethodWriterByInterfaceTest.java b/src/test/java/net/openhft/chronicle/wire/method/MethodWriterByInterfaceTest.java index e02bf7ef4a..73f8334a35 100644 --- a/src/test/java/net/openhft/chronicle/wire/method/MethodWriterByInterfaceTest.java +++ b/src/test/java/net/openhft/chronicle/wire/method/MethodWriterByInterfaceTest.java @@ -45,22 +45,22 @@ public void teardown() { @Test public void writeReadViaImplementation() { - checkWriteReadViaImplementation(WireType.TEXT); + checkWriteReadViaImplementation(WireType.TEXT, false); } @Test public void writeReadViaImplementationGenerateTuples() { - Wires.GENERATE_TUPLES = true; - checkWriteReadViaImplementation(WireType.TEXT); + checkWriteReadViaImplementation(WireType.TEXT, true); } @Test public void writeReadViaImplementationYaml() { - checkWriteReadViaImplementation(WireType.YAML_ONLY); + checkWriteReadViaImplementation(WireType.YAML_ONLY, false); } - private void checkWriteReadViaImplementation(WireType wireType) { + private void checkWriteReadViaImplementation(WireType wireType, boolean generateTuples) { Wire tw = wireType.apply(Bytes.allocateElasticOnHeap()); + tw.generateTuples(generateTuples); MWBI0 mwbi0 = tw.methodWriter(MWBI0.class); mwbi0.method(new MWBImpl("name", 1234567890123456L)); assertFalse(Proxy.isProxyClass(mwbi0.getClass())); diff --git a/src/test/java/net/openhft/chronicle/wire/method/VanillaMethodReaderTest.java b/src/test/java/net/openhft/chronicle/wire/method/VanillaMethodReaderTest.java index 1242ec499b..745019952f 100644 --- a/src/test/java/net/openhft/chronicle/wire/method/VanillaMethodReaderTest.java +++ b/src/test/java/net/openhft/chronicle/wire/method/VanillaMethodReaderTest.java @@ -224,7 +224,6 @@ public void methodInterceptorNull() { @Test public void testNestedUnknownClassWarn() { WiresTest.wiresThrowCNFRE(false); - Wires.GENERATE_TUPLES = false; Wire wire2 = new TextWire(Bytes.allocateElasticOnHeap()) .useTextDocuments(); @@ -246,7 +245,6 @@ public void testNestedUnknownClassWarn() { @Test public void testNestedUnknownClass() { WiresTest.wiresThrowCNFRE(true); - Wires.GENERATE_TUPLES = true; Wire wire2 = new TextWire(Bytes.allocateElasticOnHeap()) .useTextDocuments(); @@ -262,6 +260,7 @@ public void testNestedUnknownClass() { "...\n"; Wire wire = TextWire.from(text) .useTextDocuments(); + wire.generateTuples(true); MethodReader reader = wire.methodReader(writer2); checkReaderType(reader); assertTrue(reader.readOne()); @@ -272,7 +271,6 @@ public void testNestedUnknownClass() { @Test(expected = ClassNotFoundRuntimeException.class) public void testUnknownClassCNFREInterface() { WiresTest.wiresThrowCNFRE(false); - Wires.GENERATE_TUPLES = false; Wire wire2 = new TextWire(Bytes.allocateElasticOnHeap()) .useTextDocuments(); @@ -303,7 +301,6 @@ public void testUnknownClassCNFREInterface() { @Test public void testUnknownClassDoesntThrow() { WiresTest.wiresThrowCNFRE(true); - Wires.GENERATE_TUPLES = true; Wire wire2 = new TextWire(Bytes.allocateElasticOnHeap()) .useTextDocuments(); @@ -323,6 +320,7 @@ public void testUnknownClassDoesntThrow() { "...\n"; Wire wire = TextWire.from(text) .useTextDocuments(); + wire.generateTuples(true); MethodReader reader = wire.methodReader(writer2); checkReaderType(reader); assertTrue(reader.readOne()); @@ -334,7 +332,6 @@ public void testUnknownClassDoesntThrow() { @Test(expected = ClassNotFoundRuntimeException.class) public void testUnknownClassThrow() { WiresTest.wiresThrowCNFRE(true); - Wires.GENERATE_TUPLES = false; Wire wire2 = new TextWire(Bytes.allocateElasticOnHeap()) .useTextDocuments(); @@ -388,11 +385,6 @@ public void testMessageHistoryCleared() { } } - @After - public void resetGenerateTuples() { - Wires.GENERATE_TUPLES = false; - } - @Test(expected = IllegalStateException.class) public void testOverloaded() { Jvm.recordExceptions();