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

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

There is a newer version: 2.27ea1
Show 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.BytesUtil;
import net.openhft.chronicle.bytes.HexDumpBytes;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.io.IORuntimeException;
import net.openhft.chronicle.core.io.InvalidMarshallableException;
import net.openhft.chronicle.core.pool.EnumInterner;
import net.openhft.chronicle.core.pool.StringInterner;
import net.openhft.chronicle.core.scoped.ScopedThreadLocal;
import net.openhft.chronicle.core.util.*;
import net.openhft.chronicle.wire.internal.FromStringInterner;
import net.openhft.chronicle.wire.internal.VanillaFieldInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.nio.BufferUnderflowException;
import java.time.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static net.openhft.chronicle.core.pool.ClassAliasPool.CLASS_ALIASES;
import static net.openhft.chronicle.wire.Wires.toIntU30;

/**
 * The WireInternal enum provides a collection of utility constants, data structures,
 * and internal configurations to support wire operations. This enum does not have any
 * direct instances (as signified by the empty enum declaration). Instead, it serves as
 * a container for static members.
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public enum WireInternal {
    ; // none
    static final StringInterner INTERNER = new StringInterner(Jvm.getInteger("wire.interner.size", 4096));
    private static final int BINARY_WIRE_SCOPED_INSTANCES_PER_THREAD = Jvm.getInteger("chronicle.wireInternal.pool.binaryWire.instancesPerThread", 4);
    private static final int BYTES_SCOPED_INSTANCES_PER_THREAD = Jvm.getInteger("chronicle.wireInternal.pool.bytes.instancesPerThread", 2);

    // Thread-local storage for various utility instances.
    static final ThreadLocal>> BYTES_TL = new ThreadLocal<>();
    static final ThreadLocal>> BYTES_F2S_TL = new ThreadLocal<>();
    static final ThreadLocal> BINARY_WIRE_TL = new ThreadLocal<>();
    static final ThreadLocal>> INTERNAL_BYTES_TL = new ThreadLocal<>();
    static final ScopedThreadLocal BINARY_WIRE_SCOPED_TL = new ScopedThreadLocal<>(
            () -> new BinaryWire(Wires.unmonitoredDirectBytes())
                    .setOverrideSelfDescribing(true),
            Wire::clear,
            BINARY_WIRE_SCOPED_INSTANCES_PER_THREAD);
    static final ScopedThreadLocal> BYTES_SCOPED_THREAD_LOCAL = new ScopedThreadLocal<>(
            Wires::unmonitoredDirectBytes,
            Bytes::clear,
            BYTES_SCOPED_INSTANCES_PER_THREAD);

    // Empty array for stack trace elements.
    static final StackTraceElement[] NO_STE = {};

    // Collection of classes that are internable to reduce memory consumption.
    static final Set INTERNABLE = new HashSet<>(Arrays.asList(
            String.class,
//            Date.class,
//            TimeZone.class,
            UUID.class,
            DayOfWeek.class,
            LocalDate.class,
            LocalDateTime.class,
            LocalTime.class,
            Month.class,
            MonthDay.class,
            OffsetDateTime.class,
            OffsetTime.class,
            Period.class,
            Year.class,
            YearMonth.class,
            ZonedDateTime.class
//            ZoneId.class,
//            ZoneOffset.class
    ));

    // Map to store and retrieve object interners for specific classes.
    static final Map OBJECT_INTERNERS = new ConcurrentHashMap<>();

    // Internal fields for obtaining detailed messages and stack traces from Throwable instances.
    private static final Field DETAILED_MESSAGE = Jvm.getField(Throwable.class, "detailMessage");
    private static final Field STACK_TRACE = Jvm.getField(Throwable.class, "stackTrace");

    static {
        // Initialization block to add aliases for various classes.
        CLASS_ALIASES.addAlias(WireSerializedLambda.class, "SerializedLambda");
        CLASS_ALIASES.addAlias(WireType.class);
        CLASS_ALIASES.addAlias(SerializableFunction.class, "Function");
        CLASS_ALIASES.addAlias(SerializableBiFunction.class, "BiFunction");
        CLASS_ALIASES.addAlias(SerializableConsumer.class, "Consumer");
        CLASS_ALIASES.addAlias(SerializablePredicate.class, "Predicate");
        CLASS_ALIASES.addAlias(SerializableUpdater.class, "Updater");
        CLASS_ALIASES.addAlias(SerializableUpdaterWithArg.class, "UpdaterWithArg");
        CLASS_ALIASES.addAlias(VanillaFieldInfo.class, "FieldInfo");
        CLASS_ALIASES.addAlias(WireSerializedLambda.class, "SerializedLambda");
        CLASS_ALIASES.addAlias(INTERNABLE.stream().toArray(Class[]::new));
        CLASS_ALIASES.addAlias(LongArrayValueBitSet.class);
    }

    /**
     * Triggers the static initialization block of this enum.
     * The actual work of adding aliases is done by the static init block.
     */
    static void addAliases() {
        // static init block does the work.
    }

    /**
     * Returns the interned instance of the given enum {@code E} that matches the specified character sequence.
     * The method ensures that repeated requests for the same enum instance return the same shared object.
     *
     * @param     The enum type
     * @param eClass The class of the enum type
     * @param cs     The character sequence that represents the enum constant
     * @return The interned enum constant that matches the provided character sequence
     */
    @NotNull
    public static > E internEnum(@NotNull Class eClass, @NotNull CharSequence cs) {
        return (E) EnumInterner.ENUM_INTERNER.get(eClass).intern(cs);
    }

    /**
     * Writes data to the provided {@code wireOut} based on the given configurations and writer.
     *
     * @param wireOut    The output wire to write to
     * @param metaData   A flag indicating if meta data should be included in the write
     * @param notComplete A flag indicating if the data is not complete
     * @param writer     The writer responsible for writing the marshallable data
     * @return The position where the data was written
     * @throws InvalidMarshallableException If the provided data cannot be marshalled properly
     */
    public static long writeData(@NotNull WireOut wireOut, boolean metaData, boolean notComplete,
                                 @NotNull WriteMarshallable writer) throws InvalidMarshallableException {
        wireOut.getValueOut().resetBetweenDocuments();
        long position;

        @NotNull Bytes bytes = wireOut.bytes();
        position = bytes.writePositionForHeader(wireOut.usePadding());

        int metaDataBit = metaData ? Wires.META_DATA : 0;
        int len0 = metaDataBit | Wires.NOT_COMPLETE | Wires.UNKNOWN_LENGTH;
        bytes.writeOrderedInt(len0);
        writer.writeMarshallable(wireOut);
        if (!wireOut.isBinary())
            BytesUtil.combineDoubleNewline(bytes);
        long position1 = bytes.writePosition();
        if (wireOut.usePadding()) {
            int bytesToSkip = (int) ((position - position1) & 0x3);
            wireOut.addPadding(bytesToSkip);
            position1 = bytes.writePosition();
        }
//            if (position1 < position)
//                System.out.println("Message truncated from " + position + " to " + position1);
        int length;
        if (bytes instanceof HexDumpBytes) {
            // Todo: this looks suspicious. Why cast to int individually rather than use long arithmetics?
            length = metaDataBit | toIntU30((int) position1 - (int) position - 4, "Document length %,d out of 30-bit int range.");
        } else {
            length = metaDataBit | toIntU30(position1 - position - 4L, "Document length %,d out of 30-bit int range.");
        }
        if (wireOut.usePadding())
            bytes.testAndSetInt(position, len0, length | (notComplete ? Wires.NOT_COMPLETE : 0));
        else
            bytes.writeInt(position, length | (notComplete ? Wires.NOT_COMPLETE : 0));

        return position;
    }

    /**
     * Reads data from the provided {@code wireIn} starting from the given offset, and processes
     * the data using the provided meta data and data consumers.
     *
     * @param offset            The offset to start reading from within the wire input
     * @param wireIn            The input wire to read data from
     * @param metaDataConsumer  The consumer responsible for processing meta data.
     *                          May be null if meta data processing is not required.
     * @param dataConsumer      The consumer responsible for processing the actual data.
     *                          May be null if data processing is not required.
     * @return A boolean value indicating success or failure of reading the data.
     * @throws InvalidMarshallableException If the data cannot be unmarshalled properly.
     */
    public static boolean readData(long offset,
                                   @NotNull WireIn wireIn,
                                   @Nullable ReadMarshallable metaDataConsumer,
                                   @Nullable ReadMarshallable dataConsumer) throws InvalidMarshallableException {
        @NotNull final Bytes bytes = wireIn.bytes();
        long position = bytes.readPosition();
        long limit = bytes.readLimit();
        try {
            bytes.readLimit(bytes.isElastic() ? bytes.capacity() : bytes.realCapacity());
            bytes.readPosition(offset);
            return readData(wireIn, metaDataConsumer, dataConsumer);
        } finally {
            bytes.readLimit(limit);
            bytes.readPosition(position);
        }
    }

    /**
     * Reads data from the given {@code wireIn} and processes the data using the provided
     * meta data and data consumers. This method continues to read as long as there is data
     * to read and processes the data based on its header to determine whether it's meta data
     * or actual data.
     *
     * @param wireIn            The input wire to read data from.
     * @param metaDataConsumer  The consumer responsible for processing meta data.
     *                          May be null if meta data processing is not required.
     * @param dataConsumer      The consumer responsible for processing the actual data.
     *                          May be null if data processing is not required.
     * @return A boolean value indicating whether any data was successfully read.
     *         True if data was read, false otherwise.
     * @throws InvalidMarshallableException If the data cannot be unmarshalled properly or
     *                                      there's an issue with the data structure.
     * @throws BufferUnderflowException If attempting to read more data than what's available.
     */
    public static boolean readData(@NotNull WireIn wireIn,
                                   @Nullable ReadMarshallable metaDataConsumer,
                                   @Nullable ReadMarshallable dataConsumer) throws InvalidMarshallableException {
        @NotNull final Bytes bytes = wireIn.bytes();
        boolean read = false;

        // Loop to continually read data as long as there's data available
        while (true) {
            bytes.readPositionForHeader(wireIn.usePadding());

            // If less than 4 bytes remain, break from loop
            if (bytes.readRemaining() < 4) break;
            long position = bytes.readPosition();
            int header = bytes.readVolatileInt(position);

            // Check if the header's length is known
            if (!isKnownLength(header))
                return read;

            // Move the read position past the header
            bytes.readSkip(4);

            final int len = Wires.lengthOf(header);

            // If header indicates data
            if (Wires.isData(header)) {
                if (dataConsumer == null) {
                    return false;

                } else {
                    bytes.readWithLength(len, b -> dataConsumer.readMarshallable(wireIn));
                    return true;
                }
            } else {  // If header indicates metadata

                if (metaDataConsumer == null) {
                    // Skip the metadata
                    bytes.readSkip(len);
                } else {
                    // bytes.readWithLength(len, b -> metaDataConsumer.accept(wireIn));
                    // Reading meta data, setting limits to ensure correct data length
                    if (len > bytes.readRemaining())
                        throw new BufferUnderflowException();
                    long limit0 = bytes.readLimit();
                    long limit = bytes.readPosition() + len;
                    try {
                        bytes.readLimit(limit);
                        metaDataConsumer.readMarshallable(wireIn);
                    } finally {
                        bytes.readLimit(limit0);
                        bytes.readPosition(limit);
                    }
                }

                // If there's no data consumer, simply return true
                if (dataConsumer == null)
                    return true;
                read = true;
            }
        }
        return read;
    }

    /**
     * Reads raw data from the given {@code wireIn} without processing metadata and uses the provided
     * {@code dataConsumer} to process this data.
     *
     * @param wireIn        The input wire from which data is read.
     * @param dataConsumer  The consumer responsible for processing the actual data.
     * @throws InvalidMarshallableException If the data cannot be unmarshalled properly or
     *                                      there's an issue with the data structure.
     */
    public static void rawReadData(@NotNull WireIn wireIn, @NotNull ReadMarshallable dataConsumer) throws InvalidMarshallableException {
        @NotNull final Bytes bytes = wireIn.bytes();

        // Read the data header from the wire
        int header = bytes.readInt();

        // Ensure the header is both ready and indicates data
        assert Wires.isReady(header) && Wires.isData(header);

        // Determine the length of the data from the header
        final int len = Wires.lengthOf(header);

        long limit0 = bytes.readLimit();
        long limit = bytes.readPosition() + len;

        // Set the new reading limit and process the data, resetting the limit afterwards
        try {
            bytes.readLimit(limit);
            dataConsumer.readMarshallable(wireIn);
        } finally {
            bytes.readLimit(limit0);
        }
    }

    /**
     * Determines if the provided length value is a known length (neither metadata nor of unknown length).
     *
     * @param len The length value to be evaluated.
     * @return True if the length is known, false otherwise.
     */
    private static boolean isKnownLength(long len) {
        return (len & (Wires.META_DATA | Wires.LENGTH_MASK)) != Wires.UNKNOWN_LENGTH;
    }

    /**
     * Creates a throwable instance based on the type provided by {@code valueIn}. Optionally, this method
     * can also append the current stack trace to the throwable if {@code appendCurrentStack} is true.
     *
     * @param valueIn           The value input from which the type of the throwable is determined.
     * @param appendCurrentStack Flag indicating whether to append the current stack trace to the throwable.
     * @return A throwable instance of the type determined from {@code valueIn}.
     * @throws InvalidMarshallableException If the throwable cannot be instantiated properly.
     */
    public static Throwable throwable(@NotNull ValueIn valueIn, boolean appendCurrentStack) throws InvalidMarshallableException {
        @Nullable Class type = valueIn.typePrefix();
        Throwable throwable = ObjectUtils.newInstance((Class) type);

        // Further process and return the throwable (the method for this is not provided)
        return throwable(valueIn, appendCurrentStack, throwable);
    }

    /**
     * Creates and processes a throwable object based on the data present in the provided {@code valueIn}.
     * The provided throwable is populated with a message and stack trace extracted from the {@code valueIn}.
     * Optionally, the current stack trace can be appended to the throwable's stack trace.
     *
     * @param valueIn           The value input containing the details of the throwable.
     * @param appendCurrentStack Flag indicating whether to append the current stack trace to the throwable.
     * @param throwable         The throwable to be populated with details from {@code valueIn}.
     * @return The processed throwable.
     * @throws InvalidMarshallableException If the data in {@code valueIn} cannot be processed properly.
     */
    protected static Throwable throwable(@NotNull ValueIn valueIn, boolean appendCurrentStack, Throwable throwable) throws InvalidMarshallableException {

        // Store the reference to the throwable being processed
        final Throwable finalThrowable = throwable;

        // List to hold the stack trace elements extracted from valueIn
        @NotNull final List stes = new ArrayList<>();

        // Process the marshallable data in valueIn
        valueIn.marshallable(m -> {
            // Read and set the throwable's message
            @Nullable final String message = m.read(() -> "message").text();

            if (message != null) {
                try {
                    DETAILED_MESSAGE.set(finalThrowable, message);
                } catch (IllegalAccessException e) {
                    throw new AssertionError(e);
                }
            }

            // Extract and process the stack trace
            m.read(() -> "stackTrace").sequence(stes, (stes0, stackTrace) -> {
                while (stackTrace.hasNextSequenceItem()) {
                    stackTrace.marshallable(r -> {
                        // Extract details of each stack trace element
                        @Nullable final String declaringClass = r.read(() -> "class").text();
                        @Nullable final String methodName = r.read(() -> "method").text();
                        @Nullable final String fileName = r.read(() -> "file").text();
                        final int lineNumber = r.read(() -> "line").int32();

                        stes0.add(new StackTraceElement(declaringClass, methodName,
                                fileName, lineNumber));
                    });
                }
            });
        });

        // If appendCurrentStack is true, add current stack trace details
        if (appendCurrentStack) {
            stes.add(new StackTraceElement("~ remote", "tcp ~", "", 0));
            StackTraceElement[] stes2 = Thread.currentThread().getStackTrace();
            int first = 6;
            int last = Jvm.trimLast(first, stes2);
            // Loop to add each stack trace element from current thread's stack
            for (int i = first; i <= last; i++)
                stes.add(stes2[i]);
        }

        // Set the final stack trace to the throwable
        try {
            STACK_TRACE.set(finalThrowable, stes.toArray(NO_STE));
        } catch (IllegalAccessException e) {
            throw Jvm.rethrow(e);
        }
        return throwable;
    }

    /**
     * Merges two strings with a space in between. If one of the strings is null,
     * the other string is returned. If both are null, null is returned.
     *
     * @param a The first string.
     * @param b The second string.
     * @return The merged string or one of the strings if the other is null.
     */
    @Nullable
    static String merge(@Nullable String a, @Nullable String b) {
        return a == null ? b : b == null ? a : a + " " + b;
    }

    /**
     * Attempts to intern an object if it's of a type that is internable
     * and is actually a string. Otherwise, converts the object to the given class.
     *
     * @param tClass The class to which the object needs to be converted or interned.
     * @param o      The object to be interned or converted.
     * @param     Type of the class.
     * @return The interned or converted object.
     */
    static  T intern(Class tClass, Object o) {
        if (INTERNABLE.contains(tClass) && o instanceof String) {
            String s = (String) o;
            if (tClass == String.class)
                return (T) INTERNER.intern(s);
            ObjectInterner interner = OBJECT_INTERNERS
                    .computeIfAbsent(tClass,
                            ObjectInterner::new);
            return (T) interner.intern(s);
        }
        return ObjectUtils.convertTo(tClass, o);
    }

    /**
     * A specialized class that interns objects based on their string representations.
     *
     * @param  Type of the object being interned.
     */
    static class ObjectInterner extends FromStringInterner {
        final Class tClass;

        /**
         * Constructs an ObjectInterner for the specified class type with a
         * default capacity of 256.
         *
         * @param tClass The class type of the object to be interned.
         */
        ObjectInterner(Class tClass) {
            super(256);
            this.tClass = tClass;
        }

        @Override
        protected @NotNull T getValue(String s) throws IORuntimeException {
            return ObjectUtils.convertTo(tClass, s);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy