Skip to content

Commit

Permalink
Added support for params with multiple object types in method reader -
Browse files Browse the repository at this point in the history
…fixes #930 (#931)
  • Loading branch information
benbonavia authored Sep 11, 2024
1 parent 847891b commit 361324c
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 26 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@
<configuration>
<referenceVersion>2.26ea0</referenceVersion>
<artifactsURI>https://teamcity.chronicle.software/repository/download</artifactsURI>
<binaryCompatibilityPercentageRequired>98.4</binaryCompatibilityPercentageRequired>
<binaryCompatibilityPercentageRequired>98.3</binaryCompatibilityPercentageRequired>
<extraOptions>
<extraOption>
<name>skip-internal-packages</name>
Expand Down
80 changes: 56 additions & 24 deletions src/main/java/net/openhft/chronicle/wire/GenerateMethodReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
*/
public class GenerateMethodReader {

// Configuration flag for dumping the generated code.
// Configuration flag for dumping the generated code.
private static final boolean DUMP_CODE = Jvm.getBoolean("dumpCode");
// Set of interfaces that are not meant to be processed.
private static final Set<Class<?>> IGNORED_INTERFACES = new LinkedHashSet<>();
Expand Down Expand Up @@ -78,7 +78,10 @@ public class GenerateMethodReader {
// Configuration for the type of wire to use for serialization/deserialization.
private final WireType wireType;

// Handlers for metadata during the method reader generation.
// Check for supporting parameters which can either be non-Marshallable or Marshallable
private final Boolean multipleNonMarshallableParamTypes;

// Handlers for metadata during the method reader generation.
private final Object[] metaDataHandler;

// Instances of the classes/interfaces for which method readers are to be generated.
Expand Down Expand Up @@ -118,14 +121,15 @@ public class GenerateMethodReader {
* Constructs a new instance of GenerateMethodReader.
* Initializes the required configurations, metadata handlers, and instances which are essential for code generation.
*
* @param wireType Configuration for serialization/deserialization
* @param interceptor An instance of MethodReaderInterceptorReturns
* @param wireType Configuration for serialization/deserialization
* @param interceptor An instance of MethodReaderInterceptorReturns
* @param metaDataHandler Array of meta-data handlers
* @param instances Instances that dictate the structure of the generated MethodReader
* @param instances Instances that dictate the structure of the generated MethodReader
*/
public GenerateMethodReader(WireType wireType, MethodReaderInterceptorReturns interceptor, Object[] metaDataHandler, Object... instances) {
public GenerateMethodReader(WireType wireType, MethodReaderInterceptorReturns interceptor, Boolean multipleNonMarshallableParamTypes, Object[] metaDataHandler, Object... instances) {
this.wireType = wireType;
this.interceptor = interceptor;
this.multipleNonMarshallableParamTypes = multipleNonMarshallableParamTypes;
this.metaDataHandler = metaDataHandler;
this.instances = instances;
this.generatedClassName = generatedClassName0();
Expand All @@ -135,7 +139,7 @@ public GenerateMethodReader(WireType wireType, MethodReaderInterceptorReturns in
* Computes the signature of a given method.
* The signature comprises the return type, method name, and parameter types.
*
* @param m The method for which the signature is to be computed
* @param m The method for which the signature is to be computed
* @param type The type under consideration
* @return A string representing the method's signature
*/
Expand Down Expand Up @@ -239,6 +243,7 @@ private void generateSourceCode() {
sourceCode.append(format("package %s;\n", packageName()));

// Import statements required for the generated code.
boolean hasMultipleNonMarshallableParamTypes = !Boolean.FALSE.equals(multipleNonMarshallableParamTypes);
sourceCode.append("" +
"import net.openhft.chronicle.core.Jvm;\n" +
"import net.openhft.chronicle.core.util.InvocationTargetRuntimeException;\n" +
Expand All @@ -248,7 +253,9 @@ private void generateSourceCode() {
"import net.openhft.chronicle.wire.utils.*;\n" +
"import net.openhft.chronicle.wire.BinaryWireCode;\n" +
"\n" +
(hasMultipleNonMarshallableParamTypes ? "import java.util.HashMap;\n" : "") +
"import java.util.Map;\n" +
(hasMultipleNonMarshallableParamTypes ? "import java.util.function.Function;\n" : "") +
"import java.lang.reflect.Method;\n" +
"\n");

Expand Down Expand Up @@ -462,9 +469,9 @@ private void generateSourceCode() {
* @param anInterface The interface being processed.
* @param instanceFieldName In the generated code, methods are executed on a field with this name.
* @param methodFilter Indicates if the passed interface is marked with {@link MethodFilterOnFirstArg}. If true, only certain methods are processed.
* @ blocks based on method event IDs.
* @param eventNameSwitchBlock The block of code that handles the switching of event names.
* @param eventIdSwitchBlock The block of code that handles the switching of event IDs.
* @ blocks based on method event IDs.
*/
private void handleInterface(Class<?> anInterface, String instanceFieldName, boolean methodFilter, SourceCodeFormatter eventNameSwitchBlock, SourceCodeFormatter eventIdSwitchBlock) {
if (Jvm.dontChain(anInterface))
Expand Down Expand Up @@ -525,11 +532,11 @@ private void handleInterface(Class<?> anInterface, String instanceFieldName, boo
*
* <p>Finally, if the method's return type is chainable, it calls {@code handleInterface()} on it.
*
* @param m The method for which code is generated.
* @param anInterface The interface containing the method.
* @param instanceFieldName In the generated code, this method is executed on a field with this name.
* @param methodFilter Indicates if the passed interface is marked with {@link MethodFilterOnFirstArg}. If true, only certain methods are processed.
* @param eventIdSwitchBlock The block of code that handles the switching of event IDs.
* @param m The method for which code is generated.
* @param anInterface The interface containing the method.
* @param instanceFieldName In the generated code, this method is executed on a field with this name.
* @param methodFilter Indicates if the passed interface is marked with {@link MethodFilterOnFirstArg}. If true, only certain methods are processed.
* @param eventIdSwitchBlock The block of code that handles the switching of event IDs.
* @param eventNameSwitchBlock The block of code that handles the switching of event names.
*/
private void handleMethod(Method m, Class<?> anInterface, String instanceFieldName, boolean methodFilter, SourceCodeFormatter eventNameSwitchBlock, SourceCodeFormatter eventIdSwitchBlock) {
Expand All @@ -555,10 +562,15 @@ private void handleMethod(Method m, Class<?> anInterface, String instanceFieldNa
final String typeName = parameterType.getCanonicalName();
String fieldName = m.getName() + "arg" + i;
if (fieldNames.add(fieldName)) {
if (parameterType == Bytes.class)
if (parameterType == Bytes.class) {
fields.append(format("private Bytes %s = Bytes.allocateElasticOnHeap();\n", fieldName));
else
} else {
if (!parameterType.isPrimitive() && !Modifier.isFinal(parameterType.getModifiers()) && multipleNonMarshallableParamTypes(parameterType)) {
fields.append(format("private final Map<Class<? extends %s>, %s> %sInstances = new HashMap<>();\n", typeName, typeName, fieldName));
fields.append(format("private final Function<Class<? extends %s>, %s> %sFunc = %sInstances::get;\n", typeName, typeName, fieldName, fieldName));
}
fields.append(format("private %s %s;\n", typeName, fieldName));
}
}
}

Expand Down Expand Up @@ -756,13 +768,12 @@ private String methodCall(Method m, String instanceFieldName, String chainedCall
* influence the generated code. If {@link LongConversion}
* annotations are present on the argument, a converter field is registered.
*
* @param m Method for which an argument is read.
* @param argIndex Index of an argument to be read.
* @param inLambda {@code true} if argument is read in a lambda passed to a
* {@link ValueIn#sequence(Object, BiConsumer)} call.
* @param m Method for which an argument is read.
* @param argIndex Index of an argument to be read.
* @param inLambda {@code true} if argument is read in a lambda passed to a
* {@link ValueIn#sequence(Object, BiConsumer)} call.
* @param parameterTypes The types of the method parameters.
* @return Code in the form of a String that retrieves the specified argument from {@link ValueIn} input.
*
* @see LongConversion
* @see ValueIn
*/
Expand Down Expand Up @@ -870,13 +881,28 @@ else if (numericConversionClass != null && LongConverter.class.isAssignableFrom(
} else {
// Handling other object types.
final String typeName = argumentType.getCanonicalName();
if (!argumentType.isArray() && !AbstractMarshallableCfg.class.isAssignableFrom(argumentType) && !Collection.class.isAssignableFrom(argumentType) && !Map.class.isAssignableFrom(argumentType) && Object.class != argumentType && !argumentType.isInterface()) {
return format("%s = %s.object(%s, %s.class);\n", argumentName, valueInName, argumentName, typeName);
boolean multipleNonMarshallableParamTypes = multipleNonMarshallableParamTypes(argumentType);
if (!Modifier.isFinal(argumentType.getModifiers()) && multipleNonMarshallableParamTypes) {
return format("%s = %s.object(%s, %s.class); %sInstances.put(%s.getClass(), %s);\n", argumentName, valueInName, argumentName + "Func", typeName, argumentName, argumentName, argumentName);
}
if (isRecyclable(argumentType)) {
return format("%s = %s.object(checkRecycle(%s), %s.class);\n", argumentName, valueInName, argumentName, typeName);
}
return format("%s = %s.object(checkRecycle(%s), %s.class);\n", argumentName, valueInName, argumentName, typeName);
return format("%s = %s.object(%s, %s.class);\n", argumentName, valueInName, argumentName, typeName);
}
}

private static boolean isRecyclable(Class<?> argumentType) {
return argumentType.isArray() || AbstractMarshallableCfg.class.isAssignableFrom(argumentType) || Collection.class.isAssignableFrom(argumentType) || Map.class.isAssignableFrom(argumentType);
}

private boolean multipleNonMarshallableParamTypes(Class<?> argumentType) {
Boolean _multipleNonMarshallableParamTypes = this.multipleNonMarshallableParamTypes;
return _multipleNonMarshallableParamTypes == null
? argumentType.isInterface() && !isRecyclable(argumentType) || argumentType == Object.class
: _multipleNonMarshallableParamTypes;
}

/**
* Checks if a real interceptor is present that returns.
*
Expand Down Expand Up @@ -910,7 +936,8 @@ public String generatedClassName() {

/**
* Constructs the generated class name using various components such as
* the names of instances, metadata handlers, wire type, and potential interceptor.
* the names of instances, metadata handlers, wire type, support for interchangeable marshallable/non-marshallable
* ([T]rue, [F]alse or [A]uto) and potential interceptor.
* Special characters, such as underscores and slashes, are handled to format the class name.
*
* @return The constructed name for the generated class.
Expand All @@ -933,6 +960,11 @@ private String generatedClassName0() {
sb.append(wireType.toString()
.replace("_", ""));

// Append multi marshal/non-marshal support
if (multipleNonMarshallableParamTypes != null) {
sb.append(Boolean.TRUE.equals(multipleNonMarshallableParamTypes) ? 'T' : 'F');
}

// Append interceptor details to the class name.
if (interceptor instanceof GeneratingMethodReaderInterceptorReturns)
sb.append(((GeneratingMethodReaderInterceptorReturns) interceptor).generatorId());
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/net/openhft/chronicle/wire/ValueIn.java
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,27 @@ default <E> E object(@Nullable E using, @Nullable Class<? extends E> clazz) thro
return ValidatableUtil.validate(t);
}

/**
* Reads an object from the wire.
*
* @param <E> The type of the object to read.
* @param usingFunction A function to apply retrieve the instance of an object to reuse, or null to create a new instance.
* @param clazz The class of the object to read.
* @return The object read from the wire, or null if it cannot be read.
* @throws InvalidMarshallableException if the object is invalid
*/
@Nullable
default <E> E object(@Nullable Function<Class<? extends E>, E> usingFunction, @Nullable Class<? extends E> clazz) throws InvalidMarshallableException {
E t;
Object o = typePrefixOrObject(clazz);
if (o != null && !(o instanceof Class)) {
t = (@Nullable E) marshallable(o, MARSHALLABLE);
} else {
t = Wires.object2(this, o != null ? usingFunction.apply((Class<? extends E>) o) : null , clazz, true, (Class) o);
}
return ValidatableUtil.validate(t);
}

/**
* Reads an object from the wire.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ public class VanillaMethodReaderBuilder implements MethodReaderBuilder {
// A flag to indicate whether the reader is in a scanning mode.
private boolean scanning = false;

// A flag to determine support for parameters which can either be non-Marshallable or Marshallable
// null for auto-detect
private Boolean multipleNonMarshallableParamTypes = null;

/**
* Constructs a new {@code VanillaMethodReaderBuilder} with the specified wire input.
*
Expand Down Expand Up @@ -189,6 +193,17 @@ public VanillaMethodReaderBuilder scanning(boolean scanning) {
return this;
}

/**
* Configures the reader to handle parameters which can have multiple non-marshallable parameter types.
*
* @param multipleNonMarshallableParamTypes Whether the reader should handle multiple non-marshallable parameter types.
* @return This builder instance for chaining.
*/
public VanillaMethodReaderBuilder multipleNonMarshallableParamTypes(Boolean multipleNonMarshallableParamTypes) {
this.multipleNonMarshallableParamTypes = multipleNonMarshallableParamTypes;
return this;
}

/**
* Creates an instance of a generated method reader.
* The method first checks if the desired generated reader class is already loaded.
Expand All @@ -202,7 +217,7 @@ private MethodReader createGeneratedInstance(Object... impls) {
if (ignoreDefaults || Jvm.getBoolean(DISABLE_READER_PROXY_CODEGEN))
return null;

GenerateMethodReader generateMethodReader = new GenerateMethodReader(wireType, methodReaderInterceptorReturns, metaDataHandler, impls);
GenerateMethodReader generateMethodReader = new GenerateMethodReader(wireType, methodReaderInterceptorReturns, multipleNonMarshallableParamTypes, metaDataHandler, impls);

String fullClassName = generateMethodReader.packageName() + "." + generateMethodReader.generatedClassName();

Expand Down
Loading

0 comments on commit 361324c

Please sign in to comment.