/*
* Copyright (c) 2013-2017 Cinchapi Inc.
*
* 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 com.cinchapi.concourse.server.model;
import java.nio.ByteBuffer;
import java.util.Comparator;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import com.cinchapi.concourse.Link;
import com.cinchapi.concourse.server.io.Byteable;
import com.cinchapi.concourse.thrift.TObject;
import com.cinchapi.concourse.thrift.Type;
import com.cinchapi.concourse.util.ByteBuffers;
import com.cinchapi.concourse.util.Convert;
import com.cinchapi.concourse.util.Numbers;
/**
* A Value is an abstraction for a {@link TObject} that records type information
* and serves as the most basic element of data in Concourse. Values are
* logically sortable using weak typing and cannot exceed 2^32 bytes.
* <p>
* <h2>Storage Requirements</h2>
* Each Value requires at least {@value #CONSTANT_SIZE} bytes of space in
* addition to the following type specific requirements:
* <ul>
* <li>BOOLEAN requires an additional 1 byte</li>
* <li>DOUBLE requires an additional 8 bytes</li>
* <li>FLOAT requires an additional 4 bytes</li>
* <li>INTEGER requires an additional 4 bytes</li>
* <li>LONG requires an additional 8 bytes</li>
* <li>LINK requires an additional 8 bytes</li>
* <li>STRING requires an additional 14 bytes for every character (uses UTF8
* encoding)</li>
* </ul>
* </p>
*
* @author Jeff Nelson
*/
@Immutable
public final class Value implements Byteable, Comparable<Value> {
/**
* Return the Value encoded in {@code bytes} so long as those bytes adhere
* to the format specified by the {@link #getBytes()} method. This method
* assumes that all the bytes in the {@code bytes} belong to the Value. In
* general, it is necessary to get the appropriate Value slice from the
* parent ByteBuffer using {@link ByteBuffers#slice(ByteBuffer, int, int)}.
*
* @param bytes
* @return the Value
*/
public static Value fromByteBuffer(ByteBuffer bytes) {
Type type = Type.values()[bytes.get()];
TObject data = extractTObjectAndCache(bytes, type);
return new Value(data, bytes);
}
/**
* Return the optimized Value of {@code value}.
*
* @param value
* @return the optimized Value
*/
public static Value optimize(Value value) {
if(value.getType() == Type.TAG) {
return Value.wrap(Convert
.javaToThrift(value.getObject().toString()));
}
return value;
}
/**
* Return a Value that is backed by {@code data}.
*
* @param data
* @return the Value
*/
public static Value wrap(TObject data) {
Object obj = data.getServerWrapper(); /* (Authorized) */
if(obj == null) {
// We cache the Value that wraps the TObject, onto the TObject
// itself to prevent unnecessary creation of additional wrappers
// throughout the Engine when TObjects and Values are converted
// back-and-forth (interface-based programming for the win, right).
// TObject is defined in the client, which doesn't have access to
// this Value class, so the #serverWrapper attribute of the TObject
// is a generic object. Thats not ideal, but this approach means
// that we only pay the penalty for a type cast (which can be JIT
// optimized) as opposed to the penalty for object creation when
// wrapping the same TObject to a Value more than once
Value value = new Value(data);
data.cacheServerWrapper(value);
return value;
}
else if(obj instanceof Value) {
return (Value) obj;
}
else {
// We should never get here because this means that someone
// deliberately cached a garbage value, which shouldn't happen once
// the TObject is re-constructed by the server.
data.cacheServerWrapper(null);
return wrap(data);
}
}
/**
* Return the {@link TObject} of {@code type} represented by {@code bytes}.
* This method reads the remaining bytes from the current position into the
* returned TObject.
*
* @param bytes
* @param type
* @return the TObject
*/
private static TObject extractTObjectAndCache(ByteBuffer bytes, Type type) {
// Must allocate a heap buffer because TObject assumes it has a
// backing array and because of THRIFT-2104 that buffer must wrap a
// byte array in order to assume that the TObject does not lose data
// when transferred over the wire.
byte[] array = new byte[bytes.remaining()];
bytes.get(array); // We CANNOT simply slice {@code buffer} and use
// the slice's backing array because the backing
// array of the slice is the same as the
// original, which contains more data than we
// need for the quantity
return new TObject(ByteBuffer.wrap(array), type);
}
/**
* Check to see if the specific {@code type} is numeric.
*
* @param type
* @return {@code true} if the type is numeric
*/
private static boolean isNumericType(Type type) {
return type == Type.DOUBLE || type == Type.FLOAT
|| type == Type.INTEGER || type == Type.LONG;
}
/**
* A constant representing the smallest possible Value. This should be used
* in normal operations, but should only be used to indicate an infinite
* range.
*/
public static Value NEGATIVE_INFINITY = Value.wrap(Convert
.javaToThrift(Long.MIN_VALUE));
/**
* A constant representing the largest possible Value. This shouldn't be
* used in normal operations, but should only be used to indicate an
* infinite range.
*/
public static Value POSITIVE_INFINITY = Value.wrap(Convert
.javaToThrift(Long.MAX_VALUE));
/**
* The minimum number of bytes needed to encode every Value.
*/
private static final int CONSTANT_SIZE = 1; // type(1)
/**
* A cached copy of the binary representation that is returned from
* {@link #getBytes()}.
*/
@Nullable
private transient ByteBuffer bytes = null;
/**
* The underlying data represented by this Value. This representation is
* used when serializing/deserializing the data for RPC or disk and network
* I/O.
*/
private final TObject data;
/**
* The java representation of the underlying {@link #data}. This
* representation is used when interacting with other components in the JVM.
*/
@Nullable
private transient Object object = null;
/**
* Construct a new instance.
*
* @param data
*/
private Value(TObject data) {
this(data, null);
}
/**
* Construct a new instance.
*
* @param data
* @param bytes
*/
private Value(TObject data, @Nullable ByteBuffer bytes) {
this.data = data;
this.bytes = bytes;
}
@Override
public int compareTo(Value other) {
return Sorter.INSTANCE.compare(this, other);
}
@Override
public void copyTo(ByteBuffer buffer) {
buffer.put((byte) data.getType().ordinal());
buffer.put(data.bufferForData());
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Value) {
final Value other = (Value) obj;
Type typeA = getType();
Type typeB = other.getType();
if(typeA != typeB && (isNumericType(typeA) && isNumericType(typeB))) {
return Numbers.isEqualTo((Number) getObject(),
(Number) other.getObject());
}
else {
return data.equals(other.data);
}
}
return false;
}
/**
* Return a byte buffer that represents this Value with the following order:
* <ol>
* <li><strong>type</strong> - position 0</li>
* <li><strong>data</strong> - position 1</li>
* </ol>
*
* @return the ByteBuffer representation
*/
@Override
public ByteBuffer getBytes() {
if(bytes == null) {
bytes = ByteBuffer.allocate(size());
copyTo(bytes);
bytes.rewind();
}
return ByteBuffers.asReadOnlyBuffer(bytes);
}
/**
* Return the java object that is represented by this Value.
*
* @return the object representation
*/
public Object getObject() {
if(object == null) {
object = Convert.thriftToJava(data);
}
return object;
}
/**
* Return the TObject that is represented by this Value.
*
* @return the TObject representation
*/
public TObject getTObject() {
return data;
}
/**
* Return the {@link Type} that describes the underlying data represented by
* this Value.
*
* @return the type
*/
public Type getType() {
return data.getType();
}
@Override
public int hashCode() {
return data.hashCode();
}
/**
* Return {@code true} if the value {@link #getType() type} is numeric.
*
* @return {@code true} if the value type is numeric
*/
public boolean isNumericType() {
return isNumericType(getType());
}
@Override
public int size() {
return CONSTANT_SIZE + data.data.capacity();
}
@Override
public String toString() {
return getObject().toString() + " (" + getType() + ")";
}
/**
* A {@link Comparator} that is used to sort Values using weak typing.
*
* @author Jeff Nelson
*/
public static enum Sorter implements Comparator<Value> {
INSTANCE;
@Override
public int compare(Value v1, Value v2) {
if((v1 == POSITIVE_INFINITY && v2 == POSITIVE_INFINITY)
|| (v1 == NEGATIVE_INFINITY && v2 == NEGATIVE_INFINITY)) {
return 0;
}
else if(v1 == POSITIVE_INFINITY) {
return 1;
}
else if(v2 == POSITIVE_INFINITY) {
return -1;
}
else if(v1 == NEGATIVE_INFINITY) {
return -1;
}
else if(v2 == NEGATIVE_INFINITY) {
return 1;
}
else {
Object o1 = v1.getObject();
Object o2 = v2.getObject();
if(o1 instanceof Number
&& o2 instanceof Number
&& ((!(o1 instanceof Link) && !(o2 instanceof Link)) || (o1 instanceof Link && o2 instanceof Link))) {
return Numbers.compare((Number) o1, (Number) o2);
}
else if(o1 instanceof Number) {
return -1;
}
else if(o2 instanceof Number) {
return 1;
}
else {
return o1.toString().compareToIgnoreCase(o2.toString());
}
}
}
}
}