Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support generateTuple being set for an individual WireIn, rather than global all or nothing. #823

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/main/java/net/openhft/chronicle/wire/AbstractWire.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 " +
Expand All @@ -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) {
Expand All @@ -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.
* <p>
* 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.
* <p>
* 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());
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/net/openhft/chronicle/wire/BinaryWire.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/net/openhft/chronicle/wire/TextWire.java
Original file line number Diff line number Diff line change
Expand Up @@ -1881,15 +1881,15 @@ 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;
}

@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.";
Expand All @@ -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());
}

Expand Down
29 changes: 29 additions & 0 deletions src/main/java/net/openhft/chronicle/wire/WireIn.java
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,35 @@ default boolean hasMetaDataPrefix() {
return false;
}

/**
* Sets the state of tuple generation for the implementing class.
* <p>
* 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.
* <p>
* 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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
29 changes: 24 additions & 5 deletions src/main/java/net/openhft/chronicle/wire/Wires.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* 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.
* <p>
* 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 <T> 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> T tupleFor(Class<T> 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<T>) 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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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.")));
}
}

Expand Down
9 changes: 1 addition & 8 deletions src/test/java/net/openhft/chronicle/wire/WireTestCommon.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ public class WireTestCommon {
private final Map<Predicate<ExceptionKey>, String> ignoredExceptions = new LinkedHashMap<>();
private final Map<Predicate<ExceptionKey>, String> expectedExceptions = new LinkedHashMap<>();

private boolean gt;

public WireTestCommon() {
ignoreException("The incubating features are subject to change");
ignoreException("NamedThreadFactory created here");
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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());
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -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());
Expand All @@ -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();
Expand Down Expand Up @@ -388,11 +385,6 @@ public void testMessageHistoryCleared() {
}
}

@After
public void resetGenerateTuples() {
Wires.GENERATE_TUPLES = false;
}

@Test(expected = IllegalStateException.class)
public void testOverloaded() {
Jvm.recordExceptions();
Expand Down