All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.openhft.chronicle.wire.GenerateMethodWriter Maven / Gradle / Ivy

There is a newer version: 2.27ea1
Show newest version
/*
 * Copyright 2016-2022 chronicle.software
 *
 *       https://chronicle.software
 *
 * 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.
 */

package net.openhft.chronicle.wire;

import net.openhft.chronicle.bytes.MethodId;
import net.openhft.chronicle.bytes.MethodReader;
import net.openhft.chronicle.bytes.UpdateInterceptor;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.io.Closeable;
import net.openhft.chronicle.core.io.Syncable;
import net.openhft.chronicle.core.util.GenericReflection;
import net.openhft.chronicle.core.util.ObjectUtils;
import net.openhft.chronicle.wire.utils.JavaSourceCodeFormatter;
import net.openhft.chronicle.wire.utils.SourceCodeFormatter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.lang.String.format;
import static java.util.Arrays.stream;
import static java.util.Collections.*;
import static net.openhft.chronicle.core.util.GenericReflection.erase;
import static net.openhft.chronicle.core.util.GenericReflection.getParameterTypes;
/**
 * This is the GenerateMethodWriter class responsible for generating method writer code.
 * It provides utility methods and configurations to facilitate the dynamic generation of method writers.
 */
@SuppressWarnings("deprecation")
public class GenerateMethodWriter {

    // Constants for class names to be used in the generated method writer code
    public static final String UPDATE_INTERCEPTOR = UpdateInterceptor.class.getSimpleName();

    // Constant for the DocumentContext class's simple name
    private static final String DOCUMENT_CONTEXT = DocumentContext.class.getSimpleName();

    // Constant for the WriteDocumentContext class's simple name
    private static final String WRITE_DOCUMENT_CONTEXT = WriteDocumentContext.class.getSimpleName();

    // Constant for the MarshallableOut class's simple name
    private static final String MARSHALLABLE_OUT = MarshallableOut.class.getSimpleName();

    // Constant for the MethodId class's simple name
    private static final String METHOD_ID = MethodId.class.getSimpleName();

    // Constant for the ValueOut class's simple name
    private static final String VALUE_OUT = ValueOut.class.getSimpleName();

    // Constant for the Closeable class's simple name
    private static final String CLOSEABLE = Closeable.class.getSimpleName();

    // Constant field name for the UpdateInterceptor class
    private static final String UPDATE_INTERCEPTOR_FIELD = "updateInterceptor";

    // Indicates whether to dump the generated code or not
    static final boolean DUMP_CODE = Jvm.getBoolean("dumpCode");

    // Map to hold template methods for generating method writer code
    private static final Map>, String>> TEMPLATE_METHODS = new LinkedHashMap<>();
    private static final int SYNTHETIC = 0x00001000;

    static {
        // Ensure Wires static block is called and classpath is set up
        Wires.init();

        // Define template methods for the generation process
        TEMPLATE_METHODS.put("close",
                singletonMap(singletonList(void.class), "public void close() {\n" +
                        "   if (this.closeable != null) {\n" +
                        "        this.closeable.close();\n" +
                        "   }\n" +
                        "}\n"));
        TEMPLATE_METHODS.put("recordHistory",
                singletonMap(singletonList(boolean.class), "public boolean recordHistory() {\n" +
                        "    return out.get().recordHistory();\n" +
                        "}\n"));
        List> dcBoolean = Stream.of(DocumentContext.class, boolean.class).collect(Collectors.toList());
        TEMPLATE_METHODS.put("acquireWritingDocument",
                singletonMap(dcBoolean, "public " + DOCUMENT_CONTEXT + " acquireWritingDocument(boolean metaData){\n" +
                        "    return out.get().acquireWritingDocument(metaData);\n" +
                        "}\n"));
        Map>, String> wd = new LinkedHashMap<>();
        wd.put(singletonList(DocumentContext.class), "public " + DOCUMENT_CONTEXT + " writingDocument(){\n" +
                "    return out.get().writingDocument();\n" +
                "}\n");
        wd.put(dcBoolean, "public " + DOCUMENT_CONTEXT + " writingDocument(boolean metaData){\n" +
                "return out.get().writingDocument(metaData);\n" +
                "}\n");
        TEMPLATE_METHODS.put("writingDocument", wd);
    }

