@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) { 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()); } } }
@Override public boolean equals(Object obj) { if (obj instanceof Value) { final Value other = (Value) obj; return getObject().equals(other.getObject()); } return false; }
/** * 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; }
/** * 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>DOUBLE requires an additional 8 bytes * <li>FLOAT requires an additional 4 bytes * <li>INTEGER requires an additional 4 bytes * <li>LONG requires an additional 8 bytes * <li>LINK requires an additional 8 bytes * <li>STRING requires an additional 14 bytes for every character (uses UTF8 encoding) * </ul> * * @author jnelson */ @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) { return new Value(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); } /** * 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 boolean equals(Object obj) { if (obj instanceof Value) { final Value other = (Value) obj; return getObject().equals(other.getObject()); } return false; } /** * Return a byte buffer that represents this Value with the following order: * * <ol> * <li><strong>type</strong> - position 0 * <li><strong>data</strong> - position 1 * </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 getObject().hashCode(); } @Override public int size() { return CONSTANT_SIZE + data.data.capacity(); } @Override public String toString() { return getObject().toString() + " (" + getType() + ")"; } @Override public void copyTo(ByteBuffer buffer) { buffer.put((byte) data.getType().ordinal()); buffer.put(data.bufferForData()); } /** * A {@link Comparator} that is used to sort Values using weak typing. * * @author jnelson */ 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) { 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()); } } } } }