/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.tuple;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import com.foundationdb.Range;
/**
* Represents a set of elements that make up a sortable, typed key. This object
* is comparable with other {@code Tuple}s and will sort in Java in
* the same order in which they would sort in FoundationDB. {@code Tuple}s sort
* first by the first element, then by the second, etc. This makes the tuple layer
* ideal for building a variety of higher-level data models.<br>
* <h3>Types</h3>
* A {@code Tuple} can
* contain byte arrays ({@code byte[]}), {@link String}s, {@link Number}s, and {@code null}.
* Note that for numbers outside this range the way that Java
* truncates integral values may yield unexpected results.<br>
* <h3>{@code null} values</h3>
* The FoundationDB tuple specification has a special type-code for {@code None}; {@code nil}; or,
* as Java would understand it, {@code null}.
* The behavior of the layer in the presence of {@code null} varies by type with the intention
* of matching expected behavior in Java. {@code byte[]} and {@link String}s can be {@code null},
* where integral numbers (i.e. {@code long}s) cannot.
* This means that the typed getters ({@link #getBytes(int) getBytes()} and {@link #getString(int) getString()})
* will return {@code null} if the entry at that location was {@code null} and the typed adds
* ({@link #add(byte[])} and {@link #add(String)}) will accept {@code null}. The
* {@link #getLong(int) typed get for integers}, however, will throw a {@code NullPointerException} if
* the entry in the {@code Tuple} was {@code null} at that position.<br>
* <br>
* This class is not thread safe.
*/
public class Tuple2 extends Tuple {
private List<Object> elements;
private Tuple2(List<? extends Object> elements, Object newItem) {
this(new LinkedList<Object>(elements));
this.elements.add(newItem);
}
private Tuple2(List<? extends Object> elements) {
this.elements = new ArrayList<Object>(elements);
}
/**
* Creates a copy of this {@code Tuple} with an appended last element. The parameter
* is untyped but only {@link String}, {@code byte[]}, {@link Number}s, and {@code null} are allowed.
* All {@code Number}s are converted to a 8 byte integral value, so all floating point
* information is lost.
*
* @param o the object to append. Must be {@link String}, {@code byte[]},
* {@link Number}s, or {@code null}.
*
* @return a newly created {@code Tuple}
*/
public Tuple2 addObject(Object o) {
if(o != null &&
!(o instanceof Boolean) &&
!(o instanceof String) &&
!(o instanceof byte[]) &&
!(o instanceof Number) &&
!(o instanceof UUID)) {
throw new IllegalArgumentException("Parameter type (" + o.getClass().getName() + ") not recognized");
}
return new Tuple2(this.elements, o);
}
/**
* Creates a copy of this {@code Tuple} with a {@code String} appended as the last element.
*
* @param s the {@code String} to append
*
* @return a newly created {@code Tuple}
*/
public Tuple2 add(String s) {
return new Tuple2(this.elements, s);
}
/**
* Creates a copy of this {@code Tuple} with a {@code long} appended as the last element.
*
* @param l the number to append
*
* @return a newly created {@code Tuple}
*/
public Tuple2 add(long l) {
return new Tuple2(this.elements, l);
}
/**
* Creates a copy of this {@code Tuple} with a {@code byte} array appended as the last element.
*
* @param b the {@code byte}s to append
*
* @return a newly created {@code Tuple}
*/
public Tuple2 add(byte[] b) {
return new Tuple2(this.elements, b);
}
public Tuple2 add(Double b) {
return new Tuple2(this.elements, b);
}
public Tuple2 add(UUID u) {
return new Tuple2(this.elements, u);
}
public Tuple2 add(Float b) {
return new Tuple2(this.elements, b);
}
public Tuple2 add(BigDecimal b) {
return new Tuple2(this.elements, b);
}
public Tuple2 add(BigInteger b) {
return new Tuple2(this.elements, b);
}
public Tuple2 add(Boolean b) {
return new Tuple2(this.elements, b);
}
/**
* Creates a copy of this {@code Tuple} with a {@code byte} array appended as the last element.
*
* @param b the {@code byte}s to append
* @param offset the starting index of {@code b} to add
* @param length the number of elements of {@code b} to copy into this {@code Tuple}
*
* @return a newly created {@code Tuple}
*/
public Tuple2 add(byte[] b, int offset, int length) {
return new Tuple2(this.elements, Arrays.copyOfRange(b, offset, offset + length));
}
/**
* Create a copy of this {@code Tuple} with a list of items appended.
*
* @param o the list of objects to append. Elements must be {@link String}, {@code byte[]},
* {@link Number}s, or {@code null}.
*
* @return a newly created {@code Tuple}
*/
public Tuple2 addAll(List<? extends Object> o) {
List<Object> merged = new ArrayList<Object>(o.size() + this.elements.size());
merged.addAll(this.elements);
merged.addAll(o);
return new Tuple2(merged);
}
/**
* Create a copy of this {@code Tuple} with all elements from anther {@code Tuple} appended.
*
* @param other the {@code Tuple} whose elements should be appended
*
* @return a newly created {@code Tuple}
*/
public Tuple2 addAll(Tuple2 other) {
List<Object> merged = new ArrayList<Object>(this.size() + other.size());
merged.addAll(this.elements);
merged.addAll(other.peekItems());
return new Tuple2(merged);
}
/**
* Get an encoded representation of this {@code Tuple}. Each element is encoded to
* {@code byte}s and concatenated.
*
* @return a serialized representation of this {@code Tuple}.
*/
@Override
public byte[] pack() {
return TupleFloatingUtil.pack(this.elements);
}
/**
* Gets the unserialized contents of this {@code Tuple}.
*
* @return the elements that make up this {@code Tuple}.
*/
public List<Object> getItems() {
return new ArrayList<Object>(elements);
}
/**
* Returns the internal elements that make up this tuple. For internal use only, as
* modifications to the result will mean that this Tuple is modified.
*
* @return the elements of this Tuple, without copying
*/
private List<Object> peekItems() {
return this.elements;
}
/**
* Gets an {@code Iterator} over the {@code Objects} in this {@code Tuple}. This {@code Iterator} is
* unmodifiable and will throw an exception if {@link Iterator#remove() remove()} is called.
*
* @return an unmodifiable {@code Iterator} over the elements in the {@code Tuple}.
*/
@Override
public Iterator<Object> iterator() {
return Collections.unmodifiableList(this.elements).iterator();
}
/**
* Construct a new empty {@code Tuple}. After creation, items can be added
* with calls the the variations of {@code add()}.
*
* @see #from(Object...)
* @see #fromBytes(byte[])
* @see #fromItems(Iterable)
*/
public Tuple2() {
this.elements = new LinkedList<Object>();
}
/**
* Construct a new {@code Tuple} with elements decoded from a supplied {@code byte} array.
*
* @param bytes encoded {@code Tuple} source. Must not be {@code null}
*
* @return a newly constructed object.
*/
public static Tuple2 fromBytes(byte[] bytes) {
return fromBytes(bytes, 0, bytes.length);
}
/**
* Construct a new {@code Tuple} with elements decoded from a supplied {@code byte} array.
*
* @param bytes encoded {@code Tuple} source. Must not be {@code null}
*
* @return a newly constructed object.
*/
public static Tuple2 fromBytes(byte[] bytes, int offset, int length) {
Tuple2 t = new Tuple2();
t.elements = TupleFloatingUtil.unpack(bytes, offset, length);
return t;
}
/**
* Gets the number of elements in this {@code Tuple}.
*
* @return the count of elements
*/
public int size() {
return this.elements.size();
}
/**
* Determine if this {@code Tuple} contains no elements.
*
* @return {@code true} if this {@code Tuple} contains no elements, {@code false} otherwise
*/
public boolean isEmpty() {
return this.elements.isEmpty();
}
/**
* Gets an indexed item as a {@code long}. This function will not do type conversion
* and so will throw a {@code ClassCastException} if the element is not a number type.
* The element at the index may not be {@code null}.
*
* @param index the location of the item to return
*
* @return the item at {@code index} as a {@code long}
*/
public long getLong(int index) {
Object o = this.elements.get(index);
if(o == null)
throw new NullPointerException("Number types in Tuples may not be null");
return (Long)o;
}
/**
* Gets an indexed item as a {@code byte[]}. This function will not do type conversion
* and so will throw a {@code ClassCastException} if the tuple element is not a
* {@code byte} array.
*
* @param index the location of the element to return
*
* @return the item at {@code index} as a {@code byte[]}
*/
public byte[] getBytes(int index) {
Object o = this.elements.get(index);
// Check needed, since the null may be of type "Object" and may not be casted to byte[]
if(o == null)
return null;
return (byte[])o;
}
/**
* Gets an indexed item as a {@code String}. This function will not do type conversion
* and so will throw a {@code ClassCastException} if the tuple element is not of
* {@code String} type.
*
* @param index the location of the element to return
*
* @return the item at {@code index} as a {@code String}
*/
public String getString(int index) {
Object o = this.elements.get(index);
// Check needed, since the null may be of type "Object" and may not be casted to byte[]
if(o == null) {
return null;
}
return (String)o;
}
/**
* Gets an indexed item without forcing a type.
*
* @param index the index of the item to return
*
* @return an item from the list, without forcing type conversion
*/
public Object get(int index) {
return this.elements.get(index);
}
/**
* Creates a new {@code Tuple} with the first item of this {@code Tuple} removed.
*
* @return a newly created {@code Tuple}
*/
public Tuple2 popFront() {
if(elements.size() == 0)
throw new IllegalStateException("Tuple contains no elements");
List<Object> items = new ArrayList<Object>(elements.size() - 1);
for(int i = 1; i < this.elements.size(); i++) {
items.add(this.elements.get(i));
}
return new Tuple2(items);
}
/**
* Creates a new {@code Tuple} with the last item of this {@code Tuple} removed.
*
* @return a newly created {@code Tuple}
*/
public Tuple2 popBack() {
if(elements.size() == 0)
throw new IllegalStateException("Tuple contains no elements");
List<Object> items = new ArrayList<Object>(elements.size() - 1);
for(int i = 0; i < this.elements.size() - 1; i++) {
items.add(this.elements.get(i));
}
return new Tuple2(items);
}
/**
* Returns a range representing all keys that encode {@code Tuple}s strictly starting
* with this {@code Tuple}.
* <br>
* <br>
* For example:
* <pre>
* Tuple t = Tuple.from("a", "b");
* Range r = t.range();</pre>
* {@code r} includes all tuples ("a", "b", ...)
*
* @return the keyspace range containing all {@code Tuple}s that have this {@code Tuple}
* as a prefix.
*/
public Range range() {
byte[] p = pack();
//System.out.println("Packed tuple is: " + ByteArrayUtil.printable(p));
return new Range(ByteArrayUtil.join(p, new byte[] {0x0}),
ByteArrayUtil.join(p, new byte[] {(byte)0xff}));
}
/**
* Compare the byte-array representation of this {@code Tuple} against another. This method
* will sort {@code Tuple}s in the same order that they would be sorted as keys in
* FoundationDB. Returns a negative integer, zero, or a positive integer when this object's
* byte-array representation is found to be less than, equal to, or greater than the
* specified {@code Tuple}.
*
* @param t the {@code Tuple} against which to compare
*
* @return a negative integer, zero, or a positive integer when this {@code Tuple} is
* less than, equal, or greater than the parameter {@code t}.
*/
public int compareTo(Tuple2 t) {
return ByteArrayUtil.compareUnsigned(this.pack(), t.pack());
}
/**
* Returns a hash code value for this {@code Tuple}.
* {@inheritDoc}
*
* @return a hashcode
*/
@Override
public int hashCode() {
return Arrays.hashCode(this.pack());
}
/**
* Tests for equality with another {@code Tuple}. If the passed object is not a {@code Tuple}
* this returns false. If the object is a {@code Tuple}, this returns true if
* {@link Tuple2#compareTo(Tuple2) compareTo()} would return {@code 0}.
*
* @return {@code true} if {@code obj} is a {@code Tuple} and their binary representation
* is identical.
*/
@Override
public boolean equals(Object o) {
if(o == null)
return false;
if(o instanceof Tuple2) {
return Arrays.equals(this.pack(), ((Tuple2) o).pack());
}
return false;
}
/**
* Creates a new {@code Tuple} from a variable number of elements. The elements
* must follow the type guidelines from {@link Tuple2#addObject(Object) add}, and so
* can only be {@link String}s, {@code byte[]}s, {@link Number}s, or {@code null}s.
*
* @param items the elements from which to create the {@code Tuple}.
*
* @return a newly created {@code Tuple}
*/
public static Tuple2 fromItems(Iterable<? extends Object> items) {
Tuple2 t = new Tuple2();
for(Object o : items) {
t = t.addObject(o);
}
return t;
}
/**
* Efficiently creates a new {@code Tuple} from a list of objects. The elements
* must follow the type guidelines from {@link Tuple2#addObject(Object) add}, and so
* can only be {@link String}s, {@code byte[]}s, {@link Number}s, or {@code null}s.
*
* @param items the elements from which to create the {@code Tuple}.
*
* @return a newly created {@code Tuple}
*/
public static Tuple2 fromList(List<? extends Object> items) {
return new Tuple2(items);
}
/**
* Creates a new {@code Tuple} from a variable number of elements. The elements
* must follow the type guidelines from {@link Tuple2#addObject(Object) add}, and so
* can only be {@link String}s, {@code byte[]}s, {@link Number}s, or {@code null}s.
*
* @param items the elements from which to create the {@code Tuple}.
*
* @return a newly created {@code Tuple}
*/
public static Tuple2 from(Object ... items) {
return fromList(Arrays.asList(items));
}
}