    // Indicates if metadata is included in the generated method writer
    private final boolean metaData;

    // Indicates if a method ID is used in the generated method writer
    private final boolean useMethodId;

    // Package name for the generated method writer
    private final String packageName;

    // Set of interfaces to be implemented by the generated method writer
    private final Set> interfaces;

    // Name of the generated class
    private final String className;

    // Class loader used for the generated method writer
    private final ClassLoader classLoader;

    // Wire type for serialization in the generated method writer
    private final WireType wireType;

    // Generic event type used in the generated method writer
    private final String genericEvent;

    // Indicates if the update interceptor is used in the generated method writer
    private final boolean useUpdateInterceptor;

    // Concurrent map to cache method writers
    private final ConcurrentMap, String> methodWritersMap = new ConcurrentHashMap<>();
    // AtomicInteger to manage indentation in the generated code
    final private AtomicInteger indent = new AtomicInteger();
    // Indicates if verbose types are used in the generated method writer
    private final boolean verboseTypes;

    /**
     * Constructor for the GenerateMethodWriter class.
     * Initializes all the required fields for the code generation process.
     *
     * @param packageName         The package name for the generated method writer.
     * @param interfaces          The interfaces to be implemented by the generated method writer.
     * @param className           The name of the generated class.
     * @param classLoader         The class loader to use.
     * @param wireType            The wire type for serialization.
     * @param genericEvent        The generic event type.
     * @param metaData            Indicates if metadata should be included.
     * @param useMethodId         Indicates if method ID should be used.
     * @param useUpdateInterceptor Indicates if the update interceptor should be used.
     * @param verboseTypes        Indicates if verbose types should be used.
     */
    private GenerateMethodWriter(final String packageName,
                                 final Set> interfaces,
                                 final String className,
                                 final ClassLoader classLoader,
                                 final WireType wireType,
                                 final String genericEvent,
                                 final boolean metaData,
                                 final boolean useMethodId,
                                 final boolean useUpdateInterceptor,
                                 final boolean verboseTypes) {

        this.packageName = packageName;
        this.interfaces = interfaces;
        this.className = className;
        this.classLoader = classLoader;
        this.wireType = wireType;
        this.genericEvent = genericEvent;
        this.metaData = metaData;
        this.useMethodId = useMethodId;
        this.useUpdateInterceptor = useUpdateInterceptor;
        this.verboseTypes = verboseTypes;
    }

    /**
     * Generates a proxy class based on the provided interface class.
     * Note: This method is deprecated and will be removed in version x.26.
     *
     * @param fullClassName         Fully qualified class name for the generated proxy class.
     * @param interfaces            A set of interface classes that the generated proxy class will implement.
     * @param classLoader           The class loader to use for generating the proxy class.
     * @param wireType              The wire type for serialization.
     * @param genericEvent          The generic event type.
     * @param metaData              Indicates if metadata should be included.
     * @param useMethodId           Indicates if method ID should be used.
     * @param useUpdateInterceptor  Indicates if the update interceptor should be used.
     * @return                      A generated proxy class based on the provided interface class,
     *                              or null if it can't be created.
     */
    @Nullable
    @Deprecated /* to be removed is version x.26. */
    public static Class newClass(String fullClassName,
                                    Set> interfaces,
                                    ClassLoader classLoader,
                                    final WireType wireType,
                                    final String genericEvent,
                                    boolean metaData,
                                    boolean useMethodId,
                                    final boolean useUpdateInterceptor) {
        String packageName = ReflectionUtil.generatedPackageName(fullClassName);

        int lastDot = fullClassName.lastIndexOf('.');
        String className = lastDot == -1 ? fullClassName : fullClassName.substring(lastDot + 1);

        return new GenerateMethodWriter(packageName,
                interfaces,
                className,
                classLoader,
                wireType,
                genericEvent,
                metaData, useMethodId, useUpdateInterceptor, false)
                .createClass();
    }

    /**
     * Generates a proxy class based on the provided interface class.
     *
     * @param fullClassName         Fully qualified class name for the generated proxy class.
     * @param interfaces            A set of interface classes that the generated proxy class will implement.
     * @param classLoader           The class loader to use for generating the proxy class.
     * @param wireType              The wire type for serialization.
     * @param genericEvent          The generic event type.
     * @param metaData              Indicates if metadata should be included.
     * @param useMethodId           Indicates if method ID should be used.
     * @param useUpdateInterceptor  Indicates if the update interceptor should be used.
     * @param verboseTypes          Indicates if verbose types should be used.
     * @return                      A generated proxy class based on the provided interface class,
     *                              or null if it can't be created.
     */
    @Nullable
    public static Class newClass(String fullClassName,
                                    Set> interfaces,
                                    ClassLoader classLoader,
                                    final WireType wireType,
                                    final String genericEvent,
                                    boolean metaData,
                                    boolean useMethodId,
                                    final boolean useUpdateInterceptor,
                                    boolean verboseTypes) {
        String packageName = ReflectionUtil.generatedPackageName(fullClassName);

        int lastDot = fullClassName.lastIndexOf('.');
        String className = lastDot == -1 ? fullClassName : fullClassName.substring(lastDot + 1);

        return new GenerateMethodWriter(packageName,
                interfaces,
                className,
                classLoader,
                wireType,
                genericEvent,
                metaData, useMethodId, useUpdateInterceptor, verboseTypes)
                .createClass();
    }

    /**
     * Acquires a {@link DocumentContext} instance, either by reusing the existing one from the thread-local
     * context holder if it's not closed or by creating a new one using the provided output.
     *
     * @param metaData            Indicates if metadata should be included in the {@link DocumentContext}.
     * @param documentContextTL   The thread-local holder of the {@link DocumentContext}.
     * @param out                 The output where the document will be written.
     * @return                    An instance of {@link DocumentContext}.
     */
    @SuppressWarnings("unused")
    public static DocumentContext acquireDocumentContext(boolean metaData,
                                                         ThreadLocal documentContextTL,
                                                         MarshallableOut out) {
        DocumentContextHolder contextHolder = documentContextTL.get();
        if (!contextHolder.isClosed())
            return contextHolder;
        contextHolder.documentContext(
                out.writingDocument(metaData));
        return contextHolder;
    }

    /**
     * Converts a class type into a representative string, e.g., int to "int32", boolean to "bool", etc.
     * For types not explicitly mapped, a default representation is returned.
     *
     * @param type   The class type to be converted.
     * @return       A representative string for the given class type.
     */
    private static CharSequence toString(Class type) {
        if (boolean.class.equals(type)) {
            return "bool";
        } else if (byte.class.equals(type)) {
            return "writeByte";
        } else if (char.class.equals(type)) {
            return "character";
        } else if (short.class.equals(type)) {
            return "int16";
        } else if (int.class.equals(type)) {
            return "int32";
        } else if (long.class.equals(type)) {
            return "int64";
        } else if (float.class.equals(type)) {
            return "float32";
        } else if (double.class.equals(type)) {
            return "float64";
        } else if (CharSequence.class.isAssignableFrom(type)) {
            return "text";
        } else if (Marshallable.class.isAssignableFrom(type)) {
            return "marshallable";
        }
        return "object";
    }

    /**
     * Retrieves the full canonical name of a class, with any '$' characters replaced by '.'.
     *
     * @param type   The class for which the name is required.
     * @return       The full name of the class with '$' characters replaced.
     */
    @NotNull
    private static String nameForClass(Class type) {
        return type.getName().replace('$', '.');
    }

    /**
     * Retrieves the name for a class, considering any imports and package details.
     * If the type is part of the provided import set, or if it belongs to the "java.lang" package,
     * only the simple name of the class is returned. Otherwise, the full canonical name is returned.
     *
     * @param importSet   A set of class names or package patterns that have been imported.
     * @param type        The class for which the name is required.
     * @return            The appropriate name of the class based on the import context.
     */
    @NotNull
    private static String nameForClass(Set importSet, Class type) {
        if (type.isArray())
            return nameForClass(importSet, type.getComponentType()) + "[]";
        String s = nameForClass(type);
        String packageName = Jvm.getPackageName(type);
        if (importSet.contains(s)
                || "java.lang".equals(packageName)
                || (!type.getName().contains("$")
                && importSet.contains(packageName + ".*")))
            return type.getSimpleName();
        return s;
    }

    /**
     * Constructs a method signature string for a given method and type.
     * The signature string includes the return type, method name, and parameter types.
     *
     * @param m     The method for which the signature is to be generated.
     * @param type  The type that might contain the method.
     * @return      A string representing the method signature.
     */
    private static String signature(Method m, Type type) {
        final Type returnType = GenericReflection.getReturnType(m, type);
        final Type[] parameterTypes = getParameterTypes(m, type);
        return returnType + " " + m.getName() + " " + Arrays.toString(parameterTypes);
    }

    /**
     * Checks if a given modifier represents a synthetic entity.
     *
     * @param modifiers  The modifiers to check.
     * @return           True if the modifier is synthetic, false otherwise.
     */
    static boolean isSynthetic(int modifiers) {
        return (modifiers & SYNTHETIC) != 0;  // Check the SYNTHETIC flag in the modifiers
    }

    /**
     * Constructs the method signature for a method, considering its annotations,
     * parameters, and the provided import set.
     *
     * @param importSet       The set of imported classes or packages.
     * @param dm              The method for which the signature is required.
     * @param parameterTypes  The parameter types for the method.
     * @return                An {@link Appendable} containing the method signature.
     */
    @NotNull
    private Appendable methodSignature(SortedSet importSet, final Method dm, Type[] parameterTypes) {

        SourceCodeFormatter result = new JavaSourceCodeFormatter(this.indent);
        String sep = "";
        Parameter[] parameters = dm.getParameters();

        // Iterate over each parameter of the method
        for (int i = 0; i < parameters.length; i++) {
            Parameter p = parameters[i];
            result.append(sep);
            sep = ", ";
            LongConversion longConversion = Jvm.findAnnotation(p, LongConversion.class);

            if (longConversion != null)
                result.append("@LongConversion(")
                        .append(nameForClass(importSet, longConversion.value()))
                        .append(".class) ");

            // Append parameter type and name to the result
            result.append("final ")
                    .append(nameForClass(importSet, erase(parameterTypes[i])))
                    .append(' ')
                    .append(p.getName());
        }
        return result;
    }

    /**
     * Generates and loads a Java class based on provided interfaces. This method dynamically constructs
     * a new Java class that implements the specified interfaces and the {@link MethodWriter} interface.
     * The generated class will reside in the specified package and will import necessary dependencies
     * from various packages, especially from the `net.openhft.chronicle.bytes` and `net.openhft.chronicle.wire` packages.
     *
     * 

The method primarily works in the following steps: *

    *
  1. Generates the class header, including the package declaration and required imports.
  2. *
  3. Processes each method from the provided interfaces and generates an appropriate method body.
  4. *
  5. Attempts to load the newly created class using the generated source code.
  6. *
* *

Debugging: If the static field `DUMP_CODE` is set to {@code true}, the generated Java code * will be printed to the standard output. This can be useful for debugging purposes. * * @return The {@link Class} object representing the dynamically generated class. * @throws MethodWriterValidationException If there's an issue validating methods during the code generation. */ @SuppressWarnings("StringConcatenationInsideStringBufferAppend") private Class createClass() { // For storing method implementations SourceCodeFormatter interfaceMethods = new SourceCodeFormatter(1); // For storing generated Java code SourceCodeFormatter imports = new JavaSourceCodeFormatter(); try { // Define package for the generated class imports.append("package " + packageName + ";\n\n"); // Collection of classes/packages that need to be imported in generated class SortedSet importSet = new TreeSet<>(); importSet.add(LongConversion.class.getName()); importSet.add(GenerateMethodWriter.class.getName()); importSet.add(MessageHistory.class.getName()); importSet.add(MethodReader.class.getName()); importSet.add(UpdateInterceptor.class.getName()); importSet.add(MethodId.class.getName()); importSet.add(GenerateMethodWriter.class.getName()); importSet.add(DocumentContext.class.getName()); importSet.add(WriteDocumentContext.class.getName()); importSet.add(MethodWriterInvocationHandlerSupplier.class.getName()); importSet.add(Jvm.class.getName()); importSet.add(Closeable.class.getName()); importSet.add(DocumentContextHolder.class.getName()); importSet.add(java.lang.reflect.InvocationHandler.class.getName()); importSet.add(java.lang.reflect.Method.class.getName()); importSet.add(java.util.stream.IntStream.class.getName()); importSet.add(java.util.ArrayList.class.getName()); importSet.add(java.util.List.class.getName()); importSet.add(Supplier.class.getName()); // Iterate through all interfaces to extract required imports and validations for (Class interfaceClazz : interfaces) { importSet.add(nameForClass(interfaceClazz)); if (!interfaceClazz.isInterface()) throw new MethodWriterValidationException("expecting an interface instead of class=" + interfaceClazz.getName()); // TODO: Need a test to show when an extra import is required. // Methods extraction and generation for (Method dm : interfaceClazz.getMethods()) { if (dm.isDefault() || Modifier.isStatic(dm.getModifiers())) continue; String template = templateFor(dm, interfaceClazz); if (template != null) continue; for (Type type : getParameterTypes(dm, interfaceClazz)) { Class pType = erase(type); if (pType.isPrimitive() || pType.isArray() || Jvm.getPackageName(pType).equals("java.lang")) continue; importSet.add(nameForClass(pType)); } } } // Adding standard chronicle imports for byte and wire packages // and cleaning up specific imports in favour of wildcard imports importSet.removeIf(s -> s.startsWith("net.openhft.chronicle.bytes")); importSet.add("net.openhft.chronicle.bytes.*"); importSet.removeIf(s -> s.startsWith("net.openhft.chronicle.wire")); importSet.add("net.openhft.chronicle.wire.*"); for (String s : importSet) { imports.append("import ").append(s).append(";\n"); } // Declare the new class implementing all interfaces and MethodWriter imports.append("\npublic final class ") .append(className) .append(" implements "); Set handledMethodSignatures = new HashSet<>(); Set methodIds = new HashSet<>(); for (Class interfaceClazz : interfaces) { String interfaceName = nameForClass(importSet, interfaceClazz); imports.append(interfaceName); imports.append(", "); if (!interfaceClazz.isInterface()) throw new MethodWriterValidationException("expecting an interface instead of class=" + interfaceClazz.getName()); for (Method dm : interfaceClazz.getMethods()) { final int modifiers = dm.getModifiers(); if (Modifier.isStatic(modifiers) || isSynthetic(modifiers)) continue; final Class returnType = returnType(dm, interfaceClazz); if (dm.isDefault() && (!returnType.equals(void.class) && !returnType.isInterface())) continue; final String signature = signature(dm, interfaceClazz); if (!handledMethodSignatures.add(signature)) continue; String template = templateFor(dm, interfaceClazz); if (template == null) { interfaceMethods.append(createMethod(importSet, dm, interfaceClazz, methodIds)); } else { interfaceMethods.append(template); } } } imports.append(MethodWriter.class.getSimpleName()); imports.append(" {\n\n"); constructorAndFields(importSet, className, imports); addMarshallableOut(imports); imports.append(interfaceMethods); imports.append("\n}\n"); // Printing the generated code (only for debugging purposes) if (DUMP_CODE) System.out.println(imports); // Attempt to load the class using the generated code return Wires.loadFromJava(classLoader, packageName + '.' + className, imports.toString()); } catch (AssertionError e) { // In case there is a linkage error, try loading the class directly if (e.getCause() instanceof LinkageError) { try { return Class.forName(packageName + '.' + className, true, classLoader); } catch (ClassNotFoundException x) { throw Jvm.rethrow(x); } } throw Jvm.rethrow(e); } catch (MethodWriterValidationException e) { throw e; // This seems to be a custom exception, rethrow it as is } catch (Throwable e) { // In case of other exceptions, wrap the cause and rethrow throw Jvm.rethrow(new ClassNotFoundException(e.getMessage() + '\n' + imports, e)); } } /** * Determines the return type of the provided method relative to a given interface class. * It makes use of {@code GenericReflection} to get the return type and, if the return type isn't a class instance, * it attempts to resolve it as a generic declaration. * * @param dm The method whose return type needs to be determined. * @param interfaceClazz The interface class relative to which the method's return type is evaluated. * @return The class type of the method's return type. */ private Class returnType(Method dm, Class interfaceClazz) { Type returnType = GenericReflection.getReturnType(dm, interfaceClazz); if (!(returnType instanceof Class)) returnType = (Type) ((TypeVariable) returnType).getGenericDeclaration(); return erase(returnType); } /** * Looks up a template for a given method and interface type. * The method searches for predefined templates using the method's name. * * @param dm The method for which a template is required. * @param interfaceType The interface type in which the method is defined. * @return The template string if found, otherwise {@code null}. */ private String templateFor(Method dm, Class interfaceType) { Map>, String> map = TEMPLATE_METHODS.get(dm.getName()); if (map == null) return null; List> sig = new ArrayList<>(); sig.add(returnType(dm, interfaceType)); for (Type type : getParameterTypes(dm, interfaceType)) { addAll(sig, erase(type)); } return map.get(sig); } /** * Generates code for the `marshallableOut` method. * The method updates the internal state of the generated class. * * @param codeFormatter The formatter that helps structure the generated code. */ private void addMarshallableOut(SourceCodeFormatter codeFormatter) { codeFormatter.append("@Override\n"); codeFormatter.append("public void marshallableOut(MarshallableOut out) {\n"); codeFormatter.append("this.out = () -> out;"); for (Map.Entry, String> e : methodWritersMap.entrySet()) { codeFormatter.append(format("\n this.%s.remove();", e.getValue())); } codeFormatter.append("\n}\n"); } /** * Constructs the source code for class fields and the class constructor. * The method defines the state of the generated class and provides a constructor to initialize this state. * * @param importSet The set of classes that need to be imported. * @param className The name of the generated class. * @param result The formatter that helps structure the generated source code. * @return The structured source code containing the fields and constructor for the generated class. */ private CharSequence constructorAndFields(Set importSet, final String className, SourceCodeFormatter result) { result.append("// result\n" + "private transient final Closeable closeable;\n"); if (useUpdateInterceptor) result.append("private transient final " + UPDATE_INTERCEPTOR + " " + UPDATE_INTERCEPTOR_FIELD + ";\n"); result.append("private transient Supplier<") .append(MARSHALLABLE_OUT) .append("> out;\n"); for (Map.Entry, String> e : methodWritersMap.entrySet()) { result.append(format("private transient ThreadLocal<%s> %s;\n", nameForClass(importSet, e.getKey()), e.getValue())); } result.append('\n'); result.append(format("// constructor\npublic %s(Supplier<" + MARSHALLABLE_OUT + "> out, " + CLOSEABLE + " closeable, " + UpdateInterceptor.class.getSimpleName() + " " + UPDATE_INTERCEPTOR_FIELD + ") {\n", className)); if (useUpdateInterceptor) result.append("this." + UPDATE_INTERCEPTOR_FIELD + "= " + UPDATE_INTERCEPTOR_FIELD + ";\n"); result.append("this.out = out;\n" + "this.closeable = closeable;"); for (Map.Entry, String> e : methodWritersMap.entrySet()) { result.append(format("\n%s = ThreadLocal.withInitial(() -> out.get().methodWriter(%s.class));", e.getValue(), nameForClass(e.getKey()))); result.append(format("\n%s = ThreadLocal.withInitial(() -> out.get().methodWriterBuilder(%s.class)" + ".verboseTypes(%b).build());", e.getValue(), nameForClass(e.getKey()), verboseTypes)); } result.append("\n}\n\n"); return result; } /** * Creates the method writer code for a given method. * * @param importSet The set of imports required for the method * @param dm The method for which the writer code is to be generated * @param interfaceClazz The class of the interface being processed * @param methodIds The set of method IDs to track the generated methods * @return A CharSequence containing the generated method writer code */ private CharSequence createMethod(SortedSet importSet, final Method dm, final Class interfaceClazz, Set methodIds) { // Skip static methods if (Modifier.isStatic(dm.getModifiers())) return ""; // For methods with no parameters and default implementations, return an empty string int parameterCount = dm.getParameterCount(); if (parameterCount == 0 && dm.isDefault()) return ""; Parameter[] parameters = dm.getParameters(); final int len = parameters.length; final Class returnType = returnType(dm, interfaceClazz); // Get the type name for return type final String typeName = nameForClass(importSet, returnType); final StringBuilder body = new StringBuilder(); String methodIDAnotation = ""; final Type[] parameterTypes = getParameterTypes(dm, interfaceClazz); // UpdateInterceptor logic if (useUpdateInterceptor) { if (parameterCount > 1) Jvm.debug().on(getClass(), "Generated code to call updateInterceptor for " + dm + " only using last argument"); final String name; if (parameterCount > 0) { Type type = parameterTypes[parameterCount - 1]; if (type instanceof Class && ((Class) type).isPrimitive()) Jvm.warn().on(getClass(), "Generated code to call updateInterceptor for " + dm + " will box and generate garbage"); name = parameters[parameterCount - 1].getName(); } else name = "null"; body.append("// updateInterceptor\n" + "if (! this." + UPDATE_INTERCEPTOR_FIELD + ".update(\"" + dm.getName() + "\", " + name + ")) return" + returnDefault(returnType) + ";\n"); } body.append("MarshallableOut out = this.out.get();\n"); boolean terminating = returnType == Void.class || returnType == void.class || returnType.isPrimitive(); boolean passthrough = returnType == DocumentContext.class; // MarshallableOut setup logic if (!passthrough) body.append("try ("); body.append("final ") .append(WRITE_DOCUMENT_CONTEXT) .append(" _dc_ = (") .append(WRITE_DOCUMENT_CONTEXT).append(") out.acquireWritingDocument(") .append(metaData) .append(")"); if (passthrough) body.append(";\n"); else body.append(") {\n"); body.append("try {\n"); body.append("_dc_.chainedElement(" + (!terminating && !passthrough) + ");\n"); body.append("if (out.recordHistory()) MessageHistory.writeHistory(_dc_);\n"); int startJ = 0; final String eventName; // Logic for generic events if (parameterCount > 0 && dm.getName().equals(genericEvent)) { eventName = parameters[0].getName(); startJ = 1; } else { eventName = '\"' + dm.getName() + '\"'; } // Determine the appropriate event name or ID for the method methodIDAnotation = writeEventNameOrId(dm, body, eventName); // Check for duplicate method IDs and throw an exception if a duplicate is found if (methodIDAnotation.length() > 0 && !methodIds.add(methodIDAnotation)) throw new MethodWriterValidationException("Duplicate methodIds. Cannot add " + methodIDAnotation + " to " + methodIds); // Write out the parameters for the method, if they exist if (parameters.length > 0) writeArrayOfParameters(dm, parameterTypes, len, body, startJ); // Handle the scenario where there are no parameters if (parameterCount == 0) body.append("_valueOut_.text(\"\");\n"); // Handle exceptions during method execution body.append("} catch (Throwable _t_) {\n"); body.append("_dc_.rollbackOnClose();\n"); body.append("throw Jvm.rethrow(_t_);\n"); body.append("}\n"); // Synchronize the method if it belongs to the Syncable class if (dm.getDeclaringClass() == Syncable.class) { body.append(Syncable.class.getName()).append(".syncIfAvailable(out);\n"); } // Close the method body if it's not a passthrough method if (!passthrough) body.append("}\n"); // Return the formatted method writer code return format("\n%s public %s %s(%s) {\n %s%s}\n", methodIDAnotation, typeName, dm.getName(), methodSignature(importSet, dm, parameterTypes), body, methodReturn(dm, interfaceClazz)); } /** * Determines the default return value for a given return type. * * @param returnType The return type of a method * @return A string representing the default value for the specified return type */ private String returnDefault(final Class returnType) { // Void return type scenario if (returnType == void.class) return ""; // If the return type is primitive or Void class if (returnType.isPrimitive() || returnType == Void.class) return " " + ObjectUtils.defaultValue(returnType); // Default scenario return " this"; } /** * Writes the event name or ID for a given method. Determines whether to use the event name * or ID based on the wire type and the presence of a MethodId annotation. * * @param dm The method in question. * @param body The StringBuilder used to build the body of the method writer. * @param eventName The name of the event. * @return The method ID as a string, or an empty string if there's none. */ private String writeEventNameOrId(final Method dm, final StringBuilder body, final String eventName) { String methodID = ""; final Optional methodId = useMethodId ? stream(dm.getAnnotations()).filter(MethodId.class::isInstance).findFirst() : Optional.empty(); if ((wireType != WireType.TEXT && wireType != WireType.YAML) && methodId.isPresent()) { long value = ((MethodId) methodId.get()).value(); body.append(format("final " + VALUE_OUT + " _valueOut_ = _dc_.wire().writeEventId(%s, %d);\n", eventName, value)); methodID = format("@" + METHOD_ID + "(%d)\n", value); } else body.append(format("final " + VALUE_OUT + " _valueOut_ = _dc_.wire().writeEventName(%s);\n", eventName)); return methodID; } /** * Writes out the parameters for a given method in the desired format. Handles * different parameter types and annotations to generate the appropriate method writer code. * * @param dm The method whose parameters need to be written out. * @param parameterTypes An array of the types of the method's parameters. * @param len The length of the parameters array. * @param body The StringBuilder used to build the body of the method writer. * @param startJ Starting index to loop through the parameters. */ private void writeArrayOfParameters(final Method dm, Type[] parameterTypes, final int len, final StringBuilder body, final int startJ) { final int parameterCount = dm.getParameterCount(); final boolean multipleArgs = parameterCount > startJ + 1; if (multipleArgs) body.append("_valueOut_.array(_v_ -> {\n"); for (int j = startJ; j < len; j++) { final Parameter p = dm.getParameters()[j]; final LongConversion longConversion = Jvm.findAnnotation(p, LongConversion.class); final String name = longConversion != null ? longConversion.value().getName() : ""; // Append appropriate value to the body based on parameter type and annotations if (!name.isEmpty() && (WireType.TEXT == wireType || WireType.YAML == wireType)) body.append(format("_valueOut_.rawText(%s.INSTANCE.asText(%s));\n", name, p.getName())); else if (p.getType().isPrimitive() || CharSequence.class.isAssignableFrom(p.getType())) { if (longConversion != null && (p.getType() == long.class || CharSequence.class.isAssignableFrom(p.getType()))) body.append(format("%s.writeLong(%s.INSTANCE, %s);\n", multipleArgs ? "_v_" : "_valueOut_", longConversion.value().getName(), p.getName())); else body.append(format("%s.%s(%s);\n", multipleArgs ? "_v_" : "_valueOut_", toString(erase(parameterTypes[j])), p.getName())); } else writeValue(dm, erase(parameterTypes[j]), body, startJ, p); } // Close the parameter array if there are multiple arguments if (multipleArgs) body.append("}, Object[].class);\n"); } /** * Writes the value of a parameter to the method body, handling various types of parameter values. * * @param dm The method whose parameter value needs to be written. * @param type The type of the parameter. * @param body The StringBuilder used to build the body of the method writer. * @param startJ The starting index to check if the current parameter is among multiple arguments. * @param p The parameter whose value is being written. */ private void writeValue(final Method dm, Class type, final StringBuilder body, final int startJ, final Parameter p) { final String name = p.getName(); String className = type.getTypeName().replace('$', '.'); final String vOut = dm.getParameterCount() > startJ + 1 ? "_v_" : "_valueOut_"; String after = ""; if (verboseTypes) { body .append(vOut) .append(".object(") .append(name) .append(");\n") .append(after); } else { if (!type.isInterface() && Marshallable.class.isAssignableFrom(type) && !Serializable.class.isAssignableFrom(type) && !DynamicEnum.class.isAssignableFrom(type)) { body.append("if (").append(name).append(" != null && ").append(className).append(".class == ").append(name).append(".getClass()) {\n") .append(vOut).append(".marshallable(").append(name).append(");\n") .append("} else {\n"); after = "}\n"; } body .append(vOut) .append(".object(") .append(className) .append(".class, ") .append(name) .append(");\n") .append(after); } } /** * Generates the return statement for a given method based on its return type. This method * deals with various return types, such as Void, interface types, and primitive types. * * @param dm The method for which the return statement is being generated. * @param interfaceClazz The class or interface being implemented. * @return A StringBuilder containing the return statement for the method. */ private StringBuilder methodReturn(final Method dm, final Class interfaceClazz) { final StringBuilder result = new StringBuilder(); final Class returnType = returnType(dm, interfaceClazz); if (returnType == Void.class || returnType == void.class) return result; if (returnType == DocumentContext.class) { result.append("return _dc_;\n"); } else if (returnType.isAssignableFrom(interfaceClazz) || returnType == interfaceClazz) { result.append("return this;\n"); } else if (returnType.isInterface()) { methodWritersMap.computeIfAbsent(returnType, k -> "methodWriter" + k.getSimpleName() + "TL"); result.append("// method return\n"); result.append(format("return methodWriter%sTL.get();\n", returnType.getSimpleName())); } else if (!returnType.isPrimitive()) { result.append("return null;\n"); } else if (returnType == boolean.class) { result.append("return false;\n"); } else if (returnType == byte.class) { result.append("return (byte)0;\n"); } else { result.append("return 0;\n"); } return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy