net.openhft.chronicle.hash.Data Maven / Gradle / Ivy
/*
* Copyright 2012-2018 Chronicle Map Contributors
*
* 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.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 java.util.Objects;
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.
*
*
Bytes access: {@link #bytes()} + {@link #offset()} + {@link #size()}.
*
*
Object access: {@link #get()}.
*
*
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 type of the accessed objects
*/
public interface Data {
/**
* 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());
}
/**
* Returns the accessor object to the {@code Data}'s bytes.
*
*
If this {@code Data} wraps some bytes, this method just returns a reference to that bytes.
*
*
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.
*
*
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.
*
*
Default implementation compares {@link #bytes()} of this {@code Data}, but custom
* implementation may only check if {@linkplain #get() object} of this {@code Data} would
* 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}.
*
*
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, Objects.requireNonNull(bytes()), offset(), size());
}
/**
* Returns object view of this {@code Data}.
*
*
If this {@code Data} wraps some object, this method just returns that object.
*
*
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 generally disallowed to use the
* object, returned from this method, outside 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 always allowed to use the object,
* returned from this method, outside 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);
/**
* {@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
* bytes 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' bytes 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 object {@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();
}
}
}