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

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

The newest version!
/*
 * Copyright 2016-2020 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.Bytes;
import net.openhft.chronicle.bytes.MethodReader;
import net.openhft.chronicle.bytes.MethodReaderInterceptorReturns;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.io.ClosedIllegalStateException;
import net.openhft.chronicle.core.util.Mocker;
import net.openhft.chronicle.wire.utils.MethodReaderStatus;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Predicate;

import static java.lang.ThreadLocal.withInitial;
import static net.openhft.chronicle.core.io.Closeable.closeQuietly;

/**
 * This is the AbstractGeneratedMethodReader class implementing the MethodReader interface.
 * It serves as a base class for generated method readers, providing foundational functionality
 * and utility methods to facilitate method reading.
 */
public abstract class AbstractGeneratedMethodReader implements MethodReader {

    // A no-operation message history consumer.
    private static final Consumer NO_OP_MH_CONSUMER = Jvm.uncheckedCast(Mocker.ignored(Consumer.class));
    public final static ThreadLocal SERVICE_NAME = new ThreadLocal<>();
    private final static ConcurrentHashMap TEMP_MESSAGE_HISTORY_BY_SERVICE_NAME = new ConcurrentHashMap<>();
    protected final WireParselet debugLoggingParselet;
    private final MarshallableIn in;
    private final MessageHistoryThreadLocal tempMessageHistory;
    protected MessageHistory messageHistory;
    protected boolean dataEventProcessed;

    // Flag to determine if the input should be closed.
    private boolean closeIn = false;
    // Flag to determine if the reader is closed.
    private boolean closed;

    // Consumer for processing message history.
    private Consumer historyConsumer = NO_OP_MH_CONSUMER;

    private Predicate predicate;
    private boolean scanning;

    /**
     * Constructs a new AbstractGeneratedMethodReader with the provided input interface
     * and a debug logging parselet.
     *
     * @param in                    The input interface for marshallable data
     * @param debugLoggingParselet  The parselet used for debugging and logging
     */
    protected AbstractGeneratedMethodReader(MarshallableIn in,
                                            WireParselet debugLoggingParselet) {
        this.in = in;
        this.debugLoggingParselet = debugLoggingParselet;

        // gets the name of the service so we can offer history message caching

        // the services name is set by the chronicle services framework
        String serviceName = SERVICE_NAME.get();
        if (serviceName == null)
            serviceName = "";

        // this was handled to support when multiple services are using the same thread.
        this.tempMessageHistory = TEMP_MESSAGE_HISTORY_BY_SERVICE_NAME.computeIfAbsent(serviceName, x -> new MessageHistoryThreadLocal());
    }

    /**
     * Sets a predicate to be used by the method reader.
     *
     * @param predicate The predicate for filtering
     * @return The current instance of the AbstractGeneratedMethodReader class
     */
    public AbstractGeneratedMethodReader predicate(Predicate predicate) {
        this.predicate = predicate;
        return this;
    }

    /**
     * A utility method that assists implementations in retrieving a method from a class.
     * It looks up the method by its name and parameter types, making it accessible if it's private or protected.
     *
     * @param clazz          The class containing the method
     * @param name           The name of the method
     * @param parameterTypes The parameter types of the method
     * @return The method if found, otherwise throws an AssertionError
     */
    protected static Method lookupMethod(Class clazz, String name, Class... parameterTypes) {
        try {
            final Method method = clazz.getMethod(name, parameterTypes);

            Jvm.setAccessible(method);

            return method;
        } catch (NoSuchMethodException e) {
            throw new AssertionError(e);
        }
    }

    /**
     * Sets a consumer for processing message history.
     * This consumer is invoked if the next message is for a different queue and the history
     * message isn't written to the output queue. It ensures that the LAST_WRITTEN mechanism
     * works even when no output is present for a given message.
     *
     * @param historyConsumer The consumer for processing message history
     */
    public void historyConsumer(Consumer historyConsumer) {
        this.historyConsumer = historyConsumer;
    }

    /**
     * Reads call name and arguments from the wire and performs invocation on a target object instance.
     * The implementation of this method is generated at runtime, see {@link GenerateMethodReader}.
     *
     * @param wireIn Data input.
     * @return MethodReaderStatus.
     */
    protected abstract MethodReaderStatus readOneGenerated(WireIn wireIn);

    protected abstract MethodReaderStatus readOneMetaGenerated(WireIn wireIn);

    /**
     * Reads the content based on the provided document context.
     *
     * @param context Reading document context.
     * @return KNOWN if the read event is known, UNKNOWN if the event is not recognized, or EMPTY if no content is present.
     */
    public MethodReaderStatus readOne0(DocumentContext context) {
        WireIn wireIn = context.wire();

        // Return EMPTY status if no content.
        if (wireIn == null)
            return MethodReaderStatus.EMPTY;

        // Check if we need to write the unwritten message history.
        if (historyConsumer != NO_OP_MH_CONSUMER) {
            writeUnwrittenMessageHistory(context);

            // Another reader may have swapped MessageHistory.get() and TEMP_MESSAGE_HISTORY
            // Clearing local reference to recover link to the proper thread-local, which is MessageHistory.get()
            messageHistory = null;
        }

        messageHistory().reset(context.sourceId(), context.index());

        try {
            wireIn.startEvent();
            wireIn.consumePadding();
            Bytes bytes = wireIn.bytes();
            dataEventProcessed = false;
            MethodReaderStatus decoded = MethodReaderStatus.EMPTY; // Initialize status as no message.

            // Read and process all remaining bytes.
            while (bytes.readRemaining() > 0) {
                if (wireIn.isEndEvent())
                    break;
                long start = bytes.readPosition();

                // Read the wire based on whether it's data or metadata.
                MethodReaderStatus mrs = context.isData()
                        ? readOneGenerated(wireIn)
                        : readOneMetaGenerated(wireIn);

                // Update the decoding status based on the current read status.
                switch (mrs) {
                    case HISTORY:
                        // Status remains unchanged.
                        break;
                    case KNOWN:
                        decoded = MethodReaderStatus.KNOWN;
                        break;
                    case UNKNOWN:
                        if (decoded == MethodReaderStatus.EMPTY)
                            decoded = MethodReaderStatus.UNKNOWN;
                        break;
                    default:
                        throw new AssertionError(mrs);
                }

                // If any bytes are ignored, return the decoding status.
                if (restIgnored())
                    return decoded;

                wireIn.consumePadding();
                if (bytes.readPosition() == start) {
                    logNonProgressWarning(bytes.readRemaining());
                    return decoded;
                }
            }
            wireIn.endEvent();
            return decoded;

        } finally {
            // Don't save message history if we are reading non-data event (e.g. another "message history only" message)
            // Infinite loop between services is possible otherwise
            if (historyConsumer != NO_OP_MH_CONSUMER && dataEventProcessed)
                swapMessageHistoryIfDirty();
            messageHistory.reset();
        }
    }

    /**
     * Logs a warning message indicating that there has been no progress in reading the wire data.
     * The warning provides information about the number of bytes that are left unread.
     *
     * @param bytes The number of bytes that are left unread.
     */
    private void logNonProgressWarning(long bytes) {
        Jvm.warn().on(getClass(), "Failed to progress reading " + bytes + " bytes left.");
    }

    /**
     * Determines if the rest of the wire data should be ignored.
     *
     * @return {@code true} if the rest should be ignored, {@code false} otherwise. Default implementation returns false.
     */
    protected boolean restIgnored() {
        return false;
    }

    /**
     * Swaps the current message history with a temporary message history using a double buffer technique.
     * The method ensures that if an input event did not generate an output event, its message history
     * will be saved, potentially to be written by another method reader in the future.
     * If an input event did generate an output, stale information from a previous input event is cleared.
     */
    private void swapMessageHistoryIfDirty() {
        if (messageHistory.isDirty()) {
            // This input event didn't generate an output event.
            // Saving message history - in case next input event will be processed by another method reader,
            // that method reader will cooperatively write saved history.
            messageHistory = tempMessageHistory.getAndSet(messageHistory);
            MessageHistory.set(messageHistory);
            assert (messageHistory != tempMessageHistory.get());
        } else {
            // This input event generated an output event.
            // In case previous input event was processed by this method reader, TEMP_MESSAGE_HISTORY may contain
            // stale info on event's message history, which is superseded by the message history written now.
            tempMessageHistory.get().reset();
        }
    }

    /**
     * Writes the message history of the last message to the output queue, but only if required.
     * This is determined by checking if the message history has not yet been written to the output queue.
     *
     * @param context The {@link DocumentContext} of the output queue where the message history might be written.
     */
    private void writeUnwrittenMessageHistory(DocumentContext context) {
        final MessageHistory mh = tempMessageHistory.get();
        if (mh.sources() != 0 && context.sourceId() != mh.lastSourceId() && mh.isDirty())
            historyConsumer.accept(mh);
    }

