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

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

The 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.UpdateInterceptor;
import net.openhft.chronicle.core.io.Closeable;
import net.openhft.chronicle.wire.utils.SourceCodeFormatter;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Arrays.stream;
import static java.util.Collections.*;
/**
 * The {@code GenerateMethodWriter2} class is responsible for generating method writers based on the provided metadata.
 * 

* It extends the {@link AbstractClassGenerator} with metadata type {@link GMWMetaData}. This class internally maintains * a set of template methods that help in the method writer generation. These templates are based on certain method names * and parameter types, which define their structure. */ @SuppressWarnings("this-escape") public class GenerateMethodWriter2 extends AbstractClassGenerator { // The simple name of the DocumentContext class used for template methods. private static final String DOCUMENT_CONTEXT = DocumentContext.class.getSimpleName(); /** * Holds the predefined templates for methods. The outer map's key is the method name, while the inner map * has the method parameter types as the key and the method body as the value. */ private static final Map>, String>> TEMPLATE_METHODS = new LinkedHashMap<>(); static { // Initialize the TEMPLATE_METHODS with predefined method structures. 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 this.outSupplier.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 this.outSupplier.get().acquireWritingDocument(metaData);\n" + "}\n")); Map>, String> wd = new LinkedHashMap<>(); wd.put(singletonList(DocumentContext.class), "" + "public " + DOCUMENT_CONTEXT + " writingDocument() {\n" + " return this.outSupplier.get().writingDocument();\n" + "}\n"); wd.put(dcBoolean, "" + "public " + DOCUMENT_CONTEXT + " writingDocument(boolean metaData) {\n" + " return this.outSupplier.get().writingDocument(metaData);\n" + "}\n"); TEMPLATE_METHODS.put("writingDocument", wd); } // Maintains a mapping of method writers based on their respective classes. private final Map, String> methodWritersMap = new LinkedHashMap<>(); /** * Default constructor that initializes the metadata and sets up necessary imports. */ public GenerateMethodWriter2() { super(new GMWMetaData()); // add to imports nameForClass(DocumentContext.class); importSet.add("net.openhft.chronicle.bytes.*"); importSet.add("net.openhft.chronicle.wire.*"); } /** * Retrieves the method template for a given method name, return type, and parameter types. *

* The method looks up the template from the predefined {@code TEMPLATE_METHODS}. If no matching template is * found, it returns {@code null}. * * @param name The method name to look up * @param returnType The return type of the method * @param pts The parameter types of the method * @return The method template as a string, or {@code null} if not found */ private static String templateFor(String name, Class returnType, Class[] pts) { Map>, String> map = TEMPLATE_METHODS.get(name); if (map == null) return null; List> sig = new ArrayList<>(); sig.add(returnType); addAll(sig, pts); return map.get(sig); } /** * Converts a given {@link Class} type to a corresponding string representation. *

* This method provides string representations for various primitive types, {@link CharSequence}, and * {@link Marshallable}. If the type does not match any predefined types, it defaults to returning "object". * * @param type The class type to convert * @return The corresponding string representation of the type */ private static CharSequence asString(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"; } @Override protected void generateFields(SourceCodeFormatter mainCode) { super.generateFields(mainCode); withLineNumber(mainCode) .append("private transient final Closeable closeable;\n"); if (metaData().useUpdateInterceptor()) mainCode.append("private transient final ").append(nameForClass(UpdateInterceptor.class)).append(" updateInterceptor;\n"); mainCode.append("private transient ") .append(nameForClass(Supplier.class)).append("<").append(nameForClass(MarshallableOut.class)).append("> outSupplier;\n"); } @Override protected void generateConstructors(SourceCodeFormatter mainCode) { super.generateConstructors(mainCode); withLineNumber(mainCode) .append("public ").append(className()).append("(") .append(nameForClass(Supplier.class)).append("<").append(nameForClass(MarshallableOut.class)).append("> outSupplier, ") .append(nameForClass(Closeable.class)).append(" closeable, ") .append(nameForClass(UpdateInterceptor.class)).append(" updateInterceptor) {\n" + "this.outSupplier = outSupplier;\n" + "this.closeable = closeable;\n"); if (metaData().useUpdateInterceptor()) mainCode.append("this.updateInterceptor = updateInterceptor;"); mainCode.append("}\n"); } @Override protected void generateMethod(Method method, StringBuilder params, List paramList, SourceCodeFormatter mainCode) { String name = method.getName(); Class returnType = method.getReturnType(); Class[] parameterTypes = method.getParameterTypes(); String template = templateFor(name, returnType, parameterTypes); if (template != null) { mainCode.append(template); return; } boolean terminating = returnType == Void.class || returnType == void.class || returnType.isPrimitive(); String wdc = nameForClass(WriteDocumentContext.class); boolean passthrough = returnType == DocumentContext.class; withLineNumber(mainCode) .append("MarshallableOut _out_ = this.outSupplier.get();\n"); if (!passthrough) withLineNumber(mainCode) .append("try ("); mainCode.append("final ").append(wdc).append(" _dc_ = (").append(wdc).append(") _out_.acquireWritingDocument(") .append(metaData().metaData()) .append(")"); if (passthrough) mainCode.append(";\n"); else mainCode.append(") {\n"); mainCode.append("_dc_.chainedElement(" + (!terminating && !passthrough) + ");\n"); mainCode.append("if (_out_.recordHistory()) MessageHistory.writeHistory(_dc_);\n"); int startJ = 0; final String eventName; if (parameterTypes.length > 0 && name.equals(metaData().genericEvent)) { // this is used when we are processing the genericEvent eventName = parameterTypes[0].getName(); startJ = 1; } else { eventName = '\"' + name + '\"'; } writeEventNameOrId(method, mainCode, eventName); if (parameterTypes.length == 0) mainCode.append(".text(\"\");\n"); else writeArrayOfParameters(method, mainCode, startJ); mainCode.append("}\n"); methodReturn(mainCode, method, metaData().interfaces()); } @Override protected void generateEnd(SourceCodeFormatter mainCode) { super.generateEnd(mainCode); for (Map.Entry, String> e : methodWritersMap.entrySet()) { mainCode.append("private transient ThreadLocal<").append(nameForClass(e.getKey())).append("> ").append(e.getValue()) .append("= ThreadLocal.withInitial(() -> this.outSupplier.get().methodWriter(").append(nameForClass(e.getKey())).append(".class));\n"); } } /** * Writes the event name or ID associated with the provided method. * If a {@code MethodId} annotation is present, the method writes the event using its value. * Otherwise, it simply writes the event name. * * @param method The method whose event information needs to be written. * @param body The code formatter to which the write operation should be appended. * @param eventName The name of the event. */ private void writeEventNameOrId(final Method method, final SourceCodeFormatter body, final String eventName) { // Check if using method IDs is required and find any @MethodId annotation present final Optional methodId = metaData().useMethodIds() ? stream(method.getAnnotations()).filter(MethodId.class::isInstance).findFirst() : Optional.empty(); // If a MethodId annotation is present, write the event with its value if (methodId.isPresent()) { long value = ((MethodId) methodId.get()).value(); withLineNumber(body) .append("_dc_.wire().writeEventId(").append(eventName).append(", ").append(String.valueOf(value)).append(")"); } else { // Otherwise, simply write the event name withLineNumber(body) .append("_dc_.wire().write(").append(eventName).append(")"); } } /** * Writes the array of parameters for the provided method. * Handles primitive types and CharSequences directly, while delegating non-primitive types to {@code writeValue}. * * @param dm The method whose parameters are to be written. * @param body The code formatter to which the write operations should be appended. * @param startJ The starting index for parameters. */ private void writeArrayOfParameters(final Method dm, final SourceCodeFormatter body, final int startJ) { // Check if there are multiple parameters boolean multipleParams = dm.getParameterTypes().length > startJ + 1; if (multipleParams) body.append(".array(v -> {\n"); // Iterate over the method's parameters and write them Parameter[] parameters = dm.getParameters(); for (int j = startJ; j < parameters.length; j++) { final Parameter p = parameters[j]; // For primitive types and CharSequences, write directly if (p.getType().isPrimitive() || CharSequence.class.isAssignableFrom(p.getType())) { body.append(multipleParams ? "v." : ".").append(asString(p.getType())).append("(").append(p.getName()).append(");\n"); } else // For non-primitive types, delegate to writeValue writeValue(dm, body, startJ, p); } // Close array writing if there were multiple parameters if (multipleParams) body.append("}, Object[].class);\n"); } /** * Writes the value of a given parameter. * The parameter is written as an object with its associated class name and name. * * @param dm The method to which the parameter belongs. * @param body The code formatter to which the write operation should be appended. * @param startJ The starting index for parameters. * @param p The parameter whose value needs to be written. */ private void writeValue(final Method dm, final SourceCodeFormatter body, final int startJ, final Parameter p) { // Retrieve class name, replacing inner class '$' with '.' String className = p.getType().getTypeName().replace('$', '.'); // Write the parameter value as an object body .append(dm.getParameterTypes().length > startJ + 1 ? "v." : ".") .append("object(") .append(className) .append(".class, ") .append(p.getName()) .append(");\n"); } /** * Handles the return value for a provided method. * Determines the return value based on the method's return type and available interfaces. * The method ensures that the correct return type or default value is appended to the result. * * @param result The code formatter to which the return value should be appended. * @param method The method whose return value needs to be determined. * @param interfaceClases Set of interfaces to determine if the return type matches any. */ private void methodReturn(SourceCodeFormatter result, final Method method, final Set> interfaceClases) { Class returnType = method.getReturnType(); if (returnType == void.class) return; // Check if returnType matches any of the interfaces provided if (interfaceClases.stream().anyMatch(i -> returnType == i || returnType.isAssignableFrom(i))) { withLineNumber(result).append("return this;\n"); } else if (returnType.isInterface()) { // If the returnType is an interface, compute the corresponding method writer if absent methodWritersMap.computeIfAbsent(returnType, k -> "methodWriter" + k.getSimpleName() + "TL"); withLineNumber(result).append("return methodWriter").append(returnType.getSimpleName()).append("TL.get();\n"); } else if (!returnType.isPrimitive()) { // For non-primitive non-interface return types, return null withLineNumber(result).append("return null;\n"); } else if (returnType == boolean.class) { // For primitive boolean type, return false withLineNumber(result).append("return false;\n"); } else if (returnType == byte.class) { // For primitive byte type, return 0 withLineNumber(result).append("return (byte)0;\n"); } else { // For other primitive types, return 0 withLineNumber(result).append("return 0;\n"); } } /** * Metadata associated with the {@code GenerateMethodWriter2} class. * Provides options like use of method IDs, metadata, and generic events. */ public static class GMWMetaData extends AbstractClassGenerator.MetaData { private boolean metaData; private boolean useMethodIds; private String genericEvent; /** * Gets the meta data usage status. * * @return {@code true} if metadata is to be used; {@code false} otherwise. */ public boolean metaData() { return metaData; } /** * Sets the meta data usage status. * * @param metaData Whether to use metadata. * @return The updated metadata instance. */ public GMWMetaData metaData(boolean metaData) { this.metaData = metaData; return this; } /** * Gets the method IDs usage status. * * @return {@code true} if method IDs are to be used; {@code false} otherwise. */ public boolean useMethodIds() { return useMethodIds; } /** * Sets the method IDs usage status. * * @param useMethodIds Whether to use method IDs. * @return The updated metadata instance. */ public GMWMetaData useMethodIds(boolean useMethodIds) { this.useMethodIds = useMethodIds; return this; } /** * Gets the generic event. * * @return The generic event string. */ public String genericEvent() { return genericEvent; } /** * Sets the generic event. * * @param genericEvent The generic event string. * @return The updated metadata instance. */ public GMWMetaData genericEvent(String genericEvent) { this.genericEvent = genericEvent; return this; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy