/* * Copyright (C) 2012, 2016 higherfrequencytrading.com * Copyright (C) 2016 Roman Leventov * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package net.openhft.chronicle.hash; import net.openhft.chronicle.algo.hashing.LongHashFunction; import net.openhft.chronicle.bytes.BytesStore; import net.openhft.chronicle.bytes.BytesUtil; import net.openhft.chronicle.bytes.RandomDataInput; import net.openhft.chronicle.bytes.RandomDataOutput; import net.openhft.chronicle.map.ChronicleMap; import net.openhft.chronicle.map.MapEntry; import net.openhft.chronicle.map.MapQueryContext; import net.openhft.chronicle.set.ChronicleSet; import org.jetbrains.annotations.Nullable; import java.io.PrintWriter; import java.io.StringWriter; import static net.openhft.chronicle.algo.bytes.Access.checkedRandomDataInputAccess; /** * Dual bytes/object access to keys or values (for {@link ChronicleMap}) and elements (for {@link * ChronicleSet}) throughout the Chronicle Map library. * * <p>Bytes access: {@link #bytes()} + {@link #offset()} + {@link #size()}. * * <p>Object access: {@link #get()}. * * <p>In most cases, each particular {@code Data} wraps either some object or some bytes. Object * is marshalled to bytes lazily on demand, and bytes are lazily deserialized to object, * accordingly. * * @param <T> type of the accessed objects */ public interface Data<T> { /** * Returns the accessor object to the {@code Data}'s bytes. * * <p>If this {@code Data} wraps some bytes, this method just returns a reference to that bytes. * * <p>If this {@code Data} wraps an object, this method performs serialization internally and * returns a reference to the output buffer, caching the result for subsequent calls of this * method. * * <p>For safety, this interface returns read-only object, because it could expose bytes source * that must be immutable, e. g. a `char[]` array behind a {@code String}. But in cases when the * {@code Data} instance wraps off-heap bytes, e. g. {@link MapEntry#value()}, it is allowed to * cast the object, returned from this method, to {@link BytesStore}, and write into the * off-heap memory. You should only ensure that current context (e. g. {@link MapQueryContext}) * is locked exclusively, in order to avoid data races. */ RandomDataInput bytes(); /** * Returns the offset to the {@code Data}'s bytes sequence within the {@link RandomDataInput}, * returned from {@link #bytes()} method. For example, the first byte of the bytes * representation of some {@code data} is {@code data.bytes().readByte(data.offset())}. */ long offset(); /** * Returns the size of this {@code Data}'s bytes sequence. It spans from {@link #offset()} to * {@code offset() + size() - 1} bytes within the {@link RandomDataInput}, returned from * {@link #bytes()} method. */ long size(); /** * Computes hash code on the bytes representation of this {@code Data}, using the given hash * function. * * @param f the hash function to compute hash code using * @return the hash code of this {@code Data}'s bytes, computed by the given function */ default long hash(LongHashFunction f) { return f.hash(bytes(), checkedRandomDataInputAccess(), offset(), size()); } /** * Compares bytes of this {@code Data} to the given bytes {@code source}, starting from the * given offset. * * <p>Default implementation compares {@link #bytes()} of this {@code Data}, but custom * implementation may only check if {@linkplain #get() object} of this {@code Data} <i>would</i> * be serialized to the same bytes sequence, if this {@code Data} wraps an object and obtaining * {@link #bytes()} requires serialization internally. * * @param source the bytes source, to compare this {@code Data}'s bytes with * @param sourceOffset the offset in the bytes source, the bytes sequence starts from * @return {@code true} if the given bytes sequence is equivalent to this {@code Data}'s bytes, * byte-by-byte */ default boolean equivalent(RandomDataInput source, long sourceOffset) { return BytesUtil.bytesEqual(source, sourceOffset, bytes(), offset(), size()); } /** * Writes bytes of this {@code Data} to the given {@code target} from the given {@code * targetOffset}. * * <p>Default implementation copies {@link #bytes()} of this {@code Data} using standard IO * methods of {@code RandomDataInput} and {@code RandomDataOutput}, but custom implementation * may write directly from {@linkplain #get() object}, if this {@code Data} is object-based and * obtaining {@link #bytes()} requires serialization internally. This allows to avoid double * copy. * * @param target the destination to write this data bytes to * @param targetOffset the offset in the target, to write the bytes from. */ default void writeTo(RandomDataOutput target, long targetOffset) { target.write(targetOffset, bytes(), offset(), size()); } /** * Returns object view of this {@code Data}. * * <p>If this {@code Data} wraps some object, this method just returns that object. * * <p>If this {@code Data} wraps some bytes, this method performs deserialization internally * and returns the resulting on-heap object, caching it for subsequent calls of this method. The * returned object could be reused, therefore it is <i>generally disallowed</i> to use the * object, returned from this method, <i>outside</i> some context, or a block, synchronized with * locks, or lambda, etc., which provided the access to this {@code Data} instance. */ T get(); /** * Deserialize and return an object from the {@code Data}'s bytes, trying to reuse the given * {@code using} object (might be {@code null}). This method either returns the given {@code * using} object back (if reuse is possible) or creates a new object, rather than some * internally cached and reused one, therefore it is <i>always allowed</i> to use the object, * returned from this method, <i>outside</i> some context, or a block, synchronized with locks, * or lambda, etc., which provided the access to this {@code Data} instance. */ T getUsing(@Nullable T using); /** * Utility method, compares two {@code Data} instances represent equivalent bytes sequences: * by comparing {@linkplain #size() their sizes}, then calling {@link * #equivalent(RandomDataInput, long) d1.equivalent(d2.bytes(), d2.offset())}. * * @param d1 the first {@code Data} to compare * @param d2 the second {@code Data} to compare * @return if the given {@code Data} instances represent equivalent bytes sequences */ static boolean bytesEquivalent(Data<?> d1, Data<?> d2) { return d1.size() == d2.size() && d1.equivalent(d2.bytes(), d2.offset()); } /** * {@code Data} implementations should override {@link Object#hashCode()} with delegation to * this method. Computes {@code Data}'s hash code by applying a hash function to {@code Data}'s * <i>bytes</i> representation. */ default int dataHashCode() { return (int) hash(LongHashFunction.xx_r39()); } /** * {@code Data} implementations should override {@link Object#equals(Object)} with delegation to * this method. Compares {@code Data}s' <i>bytes</i> representations. */ default boolean dataEquals(Object obj) { return obj != null && obj instanceof Data && Data.bytesEquivalent(this, (Data<?>) obj); } /** * {@code Data} implementations should override {@link Object#toString()} with delegation to * this method. Delegates to {@code Data}'s <i>object</i> {@code toString()}, i. e. equivalent * to calling {@code get().toString()} on this {@code Data} instance with some fallback, if * {@code get()} method throws an exception. */ default String dataToString() { try { return get().toString(); } catch (Exception e) { StringBuilder sb = new StringBuilder(); sb.append("failed to deserialize object from data with bytes: ["); RandomDataInput bs = bytes(); for (long off = offset(), lim = offset() + size(); off < lim; off++) { sb.append(bs.printable(off)); } sb.append("], exception: "); StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); String exceptionAsString = sw.toString(); sb.append(exceptionAsString); return sb.toString(); } } }