    @Override
    public boolean readOne() {
        if (!predicate.test(this))
            return false;

        do {
            throwExceptionIfClosed();

            try (DocumentContext context = in.readingDocument()) {
                if (!context.isPresent()) {
                    break;
                }

                MethodReaderStatus mrs = readOne0(context);
                switch (mrs) {
                    case KNOWN:
                        if (scanning && context.isMetaData())
                            break; // continue
                        return true;
                    case EMPTY:
                    case UNKNOWN:
                        if (scanning)
                            break; // continue looking
                        return true;
                    default:
                        throw new AssertionError(mrs);
                }
                // retry on a data message unless a known message is found.
            }
        } while (scanning);
        return false;
    }

    /**
     * Checks if the method reader instance has been closed and throws a {@link ClosedIllegalStateException} if it is.
     * This method is intended to guard against operations on a closed instance, ensuring that no further
     * operations can be performed once the instance has been closed. This is particularly useful for
     * methods that modify the state of the instance or rely on its open state to function correctly.
     *
     * @throws ClosedIllegalStateException if this instance has already been closed.
     */
    public void throwExceptionIfClosed() throws ClosedIllegalStateException {
        if (isClosed())
            throw new ClosedIllegalStateException("Closed");
    }

    @Override
    public MethodReaderInterceptorReturns methodReaderInterceptorReturns() {
        return null;
    }

    @Override
    public void close() {
        if (closeIn)
            closeQuietly(in);
        closed = true;
    }

    @Override
    public boolean isClosed() {
        return closed;
    }

    @Override
    public MethodReader closeIn(boolean closeIn) {
        throwExceptionIfClosed();
        this.closeIn = closeIn;
        return this;
    }

    /**
     * Offers a workaround to selectively disable "object recycling read" provided by {@link ValueIn#object(Object, Class)}
     * for specific object types. This ensures that certain objects, such as arrays and collections, are
     * not unintentionally reused or recycled during the reading process.
     *
     * @param  The generic type of the object to check.
     * @param o   The object instance to verify and possibly recycle.
     * @return The object itself if recycling is not applied, or {@code null} if the object is either
     * {@code null} or an array. If the object is a collection or map, the method will clear its
     * content and return the object.
     */
    protected  T checkRecycle(T o) {
        if (o == null || o.getClass().isArray()) // If the object is null or an array, return null to prevent recycling.
            return null;

        if (o instanceof Collection) { // If the object is a collection, clear its content.
            ((Collection) o).clear();
        }

        if (o instanceof Map) { // If the object is a map, clear its content.
            ((Map) o).clear();
        }

        // For objects of type AbstractMarshallableCfg, reset them to their default state.
        if (o instanceof AbstractMarshallableCfg) {
            ((AbstractMarshallableCfg) o).reset();
        }

        // Return the potentially modified object.
        return o;
    }

    /**
     * Invokes a given method on the provided object with the specified arguments.
     *
     * @param method The method to be invoked.
     * @param o The target object on which the method is to be invoked.
     * @param objects The arguments to the method.
     * @return Returns the result of the method invocation.
     * @throws RuntimeException if the method invocation throws an exception.
     */
    protected Object actualInvoke(Method method, Object o, Object[] objects) {
        try {
            return method.invoke(o, objects);
        } catch (Exception e) {
            throw Jvm.rethrow(e);
        }
    }

    /**
     * Retrieves the current message history.
     * If the message history is not initialized, fetches it from the MessageHistory class.
     *
     * @return The current message history.
     */
    private MessageHistory messageHistory() {
        if (messageHistory == null)
            messageHistory = MessageHistory.get();

        return messageHistory;
    }

    /**
     * Sets the scanning state.
     *
     * @param scanning The desired state of scanning. True to indicate scanning, false otherwise.
     */
    public void scanning(boolean scanning) {
        this.scanning = scanning;
    }

    /**
     * This is a helper class that manages thread-local instances of MessageHistory.
     * It provides methods to get and set the current thread's message history using a {@link ThreadLocal}.
     */
    private static final class MessageHistoryThreadLocal {

        private final ThreadLocal messageHistoryTL = withInitial(() -> {
            @NotNull VanillaMessageHistory veh = new VanillaMessageHistory();
            veh.addSourceDetails(true);
            return veh;
        });

        /**
         * Replaces the current thread's message history with the provided one
         * and returns the replaced message history.
         *
         * @param mh The new message history to set for the current thread.
         * @return The replaced message history.
         */
        private MessageHistory getAndSet(MessageHistory mh) {
            final MessageHistory result = messageHistoryTL.get();
            messageHistoryTL.set(mh);
            return result;
        }

        /**
         * Retrieves the current thread's message history.
         *
         * @return The current thread's message history.
         */
        public MessageHistory get() {
            return messageHistoryTL.get();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy