Skip to content

Instantly share code, notes, and snippets.

@k-mack
Forked from agaoglu/MockHTable.java
Last active December 11, 2015 12:19
Show Gist options
  • Select an option

  • Save k-mack/4600133 to your computer and use it in GitHub Desktop.

Select an option

Save k-mack/4600133 to your computer and use it in GitHub Desktop.

Revisions

  1. k-mack revised this gist Jan 23, 2013. 1 changed file with 54 additions and 39 deletions.
    93 changes: 54 additions & 39 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -251,24 +251,7 @@ public Result get(Get get) throws IOException {
    }
    Filter filter = get.getFilter();
    if (filter != null) {
    filter.reset();
    List<KeyValue> nkvs = new ArrayList<KeyValue>(kvs.size());
    for (KeyValue kv : kvs) {
    if (filter.filterAllRemaining()) {
    break;
    }
    if (filter.filterRowKey(kv.getBuffer(), kv.getRowOffset(), kv.getRowLength())) {
    continue;
    }
    if (filter.filterKeyValue(kv) == ReturnCode.INCLUDE) {
    nkvs.add(kv);
    }
    // ignoring next key hint which is a optimization to reduce file system IO
    }
    if (filter.hasFilterRow()) {
    filter.filterRow(nkvs);
    }
    kvs = nkvs;
    kvs = filter(filter, kvs);
    }

    return new Result(kvs);
    @@ -332,27 +315,10 @@ public ResultScanner getScanner(Scan scan) throws IOException {
    }
    }
    if (filter != null) {
    filter.reset();
    List<KeyValue> nkvs = new ArrayList<KeyValue>(kvs.size());
    for (KeyValue kv : kvs) {
    if (filter.filterAllRemaining()) {
    break;
    }
    if (filter.filterRowKey(kv.getBuffer(), kv.getRowOffset(), kv.getRowLength())) {
    continue;
    }
    ReturnCode filterResult = filter.filterKeyValue(kv);
    if (filterResult == ReturnCode.INCLUDE) {
    nkvs.add(kv);
    } else if (filterResult == ReturnCode.NEXT_ROW) {
    break;
    }
    // ignoring next key hint which is a optimization to reduce file system IO
    }
    if (filter.hasFilterRow()) {
    filter.filterRow(nkvs);
    }
    kvs = nkvs;
    kvs = filter(filter, kvs);
    // Check for early out optimization
    if (filter.filterAllRemaining()) {
    break;
    }
    if (!kvs.isEmpty()) {
    ret.add(new Result(kvs));
    @@ -386,6 +352,55 @@ public Result next() throws IOException {
    public void close() {}
    };
    }

    /**
    * Follows the logical flow through the filter methods for a single row.
    * @param filter HBase filter.
    * @param kvs List of a row's KeyValues
    * @return List of KeyValues that were not filtered.
    */
    private List<KeyValue> filter(Filter filter, List<KeyValue> kvs) {
    filter.reset();

    List<KeyValue> tmp = new ArrayList<KeyValue>(kvs.size());
    tmp.addAll(kvs);

    /*
    * Note. Filter flow for a single row. Adapted from
    * "HBase: The Definitive Guide" (p. 163) by Lars George, 2011.
    * See Figure 4-2 on p. 163.
    */
    boolean filteredOnRowKey = false;
    List<KeyValue> nkvs = new ArrayList<KeyValue>(tmp.size());
    for (KeyValue kv : tmp) {
    if (filter.filterRowKey(kv.getBuffer(), kv.getRowOffset(), kv.getRowLength())) {
    filteredOnRowKey = true;
    break;
    }
    ReturnCode filterResult = filter.filterKeyValue(kv);
    if (filterResult == ReturnCode.INCLUDE) {
    nkvs.add(kv);
    }
    else if (filterResult == ReturnCode.NEXT_ROW) {
    break;
    }
    else if (filterResult == ReturnCode.NEXT_COL || filterResult == ReturnCode.SKIP) {
    continue;
    }
    /*
    * Ignoring next key hint which is a optimization to reduce file
    * system IO
    */
    }
    if (filter.hasFilterRow() && !filteredOnRowKey) {
    filter.filterRow(nkvs);
    }
    if (filter.filterRow() || filteredOnRowKey) {
    nkvs.clear();
    }
    tmp = nkvs;
    return tmp;
    }

    @Override
    public ResultScanner getScanner(byte[] family) throws IOException {
  2. @agaoglu agaoglu revised this gist Jun 19, 2012. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -315,6 +315,8 @@ public ResultScanner getScanner(Scan scan) throws IOException {
    if (qualifiers == null || qualifiers.isEmpty())
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers){
    if (data.get(row).get(family).get(qualifier) == null)
    continue;
    for (Long timestamp : data.get(row).get(family).get(qualifier).descendingKeySet()){
    if (timestamp < scan.getTimeRange().getMin())
    continue;
  3. @rodrigopr rodrigopr revised this gist May 26, 2012. 1 changed file with 34 additions and 6 deletions.
    40 changes: 34 additions & 6 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -200,12 +200,31 @@ private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], Naviga

    @Override
    public boolean exists(Get get) throws IOException {
    return data.containsKey(get.getRow());
    if(get.getFamilyMap() == null || get.getFamilyMap().size() == 0) {
    return data.containsKey(get.getRow());
    } else {
    byte[] row = get.getRow();
    if(!data.containsKey(row)) {
    return false;
    }
    for(byte[] family : get.getFamilyMap().keySet()) {
    if(!data.get(row).containsKey(family)) {
    return false;
    } else {
    for(byte[] qualifier : get.getFamilyMap().get(family)) {
    if(!data.get(row).get(family).containsKey(qualifier)) {
    return false;
    }
    }
    }
    }
    return true;
    }
    }

    @Override
    public Result get(Get get) throws IOException {
    if (!exists(get))
    if (!data.containsKey(get.getRow()))
    return new Result();
    byte[] row = get.getRow();
    List<KeyValue> kvs = new ArrayList<KeyValue>();
    @@ -273,14 +292,14 @@ public ResultScanner getScanner(Scan scan) throws IOException {
    // stopRow (exclusive) is the same, it should not be excluded which would
    // happen w/o this control.
    if (st != null && st.length > 0 &&
    Bytes.toString(st).compareTo(Bytes.toString(row)) != 0) {
    Bytes.BYTES_COMPARATOR.compare(st, row) != 0) {
    // if row is before startRow do not emit, pass to next row
    if (st != null && st.length > 0 &&
    Bytes.toString(st).compareTo(Bytes.toString(row)) > 0)
    Bytes.BYTES_COMPARATOR.compare(st, row) > 0)
    continue;
    // if row is equal to stopRow or after it do not emit, stop iteration
    if (sp != null && sp.length > 0 &&
    Bytes.toString(sp).compareTo(Bytes.toString(row)) <= 0)
    Bytes.BYTES_COMPARATOR.compare(sp, row) <= 0)
    break;
    }

    @@ -296,13 +315,16 @@ public ResultScanner getScanner(Scan scan) throws IOException {
    if (qualifiers == null || qualifiers.isEmpty())
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers){
    for (Long timestamp : data.get(row).get(family).get(qualifier).keySet()){
    for (Long timestamp : data.get(row).get(family).get(qualifier).descendingKeySet()){
    if (timestamp < scan.getTimeRange().getMin())
    continue;
    if (timestamp > scan.getTimeRange().getMax())
    continue;
    byte[] value = data.get(row).get(family).get(qualifier).get(timestamp);
    kvs.add(new KeyValue(row, family, qualifier, timestamp, value));
    if(kvs.size() == scan.getMaxVersions()) {
    break;
    }
    }
    }
    }
    @@ -477,6 +499,12 @@ public void delete(Delete delete) throws IOException {
    for (KeyValue kv : delete.getFamilyMap().get(family)){
    data.get(row).get(kv.getFamily()).remove(kv.getQualifier());
    }
    if(data.get(row).get(family).isEmpty()) {
    data.get(row).remove(family);
    }
    }
    if(data.get(row).isEmpty()) {
    data.remove(row);
    }
    }

  4. @agaoglu agaoglu revised this gist Feb 8, 2012. 1 changed file with 17 additions and 9 deletions.
    26 changes: 17 additions & 9 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -128,21 +128,23 @@ public class MockHTable implements HTableInterface {

    /**
    * Helper method to convert some data into a list of KeyValue's
    *
    *
    * @param row
    * row value of the KeyValue's
    * @param rowdata
    * data to decode
    * @param maxVersions
    * number of versions to return
    * @return List of KeyValue's
    */
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata){
    return toKeyValue(row, rowdata, 0, Long.MAX_VALUE);
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata, int maxVersions){
    return toKeyValue(row, rowdata, 0, Long.MAX_VALUE, maxVersions);
    }

    /**
    * Helper method to convert some data into a list of KeyValue's with timestamp
    * constraint
    *
    *
    * @param row
    * row value of the KeyValue's
    * @param rowdata
    @@ -151,13 +153,18 @@ private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], Naviga
    * start of the timestamp constraint
    * @param timestampEnd
    * end of the timestamp constraint
    * @param maxVersions
    * number of versions to return
    * @return List of KeyValue's
    */
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata, long timestampStart, long timestampEnd){
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata, long timestampStart, long timestampEnd, int maxVersions){
    List<KeyValue> ret = new ArrayList<KeyValue>();
    for (byte[] family : rowdata.keySet())
    for (byte[] qualifier : rowdata.get(family).keySet())
    for (Entry<Long, byte[]> tsToVal : rowdata.get(family).get(qualifier).entrySet()){
    for (byte[] qualifier : rowdata.get(family).keySet()) {
    int versionsAdded = 0;
    for (Entry<Long, byte[]> tsToVal : rowdata.get(family).get(qualifier).descendingMap().entrySet()){
    if (versionsAdded++ == maxVersions)
    break;
    Long timestamp = tsToVal.getKey();
    if (timestamp < timestampStart)
    continue;
    @@ -166,6 +173,7 @@ private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], Naviga
    byte[] value = tsToVal.getValue();
    ret.add(new KeyValue(row, family, qualifier, timestamp, value));
    }
    }
    return ret;
    }

    @@ -202,7 +210,7 @@ public Result get(Get get) throws IOException {
    byte[] row = get.getRow();
    List<KeyValue> kvs = new ArrayList<KeyValue>();
    if (!get.hasFamilies()) {
    kvs = toKeyValue(row, data.get(row));
    kvs = toKeyValue(row, data.get(row), get.getMaxVersions());
    } else {
    for (byte[] family : get.getFamilyMap().keySet()){
    if (data.get(row).get(family) == null)
    @@ -278,7 +286,7 @@ public ResultScanner getScanner(Scan scan) throws IOException {

    List<KeyValue> kvs = null;
    if (!scan.hasFamilies()) {
    kvs = toKeyValue(row, data.get(row), scan.getTimeRange().getMin(), scan.getTimeRange().getMax());
    kvs = toKeyValue(row, data.get(row), scan.getTimeRange().getMin(), scan.getTimeRange().getMax(), scan.getMaxVersions());
    } else {
    kvs = new ArrayList<KeyValue>();
    for (byte[] family : scan.getFamilyMap().keySet()){
  5. @agaoglu agaoglu revised this gist Jan 21, 2012. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -208,7 +208,7 @@ public Result get(Get get) throws IOException {
    if (data.get(row).get(family) == null)
    continue;
    NavigableSet<byte[]> qualifiers = get.getFamilyMap().get(family);
    if (qualifiers == null)
    if (qualifiers == null || qualifiers.isEmpty())
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers){
    if (qualifier == null)
    @@ -285,7 +285,7 @@ public ResultScanner getScanner(Scan scan) throws IOException {
    if (data.get(row).get(family) == null)
    continue;
    NavigableSet<byte[]> qualifiers = scan.getFamilyMap().get(family);
    if (qualifiers == null)
    if (qualifiers == null || qualifiers.isEmpty())
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers){
    for (Long timestamp : data.get(row).get(family).get(qualifier).keySet()){
    @@ -718,4 +718,4 @@ public static String toEString(long val){
    public static String toEString(short val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    }
    }
  6. @agaoglu agaoglu revised this gist Aug 25, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -346,7 +346,7 @@ public Result[] next(int nbRows) throws IOException {
    }
    public Result next() throws IOException {
    try {
    return iterator().next();
    return iterator().next();
    } catch (NoSuchElementException e) {
    return null;
    }
  7. @agaoglu agaoglu revised this gist Aug 25, 2011. 1 changed file with 13 additions and 2 deletions.
    15 changes: 13 additions & 2 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -19,6 +19,7 @@
    import java.util.Map.Entry;
    import java.util.NavigableMap;
    import java.util.NavigableSet;
    import java.util.NoSuchElementException;
    import java.util.TreeMap;

    import org.apache.hadoop.conf.Configuration;
    @@ -263,11 +264,17 @@ public ResultScanner getScanner(Scan scan) throws IOException {
    // if row is equal to startRow emit it. When startRow (inclusive) and
    // stopRow (exclusive) is the same, it should not be excluded which would
    // happen w/o this control.
    if (st != null && st.length > 0 && Bytes.compareTo(st, row) > 0)
    if (st != null && st.length > 0 &&
    Bytes.toString(st).compareTo(Bytes.toString(row)) != 0) {
    // if row is before startRow do not emit, pass to next row
    if (st != null && st.length > 0 &&
    Bytes.toString(st).compareTo(Bytes.toString(row)) > 0)
    continue;
    // if row is equal to stopRow or after it do not emit, stop iteration
    if (sp != null && sp.length > 0 && Bytes.compareTo(sp, row) <= 0)
    if (sp != null && sp.length > 0 &&
    Bytes.toString(sp).compareTo(Bytes.toString(row)) <= 0)
    break;
    }

    List<KeyValue> kvs = null;
    if (!scan.hasFamilies()) {
    @@ -338,7 +345,11 @@ public Result[] next(int nbRows) throws IOException {
    return resultSets.toArray(new Result[resultSets.size()]);
    }
    public Result next() throws IOException {
    try {
    return iterator().next();
    } catch (NoSuchElementException e) {
    return null;
    }
    }
    public void close() {}
    };
  8. @gensth gensth revised this gist Aug 24, 2011. 1 changed file with 602 additions and 600 deletions.
    1,202 changes: 602 additions & 600 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -48,13 +48,13 @@
    *
    * <pre>
    * public class MyDAO {
    * private HTableInterface table;
    * private HTableInterface table;
    *
    * public MyDAO(HTableInterface table) {
    * this.table = table;
    * }
    * public MyDAO(HTableInterface table) {
    * this.table = table;
    * }
    *
    * public void saveData(byte[] id, byte[] data) throws IOException{
    * public void saveData(byte[] id, byte[] data) throws IOException{
    * Put put = new Put(id)
    * put.add(family, qualifier, data);
    * table.put(put);
    @@ -73,11 +73,11 @@
    * <pre>
    * &#064;Test
    * public void testSave() {
    * MockHTable table = MockHTable.create();
    * MyDAO(table).saveData(id, data);
    * Get get = new Get(id);
    * Result result = table.get(get);
    * assertArrayEquals(data, result.getValue(family, qualifier));
    * MockHTable table = MockHTable.create();
    * MyDAO(table).saveData(id, data);
    * Get get = new Get(id);
    * Result result = table.get(get);
    * assertArrayEquals(data, result.getValue(family, qualifier));
    * }
    * </pre>
    * <p>
    @@ -104,605 +104,607 @@
    * <p>
    * In order to simplify assertions for tests that should put anything into
    * database, MockHTable.read() works with two parameters (id and column) and
    * returns anything written to that row/column. So, previous test can be reduced
    * to
    * returns anything written to that row/column. So, previous test can be reduced to
    *
    * <pre>
    * &#064;Test
    * public void testSave() {
    * MockHTable table = MockHTable.create();
    * MyDAO(table).saveData(id, data);
    * assertArrayEquals(data, table.read(id, &quot;family:qualifier&quot;));
    * MockHTable table = MockHTable.create();
    * MyDAO(table).saveData(id, data);
    * assertArrayEquals(data, table.read(id, "family:qualifier"));
    * }
    * </pre>
    * <p>
    * TODO: Don't know if timestamps work correctly
    *
    * @author erdem
    *
    */
    public class MockHTable implements HTableInterface {
    /**
    * This is all the data for a MockHTable instance
    */
    private NavigableMap<byte[], NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>> data = new TreeMap<byte[], NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>>(Bytes.BYTES_COMPARATOR);

    /**
    * Helper method to convert some data into a list of KeyValue's
    *
    * @param row
    * row value of the KeyValue's
    * @param rowdata
    * data to decode
    * @return List of KeyValue's
    */
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata) {
    return toKeyValue(row, rowdata, 0, Long.MAX_VALUE);
    }

    /**
    * Helper method to convert some data into a list of KeyValue's with
    * timestamp constraint
    *
    * @param row
    * row value of the KeyValue's
    * @param rowdata
    * data to decode
    * @param timestampStart
    * start of the timestamp constraint
    * @param timestampEnd
    * end of the timestamp constraint
    * @return List of KeyValue's
    */
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata, long timestampStart, long timestampEnd) {
    List<KeyValue> ret = new ArrayList<KeyValue>();
    for (byte[] family : rowdata.keySet())
    for (byte[] qualifier : rowdata.get(family).keySet())
    for (Long timestamp : rowdata.get(family).get(qualifier).keySet()) {
    if (timestamp < timestampStart)
    continue;
    if (timestamp > timestampEnd)
    continue;
    byte[] value = rowdata.get(family).get(qualifier).get(timestamp);
    ret.add(new KeyValue(row, family, qualifier, timestamp, value));
    }
    return ret;
    }

    /**
    * Clients should not rely on table names so this returns null.
    *
    * @return null
    */
    @Override
    public byte[] getTableName() {
    return null;
    }

    /**
    * No configuration needed to work so this returns null.
    *
    * @return null
    */
    @Override
    public Configuration getConfiguration() {
    return null;
    }

    /**
    * No table descriptor needed so this returns null.
    *
    * @return null
    */
    @Override
    public HTableDescriptor getTableDescriptor() {
    return null;
    }

    @Override
    public boolean exists(Get get) throws IOException {
    return data.containsKey(get.getRow());
    }

    @Override
    public Result get(Get get) throws IOException {
    if (!exists(get))
    return new Result();
    byte[] row = get.getRow();
    List<KeyValue> kvs = new ArrayList<KeyValue>();
    if (!get.hasFamilies()) {
    kvs = toKeyValue(row, data.get(row));
    } else {
    for (byte[] family : get.getFamilyMap().keySet()) {
    if (data.get(row).get(family) == null)
    continue;
    NavigableSet<byte[]> qualifiers = get.getFamilyMap().get(family);
    if (qualifiers == null)
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers) {
    if (qualifier == null)
    qualifier = "".getBytes();
    if (!data.get(row).containsKey(family) || !data.get(row).get(family).containsKey(qualifier) || data.get(row).get(family).get(qualifier).isEmpty())
    continue;
    Entry<Long, byte[]> timestampAndValue = data.get(row).get(family).get(qualifier).lastEntry();
    kvs.add(new KeyValue(row, family, qualifier, timestampAndValue.getKey(), timestampAndValue.getValue()));
    }
    }
    }
    Filter filter = get.getFilter();
    if (filter != null) {
    filter.reset();
    List<KeyValue> nkvs = new ArrayList<KeyValue>(kvs.size());
    for (KeyValue kv : kvs) {
    if (filter.filterAllRemaining()) {
    break;
    }
    if (filter.filterRowKey(kv.getBuffer(), kv.getRowOffset(), kv.getRowLength())) {
    continue;
    }
    if (filter.filterKeyValue(kv) == ReturnCode.INCLUDE) {
    nkvs.add(kv);
    }
    // ignoring next key hint which is a optimization to reduce file system IO
    }
    if (filter.hasFilterRow()) {
    filter.filterRow(nkvs);
    }
    kvs = nkvs;
    }

    return new Result(kvs);
    }

    @Override
    public Result getRowOrBefore(byte[] row, byte[] family) throws IOException {
    // FIXME: implement
    return null;
    }

    @Override
    public ResultScanner getScanner(Scan scan) throws IOException {
    final List<Result> ret = new ArrayList<Result>();
    byte[] st = scan.getStartRow();
    byte[] sp = scan.getStopRow();
    Filter filter = scan.getFilter();

    for (byte[] row : data.keySet()) {
    // if row is equal to startRow emit it. When startRow (inclusive) and
    // stopRow (exclusive) is the same, it should not be excluded which would
    // happen w/o this control.
    if (st != null && st.length > 0 && Bytes.compareTo(st, row) > 0)
    continue;
    // if row is equal to stopRow or after it do not emit, stop iteration
    if (sp != null && sp.length > 0 && Bytes.compareTo(sp, row) <= 0)
    break;

    List<KeyValue> kvs = null;
    if (!scan.hasFamilies()) {
    kvs = toKeyValue(row, data.get(row), scan.getTimeRange().getMin(), scan.getTimeRange().getMax());
    } else {
    kvs = new ArrayList<KeyValue>();
    for (byte[] family : scan.getFamilyMap().keySet()) {
    if (data.get(row).get(family) == null)
    continue;
    NavigableSet<byte[]> qualifiers = scan.getFamilyMap().get(family);
    if (qualifiers == null)
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers) {
    for (Long timestamp : data.get(row).get(family).get(qualifier).keySet()) {
    if (timestamp < scan.getTimeRange().getMin())
    continue;
    if (timestamp > scan.getTimeRange().getMax())
    continue;
    byte[] value = data.get(row).get(family).get(qualifier).get(timestamp);
    kvs.add(new KeyValue(row, family, qualifier, timestamp, value));
    }
    }
    }
    }
    if (filter != null) {
    filter.reset();
    List<KeyValue> nkvs = new ArrayList<KeyValue>(kvs.size());
    for (KeyValue kv : kvs) {
    if (filter.filterAllRemaining()) {
    break;
    }
    if (filter.filterRowKey(kv.getBuffer(), kv.getRowOffset(), kv.getRowLength())) {
    continue;
    }
    ReturnCode filterResult = filter.filterKeyValue(kv);
    if (filterResult == ReturnCode.INCLUDE) {
    nkvs.add(kv);
    } else if (filterResult == ReturnCode.NEXT_ROW) {
    break;
    }
    // ignoring next key hint which is a optimization to reduce file system IO
    }
    if (filter.hasFilterRow()) {
    filter.filterRow(nkvs);
    }
    kvs = nkvs;
    }
    if (!kvs.isEmpty()) {
    ret.add(new Result(kvs));
    }
    }

    return new ResultScanner() {
    public Iterator<Result> iterator() {
    return ret.iterator();
    }

    public Result[] next(int nbRows) throws IOException {
    ArrayList<Result> resultSets = new ArrayList<Result>(nbRows);
    for (int i = 0; i < nbRows; i++) {
    Result next = next();
    if (next != null) {
    resultSets.add(next);
    } else {
    break;
    }
    }
    return resultSets.toArray(new Result[resultSets.size()]);
    }

    public Result next() throws IOException {
    return iterator().next();
    }

    public void close() {
    }
    };
    }

    @Override
    public ResultScanner getScanner(byte[] family) throws IOException {
    Scan scan = new Scan();
    scan.addFamily(family);
    return getScanner(scan);
    }

    @Override
    public ResultScanner getScanner(byte[] family, byte[] qualifier) throws IOException {
    Scan scan = new Scan();
    scan.addColumn(family, qualifier);
    return getScanner(scan);
    }

    @Override
    public void put(Put put) throws IOException {
    byte[] row = put.getRow();
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowData = forceFind(data, row, new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(Bytes.BYTES_COMPARATOR));
    for (byte[] family : put.getFamilyMap().keySet()) {
    NavigableMap<byte[], NavigableMap<Long, byte[]>> familyData = forceFind(rowData, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(Bytes.BYTES_COMPARATOR));
    for (KeyValue kv : put.getFamilyMap().get(family)) {
    kv.updateLatestStamp(Bytes.toBytes(System.currentTimeMillis()));
    byte[] qualifier = kv.getQualifier();
    NavigableMap<Long, byte[]> qualifierData = forceFind(familyData, qualifier, new TreeMap<Long, byte[]>());
    qualifierData.put(kv.getTimestamp(), kv.getValue());
    }
    }
    }

    /**
    * Helper method to find a key in a map. If key is not found, newObject is
    * added to map and returned
    *
    * @param map
    * map to extract value from
    * @param key
    * key to look for
    * @param newObject
    * set key to this if not found
    * @return found value or newObject if not found
    */
    private <K, V> V forceFind(NavigableMap<K, V> map, K key, V newObject) {
    V data = map.get(key);
    if (data == null) {
    data = newObject;
    map.put(key, data);
    }
    return data;
    }

    @Override
    public void put(List<Put> puts) throws IOException {
    for (Put put : puts)
    put(put);
    }

    /**
    * Checks if the value with given details exists in database, or is
    * non-existent in the case of value being null
    *
    * @param row
    * row
    * @param family
    * family
    * @param qualifier
    * qualifier
    * @param value
    * value
    * @return true if value is not null and exists in db, or value is null and
    * not exists in db, false otherwise
    */
    private boolean check(byte[] row, byte[] family, byte[] qualifier, byte[] value) {
    if (value == null || value.length == 0)
    return !data.containsKey(row) || !data.get(row).containsKey(family) || !data.get(row).get(family).containsKey(qualifier);
    else
    return data.containsKey(row) && data.get(row).containsKey(family) && data.get(row).get(family).containsKey(qualifier) && !data.get(row).get(family).get(qualifier).isEmpty() && Arrays.equals(data.get(row).get(family).get(qualifier).lastEntry().getValue(), value);
    }

    @Override
    public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, byte[] value, Put put) throws IOException {
    if (check(row, family, qualifier, value)) {
    put(put);
    return true;
    }
    return false;
    }

    @Override
    public void delete(Delete delete) throws IOException {
    byte[] row = delete.getRow();
    if (data.get(row) == null)
    return;
    if (delete.getFamilyMap().size() == 0) {
    data.remove(row);
    return;
    }
    for (byte[] family : delete.getFamilyMap().keySet()) {
    if (data.get(row).get(family) == null)
    continue;
    if (delete.getFamilyMap().get(family).isEmpty()) {
    data.get(row).remove(family);
    continue;
    }
    for (KeyValue kv : delete.getFamilyMap().get(family)) {
    data.get(row).get(kv.getFamily()).remove(kv.getQualifier());
    }
    }
    }

    @Override
    public void delete(List<Delete> deletes) throws IOException {
    for (Delete delete : deletes)
    delete(delete);
    }

    @Override
    public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier, byte[] value, Delete delete) throws IOException {
    if (check(row, family, qualifier, value)) {
    delete(delete);
    return true;
    }
    return false;
    }

    @Override
    public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount) throws IOException {
    return incrementColumnValue(row, family, qualifier, amount, true);
    }

    @Override
    public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount, boolean writeToWAL) throws IOException {
    if (check(row, family, qualifier, null)) {
    Put put = new Put(row);
    put.add(family, qualifier, Bytes.toBytes(amount));
    put(put);
    return amount;
    }
    long newValue = Bytes.toLong(data.get(row).get(family).get(qualifier).lastEntry().getValue()) + amount;
    data.get(row).get(family).get(qualifier).put(System.currentTimeMillis(), Bytes.toBytes(newValue));
    return newValue;
    }

    @Override
    public boolean isAutoFlush() {
    return true;
    }

    @Override
    public void flushCommits() throws IOException {
    }

    @Override
    public void close() throws IOException {
    }

    @Override
    public RowLock lockRow(byte[] row) throws IOException {
    return null;
    }

    @Override
    public void unlockRow(RowLock rl) throws IOException {
    }

    @Override
    public Object[] batch(List<Row> actions) throws IOException, InterruptedException {
    List<Result> results = new ArrayList<Result>();
    for (Row r : actions) {
    if (r instanceof Delete) {
    delete((Delete) r);
    continue;
    }
    if (r instanceof Put) {
    put((Put) r);
    continue;
    }
    if (r instanceof Get) {
    results.add(get((Get) r));
    }
    }
    return results.toArray();
    }

    @Override
    public void batch(List<Row> actions, Object[] results) throws IOException, InterruptedException {
    results = batch(actions);
    }

    @Override
    public Result[] get(List<Get> gets) throws IOException {
    List<Result> results = new ArrayList<Result>();
    for (Get g : gets) {
    results.add(get(g));
    }
    return results.toArray(new Result[results.size()]);
    }

    @Override
    public Result increment(Increment increment) throws IOException {
    List<KeyValue> kvs = new ArrayList<KeyValue>();
    Map<byte[], NavigableMap<byte[], Long>> famToVal = increment.getFamilyMap();
    for (Entry<byte[], NavigableMap<byte[], Long>> ef : famToVal.entrySet()) {
    byte[] family = ef.getKey();
    NavigableMap<byte[], Long> qToVal = ef.getValue();
    for (Entry<byte[], Long> eq : qToVal.entrySet()) {
    incrementColumnValue(increment.getRow(), family, eq.getKey(), eq.getValue());
    kvs.add(new KeyValue(increment.getRow(), family, eq.getKey(), Bytes.toBytes(eq.getValue())));
    }
    }
    return new Result(kvs);
    }

    private MockHTable() {
    }

    /**
    * Default way of constructing a MockHTable
    *
    * @return a new MockHTable
    */
    public static MockHTable create() {
    return new MockHTable();
    }

    /**
    * Create a MockHTable with some pre-loaded data. Parameter should be a map
    * of column-to-data mappings of rows. It can be created with a YAML like
    *
    * <pre>
    * rowid:
    * family1:qualifier1: value1
    * family2:qualifier2: value2
    * </pre>
    *
    * @param dump
    * pre-loaded data
    * @return a new MockHTable loaded with given data
    */
    public static MockHTable with(Map<String, Map<String, String>> dump) {
    MockHTable ret = new MockHTable();
    for (String row : dump.keySet()) {
    for (String column : dump.get(row).keySet()) {
    String val = dump.get(row).get(column);
    put(ret, row, column, val);
    }
    }
    return ret;
    }

    /**
    * Helper method of pre-loaders, adds parameters to data.
    *
    * @param ret
    * data to load into
    * @param row
    * rowid
    * @param column
    * family:qualifier encoded value
    * @param val
    * value
    */
    private static void put(MockHTable ret, String row, String column, String val) {
    String[] fq = split(column);
    byte[] family = Bytes.toBytesBinary(fq[0]);
    byte[] qualifier = Bytes.toBytesBinary(fq[1]);
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> families = ret.forceFind(ret.data, Bytes.toBytesBinary(row), new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(Bytes.BYTES_COMPARATOR));
    NavigableMap<byte[], NavigableMap<Long, byte[]>> qualifiers = ret.forceFind(families, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(Bytes.BYTES_COMPARATOR));
    NavigableMap<Long, byte[]> values = ret.forceFind(qualifiers, qualifier, new TreeMap<Long, byte[]>());
    values.put(System.currentTimeMillis(), Bytes.toBytesBinary(val));
    }

    /**
    * Create a MockHTable with some pre-loaded data. Parameter should be an
    * array of string arrays which define every column value individually.
    *
    * <pre>
    * new String[][] {
    * { "&lt;rowid&gt;", "&lt;column&gt;", "&lt;value&gt;" },
    * { "id", "family:qualifier1", "data1" },
    * { "id", "family:qualifier2", "data2" }
    * });
    * </pre>
    *
    * @param dump
    * @return
    */
    public static MockHTable with(String[][] dump) {
    MockHTable ret = new MockHTable();
    for (String[] row : dump) {
    put(ret, row[0], row[1], row[2]);
    }
    return ret;
    }

    /**
    * Column identification helper
    *
    * @param column
    * column name in the format family:qualifier
    * @return <code>{"family", "qualifier"}</code>
    */
    private static String[] split(String column) {
    return new String[] { column.substring(0, column.indexOf(':')), column.substring(column.indexOf(':') + 1) };
    }

    /**
    * Read a value saved in the object. Useful for making assertions in tests.
    *
    * @param rowid
    * rowid of the data to read
    * @param column
    * family:qualifier of the data to read
    * @return value or null if row or column of the row does not exist
    */
    public byte[] read(String rowid, String column) {
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> row = data.get(Bytes.toBytesBinary(rowid));
    if (row == null)
    return null;
    String[] fq = split(column);
    byte[] family = Bytes.toBytesBinary(fq[0]);
    byte[] qualifier = Bytes.toBytesBinary(fq[1]);
    if (!row.containsKey(family))
    return null;
    if (!row.get(family).containsKey(qualifier))
    return null;
    return row.get(family).get(qualifier).lastEntry().getValue();
    }

    public static String toEString(boolean val) {
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }

    public static String toEString(double val) {
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }

    public static String toEString(float val) {
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }

    public static String toEString(int val) {
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }

    public static String toEString(long val) {
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }

    public static String toEString(short val) {
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    /**
    * This is all the data for a MockHTable instance
    */
    private NavigableMap<byte[], NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>> data = new TreeMap<byte[], NavigableMap<byte[],NavigableMap<byte[],NavigableMap<Long,byte[]>>>>(Bytes.BYTES_COMPARATOR);

    /**
    * Helper method to convert some data into a list of KeyValue's
    *
    * @param row
    * row value of the KeyValue's
    * @param rowdata
    * data to decode
    * @return List of KeyValue's
    */
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata){
    return toKeyValue(row, rowdata, 0, Long.MAX_VALUE);
    }

    /**
    * Helper method to convert some data into a list of KeyValue's with timestamp
    * constraint
    *
    * @param row
    * row value of the KeyValue's
    * @param rowdata
    * data to decode
    * @param timestampStart
    * start of the timestamp constraint
    * @param timestampEnd
    * end of the timestamp constraint
    * @return List of KeyValue's
    */
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata, long timestampStart, long timestampEnd){
    List<KeyValue> ret = new ArrayList<KeyValue>();
    for (byte[] family : rowdata.keySet())
    for (byte[] qualifier : rowdata.get(family).keySet())
    for (Entry<Long, byte[]> tsToVal : rowdata.get(family).get(qualifier).entrySet()){
    Long timestamp = tsToVal.getKey();
    if (timestamp < timestampStart)
    continue;
    if (timestamp > timestampEnd)
    continue;
    byte[] value = tsToVal.getValue();
    ret.add(new KeyValue(row, family, qualifier, timestamp, value));
    }
    return ret;
    }

    /**
    * Clients should not rely on table names so this returns null.
    * @return null
    */
    @Override
    public byte[] getTableName() { return null; }

    /**
    * No configuration needed to work so this returns null.
    * @return null
    */
    @Override
    public Configuration getConfiguration() { return null; }

    /**
    * No table descriptor needed so this returns null.
    * @return null
    */
    @Override
    public HTableDescriptor getTableDescriptor() { return null; }

    @Override
    public boolean exists(Get get) throws IOException {
    return data.containsKey(get.getRow());
    }

    @Override
    public Result get(Get get) throws IOException {
    if (!exists(get))
    return new Result();
    byte[] row = get.getRow();
    List<KeyValue> kvs = new ArrayList<KeyValue>();
    if (!get.hasFamilies()) {
    kvs = toKeyValue(row, data.get(row));
    } else {
    for (byte[] family : get.getFamilyMap().keySet()){
    if (data.get(row).get(family) == null)
    continue;
    NavigableSet<byte[]> qualifiers = get.getFamilyMap().get(family);
    if (qualifiers == null)
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers){
    if (qualifier == null)
    qualifier = "".getBytes();
    if (!data.get(row).containsKey(family) ||
    !data.get(row).get(family).containsKey(qualifier) ||
    data.get(row).get(family).get(qualifier).isEmpty())
    continue;
    Entry<Long, byte[]> timestampAndValue = data.get(row).get(family).get(qualifier).lastEntry();
    kvs.add(new KeyValue(row,family, qualifier, timestampAndValue.getKey(), timestampAndValue.getValue()));
    }
    }
    }
    Filter filter = get.getFilter();
    if (filter != null) {
    filter.reset();
    List<KeyValue> nkvs = new ArrayList<KeyValue>(kvs.size());
    for (KeyValue kv : kvs) {
    if (filter.filterAllRemaining()) {
    break;
    }
    if (filter.filterRowKey(kv.getBuffer(), kv.getRowOffset(), kv.getRowLength())) {
    continue;
    }
    if (filter.filterKeyValue(kv) == ReturnCode.INCLUDE) {
    nkvs.add(kv);
    }
    // ignoring next key hint which is a optimization to reduce file system IO
    }
    if (filter.hasFilterRow()) {
    filter.filterRow(nkvs);
    }
    kvs = nkvs;
    }

    return new Result(kvs);
    }

    @Override
    public Result getRowOrBefore(byte[] row, byte[] family) throws IOException {
    // FIXME: implement
    return null;
    }

    @Override
    public ResultScanner getScanner(Scan scan) throws IOException {
    final List<Result> ret = new ArrayList<Result>();
    byte[] st = scan.getStartRow();
    byte[] sp = scan.getStopRow();
    Filter filter = scan.getFilter();

    for (byte[] row : data.keySet()){
    // if row is equal to startRow emit it. When startRow (inclusive) and
    // stopRow (exclusive) is the same, it should not be excluded which would
    // happen w/o this control.
    if (st != null && st.length > 0 && Bytes.compareTo(st, row) > 0)
    continue;
    // if row is equal to stopRow or after it do not emit, stop iteration
    if (sp != null && sp.length > 0 && Bytes.compareTo(sp, row) <= 0)
    break;

    List<KeyValue> kvs = null;
    if (!scan.hasFamilies()) {
    kvs = toKeyValue(row, data.get(row), scan.getTimeRange().getMin(), scan.getTimeRange().getMax());
    } else {
    kvs = new ArrayList<KeyValue>();
    for (byte[] family : scan.getFamilyMap().keySet()){
    if (data.get(row).get(family) == null)
    continue;
    NavigableSet<byte[]> qualifiers = scan.getFamilyMap().get(family);
    if (qualifiers == null)
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers){
    for (Long timestamp : data.get(row).get(family).get(qualifier).keySet()){
    if (timestamp < scan.getTimeRange().getMin())
    continue;
    if (timestamp > scan.getTimeRange().getMax())
    continue;
    byte[] value = data.get(row).get(family).get(qualifier).get(timestamp);
    kvs.add(new KeyValue(row, family, qualifier, timestamp, value));
    }
    }
    }
    }
    if (filter != null) {
    filter.reset();
    List<KeyValue> nkvs = new ArrayList<KeyValue>(kvs.size());
    for (KeyValue kv : kvs) {
    if (filter.filterAllRemaining()) {
    break;
    }
    if (filter.filterRowKey(kv.getBuffer(), kv.getRowOffset(), kv.getRowLength())) {
    continue;
    }
    ReturnCode filterResult = filter.filterKeyValue(kv);
    if (filterResult == ReturnCode.INCLUDE) {
    nkvs.add(kv);
    } else if (filterResult == ReturnCode.NEXT_ROW) {
    break;
    }
    // ignoring next key hint which is a optimization to reduce file system IO
    }
    if (filter.hasFilterRow()) {
    filter.filterRow(nkvs);
    }
    kvs = nkvs;
    }
    if (!kvs.isEmpty()) {
    ret.add(new Result(kvs));
    }
    }

    return new ResultScanner() {
    private final Iterator<Result> iterator = ret.iterator();
    public Iterator<Result> iterator() {
    return iterator;
    }
    public Result[] next(int nbRows) throws IOException {
    ArrayList<Result> resultSets = new ArrayList<Result>(nbRows);
    for(int i = 0; i < nbRows; i++) {
    Result next = next();
    if (next != null) {
    resultSets.add(next);
    } else {
    break;
    }
    }
    return resultSets.toArray(new Result[resultSets.size()]);
    }
    public Result next() throws IOException {
    return iterator().next();
    }
    public void close() {}
    };
    }

    @Override
    public ResultScanner getScanner(byte[] family) throws IOException {
    Scan scan = new Scan();
    scan.addFamily(family);
    return getScanner(scan);
    }

    @Override
    public ResultScanner getScanner(byte[] family, byte[] qualifier)
    throws IOException {
    Scan scan = new Scan();
    scan.addColumn(family, qualifier);
    return getScanner(scan);
    }

    @Override
    public void put(Put put) throws IOException {
    byte[] row = put.getRow();
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowData = forceFind(data, row, new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(Bytes.BYTES_COMPARATOR));
    for (byte[] family : put.getFamilyMap().keySet()){
    NavigableMap<byte[], NavigableMap<Long, byte[]>> familyData = forceFind(rowData, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(Bytes.BYTES_COMPARATOR));
    for (KeyValue kv : put.getFamilyMap().get(family)){
    kv.updateLatestStamp(Bytes.toBytes(System.currentTimeMillis()));
    byte[] qualifier = kv.getQualifier();
    NavigableMap<Long, byte[]> qualifierData = forceFind(familyData, qualifier, new TreeMap<Long, byte[]>());
    qualifierData.put(kv.getTimestamp(), kv.getValue());
    }
    }
    }

    /**
    * Helper method to find a key in a map. If key is not found, newObject is
    * added to map and returned
    *
    * @param map
    * map to extract value from
    * @param key
    * key to look for
    * @param newObject
    * set key to this if not found
    * @return found value or newObject if not found
    */
    private <K, V> V forceFind(NavigableMap<K, V> map, K key, V newObject){
    V data = map.get(key);
    if (data == null){
    data = newObject;
    map.put(key, data);
    }
    return data;
    }

    @Override
    public void put(List<Put> puts) throws IOException {
    for (Put put : puts)
    put(put);
    }

    /**
    * Checks if the value with given details exists in database, or is
    * non-existent in the case of value being null
    *
    * @param row
    * row
    * @param family
    * family
    * @param qualifier
    * qualifier
    * @param value
    * value
    * @return true if value is not null and exists in db, or value is null and
    * not exists in db, false otherwise
    */
    private boolean check(byte[] row, byte[] family, byte[] qualifier, byte[] value){
    if (value == null || value.length == 0)
    return ! data.containsKey(row) ||
    ! data.get(row).containsKey(family) ||
    ! data.get(row).get(family).containsKey(qualifier);
    else
    return data.containsKey(row) &&
    data.get(row).containsKey(family) &&
    data.get(row).get(family).containsKey(qualifier) &&
    ! data.get(row).get(family).get(qualifier).isEmpty() &&
    Arrays.equals(data.get(row).get(family).get(qualifier).lastEntry().getValue(), value);
    }

    @Override
    public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier,
    byte[] value, Put put) throws IOException {
    if (check(row, family, qualifier, value)){
    put(put);
    return true;
    }
    return false;
    }

    @Override
    public void delete(Delete delete) throws IOException {
    byte[] row = delete.getRow();
    if (data.get(row) == null)
    return;
    if (delete.getFamilyMap().size() == 0){
    data.remove(row);
    return;
    }
    for (byte[] family : delete.getFamilyMap().keySet()){
    if (data.get(row).get(family) == null)
    continue;
    if (delete.getFamilyMap().get(family).isEmpty()){
    data.get(row).remove(family);
    continue;
    }
    for (KeyValue kv : delete.getFamilyMap().get(family)){
    data.get(row).get(kv.getFamily()).remove(kv.getQualifier());
    }
    }
    }

    @Override
    public void delete(List<Delete> deletes) throws IOException {
    for (Delete delete : deletes)
    delete(delete);
    }

    @Override
    public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
    byte[] value, Delete delete) throws IOException {
    if(check(row, family, qualifier, value)){
    delete(delete);
    return true;
    }
    return false;
    }

    @Override
    public long incrementColumnValue(byte[] row, byte[] family,
    byte[] qualifier, long amount) throws IOException {
    return incrementColumnValue(row, family, qualifier, amount, true);
    }

    @Override
    public long incrementColumnValue(byte[] row, byte[] family,
    byte[] qualifier, long amount, boolean writeToWAL) throws IOException {
    if (check(row, family, qualifier, null)){
    Put put = new Put(row);
    put.add(family, qualifier, Bytes.toBytes(amount));
    put(put);
    return amount;
    }
    long newValue = Bytes.toLong(data.get(row).get(family).get(qualifier).lastEntry().getValue())+amount;
    data.get(row).get(family).get(qualifier).put(System.currentTimeMillis(),
    Bytes.toBytes(newValue));
    return newValue;
    }

    @Override
    public boolean isAutoFlush() {
    return true;
    }

    @Override
    public void flushCommits() throws IOException {
    }

    @Override
    public void close() throws IOException {
    }

    @Override
    public RowLock lockRow(byte[] row) throws IOException {
    return null;
    }

    @Override
    public void unlockRow(RowLock rl) throws IOException {
    }

    @Override
    public Object[] batch(List<Row> actions)
    throws IOException,
    InterruptedException {
    List<Result> results = new ArrayList<Result>();
    for (Row r : actions) {
    if (r instanceof Delete) {
    delete((Delete) r);
    continue;
    }
    if (r instanceof Put) {
    put((Put) r);
    continue;
    }
    if (r instanceof Get) {
    results.add(get((Get) r));
    }
    }
    return results.toArray();
    }

    @Override
    public void batch(List<Row> actions, Object[] results)
    throws IOException,
    InterruptedException {
    results = batch(actions);
    }

    @Override
    public Result[] get(List<Get> gets) throws IOException {
    List<Result> results = new ArrayList<Result>();
    for (Get g : gets) {
    results.add(get(g));
    }
    return results.toArray(new Result[results.size()]);
    }

    @Override
    public Result increment(Increment increment) throws IOException {
    List<KeyValue> kvs = new ArrayList<KeyValue>();
    Map<byte[], NavigableMap<byte[], Long>> famToVal = increment.getFamilyMap();
    for (Entry<byte[], NavigableMap<byte[], Long>> ef : famToVal.entrySet()) {
    byte[] family = ef.getKey();
    NavigableMap<byte[], Long> qToVal = ef.getValue();
    for (Entry<byte[], Long> eq : qToVal.entrySet()) {
    incrementColumnValue(increment.getRow(), family, eq.getKey(), eq.getValue());
    kvs.add(new KeyValue(increment.getRow(), family, eq.getKey(), Bytes.toBytes(eq.getValue())));
    }
    }
    return new Result(kvs);
    }

    private MockHTable(){}

    /**
    * Default way of constructing a MockHTable
    * @return a new MockHTable
    */
    public static MockHTable create(){
    return new MockHTable();
    }

    /**
    * Create a MockHTable with some pre-loaded data. Parameter should be a map of
    * column-to-data mappings of rows. It can be created with a YAML like
    *
    * <pre>
    * rowid:
    * family1:qualifier1: value1
    * family2:qualifier2: value2
    * </pre>
    *
    * @param dump
    * pre-loaded data
    * @return a new MockHTable loaded with given data
    */
    public static MockHTable with(Map<String, Map<String, String>> dump){
    MockHTable ret = new MockHTable();
    for (String row : dump.keySet()){
    for (String column : dump.get(row).keySet()){
    String val = dump.get(row).get(column);
    put(ret, row, column, val);
    }
    }
    return ret;
    }

    /**
    * Helper method of pre-loaders, adds parameters to data.
    *
    * @param ret
    * data to load into
    * @param row
    * rowid
    * @param column
    * family:qualifier encoded value
    * @param val
    * value
    */
    private static void put(MockHTable ret, String row, String column,
    String val) {
    String[] fq = split(column);
    byte[] family = Bytes.toBytesBinary(fq[0]);
    byte[] qualifier = Bytes.toBytesBinary(fq[1]);
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> families = ret.forceFind(ret.data, Bytes.toBytesBinary(row), new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(Bytes.BYTES_COMPARATOR));
    NavigableMap<byte[], NavigableMap<Long, byte[]>> qualifiers = ret.forceFind(families, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(Bytes.BYTES_COMPARATOR));
    NavigableMap<Long, byte[]> values = ret.forceFind(qualifiers, qualifier, new TreeMap<Long, byte[]>());
    values.put(System.currentTimeMillis(), Bytes.toBytesBinary(val));
    }

    /**
    * Create a MockHTable with some pre-loaded data. Parameter should be an array
    * of string arrays which define every column value individually.
    *
    * <pre>
    * new String[][] {
    * { "&lt;rowid&gt;", "&lt;column&gt;", "&lt;value&gt;" },
    * { "id", "family:qualifier1", "data1" },
    * { "id", "family:qualifier2", "data2" }
    * });
    * </pre>
    *
    * @param dump
    * @return
    */
    public static MockHTable with(String[][] dump){
    MockHTable ret = new MockHTable();
    for(String[] row : dump){
    put(ret, row[0], row[1], row[2]);
    }
    return ret;
    }

    /**
    * Column identification helper
    *
    * @param column
    * column name in the format family:qualifier
    * @return <code>{"family", "qualifier"}</code>
    */
    private static String[] split(String column){
    return new String[]{
    column.substring(0, column.indexOf(':')),
    column.substring(column.indexOf(':')+1)};
    }

    /**
    * Read a value saved in the object. Useful for making assertions in tests.
    *
    * @param rowid
    * rowid of the data to read
    * @param column
    * family:qualifier of the data to read
    * @return value or null if row or column of the row does not exist
    */
    public byte[] read(String rowid, String column){
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> row = data.get(Bytes.toBytesBinary(rowid));
    if (row == null)
    return null;
    String[] fq = split(column);
    byte[] family = Bytes.toBytesBinary(fq[0]);
    byte[] qualifier = Bytes.toBytesBinary(fq[1]);
    if (!row.containsKey(family))
    return null;
    if (!row.get(family).containsKey(qualifier))
    return null;
    return row.get(family).get(qualifier).lastEntry().getValue();
    }

    public static String toEString(boolean val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(double val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(float val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(int val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(long val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(short val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    }
  9. @gensth gensth revised this gist Aug 24, 2011. 1 changed file with 8 additions and 0 deletions.
    8 changes: 8 additions & 0 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -242,6 +242,10 @@ public Result get(Get get) throws IOException {
    if (filter.filterKeyValue(kv) == ReturnCode.INCLUDE) {
    nkvs.add(kv);
    }
    // ignoring next key hint which is a optimization to reduce file system IO
    }
    if (filter.hasFilterRow()) {
    filter.filterRow(nkvs);
    }
    kvs = nkvs;
    }
    @@ -311,6 +315,10 @@ public ResultScanner getScanner(Scan scan) throws IOException {
    } else if (filterResult == ReturnCode.NEXT_ROW) {
    break;
    }
    // ignoring next key hint which is a optimization to reduce file system IO
    }
    if (filter.hasFilterRow()) {
    filter.filterRow(nkvs);
    }
    kvs = nkvs;
    }
  10. @gensth gensth revised this gist Aug 24, 2011. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -236,6 +236,9 @@ public Result get(Get get) throws IOException {
    if (filter.filterAllRemaining()) {
    break;
    }
    if (filter.filterRowKey(kv.getBuffer(), kv.getRowOffset(), kv.getRowLength())) {
    continue;
    }
    if (filter.filterKeyValue(kv) == ReturnCode.INCLUDE) {
    nkvs.add(kv);
    }
    @@ -299,6 +302,9 @@ public ResultScanner getScanner(Scan scan) throws IOException {
    if (filter.filterAllRemaining()) {
    break;
    }
    if (filter.filterRowKey(kv.getBuffer(), kv.getRowOffset(), kv.getRowLength())) {
    continue;
    }
    ReturnCode filterResult = filter.filterKeyValue(kv);
    if (filterResult == ReturnCode.INCLUDE) {
    nkvs.add(kv);
  11. @gensth gensth revised this gist Aug 22, 2011. 1 changed file with 599 additions and 592 deletions.
    1,191 changes: 599 additions & 592 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    /**
    * This file is licensed to you under the Apache License, Version 2.0 (the
    * "License"); you may not use this file except in compliance with the
    * This file is licensed to you 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
    @@ -19,14 +19,11 @@
    import java.util.Map.Entry;
    import java.util.NavigableMap;
    import java.util.NavigableSet;
    import java.util.NoSuchElementException;
    import java.util.TreeMap;
    import java.util.TreeSet;

    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.hbase.HTableDescriptor;
    import org.apache.hadoop.hbase.KeyValue;
    import org.apache.hadoop.hbase.KeyValue.KVComparator;
    import org.apache.hadoop.hbase.client.Delete;
    import org.apache.hadoop.hbase.client.Get;
    import org.apache.hadoop.hbase.client.HTableInterface;
    @@ -37,6 +34,7 @@
    import org.apache.hadoop.hbase.client.Row;
    import org.apache.hadoop.hbase.client.RowLock;
    import org.apache.hadoop.hbase.client.Scan;
    import org.apache.hadoop.hbase.filter.Filter;
    import org.apache.hadoop.hbase.filter.Filter.ReturnCode;
    import org.apache.hadoop.hbase.util.Bytes;

    @@ -47,16 +45,16 @@
    * <p>
    * Instances should be get using <code>MockHTable.create()</code>. So while a
    * DAO with a saving operation like
    *
    *
    * <pre>
    * public class MyDAO {
    * private HTableInterface table;
    *
    * public MyDAO(HTableInterface table) {
    * this.table = table;
    * }
    *
    * public void saveData(byte[] id, byte[] data) throws IOException{
    * private HTableInterface table;
    *
    * public MyDAO(HTableInterface table) {
    * this.table = table;
    * }
    *
    * public void saveData(byte[] id, byte[] data) throws IOException{
    * Put put = new Put(id)
    * put.add(family, qualifier, data);
    * table.put(put);
    @@ -65,29 +63,29 @@
    * </pre>
    * <p>
    * is used in production like
    *
    *
    * <pre>
    * MyDAO(new HTable(conf, tableName)).saveData(id, data);
    * </pre>
    * <p>
    * can be tested like
    *
    *
    * <pre>
    * &#064;Test
    * public void testSave() {
    * MockHTable table = MockHTable.create();
    * MyDAO(table).saveData(id, data);
    * Get get = new Get(id);
    * Result result = table.get(get);
    * assertArrayEquals(data, result.getValue(family, qualifier));
    * MockHTable table = MockHTable.create();
    * MyDAO(table).saveData(id, data);
    * Get get = new Get(id);
    * Result result = table.get(get);
    * assertArrayEquals(data, result.getValue(family, qualifier));
    * }
    * </pre>
    * <p>
    * MockHTable instances can also be initialized with pre-loaded data using one
    * of the String[][] or Map<String, Map<String, String>> data formats. While
    * String[][] parameter lets directly loading data from source code, Map can be
    * generated from a YAML document, using a parser.
    *
    *
    * <pre>
    * // String[][]
    * MockHTable table = MockHTable.with(new String[][] {
    @@ -102,586 +100,595 @@
    * <p>
    * If value is not supposed to be a String, but an int, double or anything,
    * <code>MockHTable.toEString()</code> can be used to turn it into a String.
    *
    *
    * <p>
    * In order to simplify assertions for tests that should put anything into
    * database, MockHTable.read() works with two parameters (id and column) and
    * returns anything written to that row/column. So, previous test can be reduced to
    *
    * returns anything written to that row/column. So, previous test can be reduced
    * to
    *
    * <pre>
    * &#064;Test
    * public void testSave() {
    * MockHTable table = MockHTable.create();
    * MyDAO(table).saveData(id, data);
    * assertArrayEquals(data, table.read(id, "family:qualifier"));
    * MockHTable table = MockHTable.create();
    * MyDAO(table).saveData(id, data);
    * assertArrayEquals(data, table.read(id, &quot;family:qualifier&quot;));
    * }
    * </pre>
    * <p>
    *
    * TODO: Don't know if timestamps work correctly
    *
    * @author erdem
    *
    */
    public class MockHTable implements HTableInterface {
    /**
    * This is all the data for a MockHTable instance
    */
    private NavigableMap<byte[], NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>> data = new TreeMap<byte[], NavigableMap<byte[],NavigableMap<byte[],NavigableMap<Long,byte[]>>>>(Bytes.BYTES_COMPARATOR);

    /**
    * Helper method to convert some data into a list of KeyValue's
    *
    * @param row
    * row value of the KeyValue's
    * @param rowdata
    * data to decode
    * @return List of KeyValue's
    */
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata){
    return toKeyValue(row, rowdata, 0, Long.MAX_VALUE);
    }

    /**
    * Helper method to convert some data into a list of KeyValue's with timestamp
    * constraint
    *
    * @param row
    * row value of the KeyValue's
    * @param rowdata
    * data to decode
    * @param timestampStart
    * start of the timestamp constraint
    * @param timestampEnd
    * end of the timestamp constraint
    * @return List of KeyValue's
    */
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata, long timestampStart, long timestampEnd){
    TreeSet<KeyValue> ret = new TreeSet<KeyValue>(new KVComparator());
    for (byte[] family : rowdata.keySet())
    for (byte[] qualifier : rowdata.get(family).keySet())
    for (Entry<Long, byte[]> tsToVal : rowdata.get(family).get(qualifier).entrySet()){
    Long timestamp = tsToVal.getKey();
    if (timestamp < timestampStart)
    continue;
    if (timestamp > timestampEnd)
    continue;
    byte[] value = tsToVal.getValue();
    ret.add(new KeyValue(row, family, qualifier, timestamp, value));
    }
    return new ArrayList<KeyValue>(ret);
    }

    /**
    * Clients should not rely on table names so this returns null.
    * @return null
    */
    @Override
    public byte[] getTableName() { return null; }

    /**
    * No configuration needed to work so this returns null.
    * @return null
    */
    @Override
    public Configuration getConfiguration() { return null; }

    /**
    * No table descriptor needed so this returns null.
    * @return null
    */
    @Override
    public HTableDescriptor getTableDescriptor() { return null; }

    @Override
    public boolean exists(Get get) throws IOException {
    return data.containsKey(get.getRow());
    }

    @Override
    public Result get(Get get) throws IOException {
    if (!exists(get))
    return new Result();
    byte[] row = get.getRow();
    List<KeyValue> kvs = new ArrayList<KeyValue>();
    if (!get.hasFamilies()) {
    kvs = toKeyValue(row, data.get(row));
    } else {
    for (byte[] family : get.getFamilyMap().keySet()){
    if (data.get(row).get(family) == null)
    continue;
    NavigableSet<byte[]> qualifiers = get.getFamilyMap().get(family);
    if (qualifiers == null)
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers){
    if (qualifier == null)
    qualifier = "".getBytes();
    if (!data.get(row).containsKey(family) ||
    !data.get(row).get(family).containsKey(qualifier) ||
    data.get(row).get(family).get(qualifier).isEmpty())
    continue;
    Entry<Long, byte[]> timestampAndValue = data.get(row).get(family).get(qualifier).lastEntry();
    kvs.add(new KeyValue(row,family, qualifier, timestampAndValue.getKey(), timestampAndValue.getValue()));
    }
    }
    }
    if (get.getFilter() != null) {
    List<KeyValue> nkvs = new ArrayList<KeyValue>();
    for (KeyValue kv : kvs) {
    if (get.getFilter().filterKeyValue(kv) == ReturnCode.INCLUDE) {
    nkvs.add(kv);
    }
    }
    kvs = nkvs;
    }

    return new Result(kvs);
    }

    @Override
    public Result getRowOrBefore(byte[] row, byte[] family) throws IOException {
    // FIXME: implement
    return null;
    }

    @Override
    public ResultScanner getScanner(Scan scan) throws IOException {
    final List<Result> ret = new ArrayList<Result>();
    byte[] st = scan.getStartRow();
    byte[] sp = scan.getStopRow();

    for (byte[] row : data.keySet()){
    // if row is equal to startRow emit it. When startRow (inclusive) and
    // stopRow (exclusive) is the same, it should not be excluded which would
    // happen w/o this control.
    if (st != null && st.length > 0 &&
    Bytes.toString(st).compareTo(Bytes.toString(row)) != 0) {
    // if row is before startRow do not emit, pass to next row
    if (st != null && st.length > 0 &&
    Bytes.toString(st).compareTo(Bytes.toString(row)) > 0)
    continue;
    // if row is equal to stopRow or after it do not emit, stop iteration
    if (sp != null && sp.length > 0 &&
    Bytes.toString(sp).compareTo(Bytes.toString(row)) <= 0)
    break;
    }

    List<KeyValue> kvs = null;
    if (!scan.hasFamilies()) {
    kvs = toKeyValue(row, data.get(row), scan.getTimeRange().getMin(), scan.getTimeRange().getMax());
    } else {
    kvs = new ArrayList<KeyValue>();
    for (byte[] family : scan.getFamilyMap().keySet()){
    if (data.get(row).get(family) == null)
    continue;
    NavigableSet<byte[]> qualifiers = scan.getFamilyMap().get(family);
    if (qualifiers == null)
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers){
    for (Long timestamp : data.get(row).get(family).get(qualifier).keySet()){
    if (timestamp < scan.getTimeRange().getMin())
    continue;
    if (timestamp > scan.getTimeRange().getMax())
    continue;
    byte[] value = data.get(row).get(family).get(qualifier).get(timestamp);
    kvs.add(new KeyValue(row, family, qualifier, timestamp, value));
    }
    }
    }
    }
    if (scan.getFilter() != null)
    scan.getFilter().filterRow(kvs);
    if (!kvs.isEmpty())
    ret.add(new Result(kvs));
    }

    return new ResultScanner() {
    private final Iterator<Result> iterator = ret.iterator();
    public Iterator<Result> iterator() {
    return iterator;
    }
    public Result[] next(int nbRows) throws IOException {
    ArrayList<Result> resultSets = new ArrayList<Result>(nbRows);
    for(int i = 0; i < nbRows; i++) {
    Result next = next();
    if (next != null) {
    resultSets.add(next);
    } else {
    break;
    }
    }
    return resultSets.toArray(new Result[resultSets.size()]);
    }
    public Result next() throws IOException {
    try {
    return iterator().next();
    } catch (NoSuchElementException e) {
    return null;
    }
    }
    public void close() {}
    };
    }

    @Override
    public ResultScanner getScanner(byte[] family) throws IOException {
    Scan scan = new Scan();
    scan.addFamily(family);
    return getScanner(scan);
    }

    @Override
    public ResultScanner getScanner(byte[] family, byte[] qualifier)
    throws IOException {
    Scan scan = new Scan();
    scan.addColumn(family, qualifier);
    return getScanner(scan);
    }

    @Override
    public void put(Put put) throws IOException {
    byte[] row = put.getRow();
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowData = forceFind(data, row, new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(Bytes.BYTES_COMPARATOR));
    for (byte[] family : put.getFamilyMap().keySet()){
    NavigableMap<byte[], NavigableMap<Long, byte[]>> familyData = forceFind(rowData, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(Bytes.BYTES_COMPARATOR));
    for (KeyValue kv : put.getFamilyMap().get(family)){
    kv.updateLatestStamp(Bytes.toBytes(System.currentTimeMillis()));
    byte[] qualifier = kv.getQualifier();
    NavigableMap<Long, byte[]> qualifierData = forceFind(familyData, qualifier, new TreeMap<Long, byte[]>());
    qualifierData.put(kv.getTimestamp(), kv.getValue());
    }
    }
    }

    /**
    * Helper method to find a key in a map. If key is not found, newObject is
    * added to map and returned
    *
    * @param map
    * map to extract value from
    * @param key
    * key to look for
    * @param newObject
    * set key to this if not found
    * @return found value or newObject if not found
    */
    private <K, V> V forceFind(NavigableMap<K, V> map, K key, V newObject){
    V data = map.get(key);
    if (data == null){
    data = newObject;
    map.put(key, data);
    }
    return data;
    }

    @Override
    public void put(List<Put> puts) throws IOException {
    for (Put put : puts)
    put(put);
    }

    /**
    * Checks if the value with given details exists in database, or is
    * non-existent in the case of value being null
    *
    * @param row
    * row
    * @param family
    * family
    * @param qualifier
    * qualifier
    * @param value
    * value
    * @return true if value is not null and exists in db, or value is null and
    * not exists in db, false otherwise
    */
    private boolean check(byte[] row, byte[] family, byte[] qualifier, byte[] value){
    if (value == null || value.length == 0)
    return ! data.containsKey(row) ||
    ! data.get(row).containsKey(family) ||
    ! data.get(row).get(family).containsKey(qualifier);
    else
    return data.containsKey(row) &&
    data.get(row).containsKey(family) &&
    data.get(row).get(family).containsKey(qualifier) &&
    ! data.get(row).get(family).get(qualifier).isEmpty() &&
    Arrays.equals(data.get(row).get(family).get(qualifier).lastEntry().getValue(), value);
    }

    @Override
    public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier,
    byte[] value, Put put) throws IOException {
    if (check(row, family, qualifier, value)){
    put(put);
    return true;
    }
    return false;
    }

    @Override
    public void delete(Delete delete) throws IOException {
    byte[] row = delete.getRow();
    if (data.get(row) == null)
    return;
    if (delete.getFamilyMap().size() == 0){
    data.remove(row);
    return;
    }
    for (byte[] family : delete.getFamilyMap().keySet()){
    if (data.get(row).get(family) == null)
    continue;
    if (delete.getFamilyMap().get(family).isEmpty()){
    data.get(row).remove(family);
    continue;
    }
    for (KeyValue kv : delete.getFamilyMap().get(family)){
    data.get(row).get(kv.getFamily()).remove(kv.getQualifier());
    }
    }
    }

    @Override
    public void delete(List<Delete> deletes) throws IOException {
    for (Delete delete : deletes)
    delete(delete);
    }

    @Override
    public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
    byte[] value, Delete delete) throws IOException {
    if(check(row, family, qualifier, value)){
    delete(delete);
    return true;
    }
    return false;
    }

    @Override
    public long incrementColumnValue(byte[] row, byte[] family,
    byte[] qualifier, long amount) throws IOException {
    return incrementColumnValue(row, family, qualifier, amount, true);
    }

    @Override
    public long incrementColumnValue(byte[] row, byte[] family,
    byte[] qualifier, long amount, boolean writeToWAL) throws IOException {
    if (check(row, family, qualifier, null)){
    Put put = new Put(row);
    put.add(family, qualifier, Bytes.toBytes(amount));
    put(put);
    return amount;
    }
    long newValue = Bytes.toLong(data.get(row).get(family).get(qualifier).lastEntry().getValue())+amount;
    data.get(row).get(family).get(qualifier).put(System.currentTimeMillis(),
    Bytes.toBytes(newValue));
    return newValue;
    }

    @Override
    public boolean isAutoFlush() {
    return true;
    }

    @Override
    public void flushCommits() throws IOException {
    }

    @Override
    public void close() throws IOException {
    }

    @Override
    public RowLock lockRow(byte[] row) throws IOException {
    return null;
    }

    @Override
    public void unlockRow(RowLock rl) throws IOException {
    }

    @Override
    public Object[] batch(List<Row> actions)
    throws IOException,
    InterruptedException {
    List<Result> results = new ArrayList<Result>();
    for (Row r : actions) {
    if (r instanceof Delete) {
    delete((Delete) r);
    continue;
    }
    if (r instanceof Put) {
    put((Put) r);
    continue;
    }
    if (r instanceof Get) {
    results.add(get((Get) r));
    }
    }
    return results.toArray();
    }

    @Override
    public void batch(List<Row> actions, Object[] results)
    throws IOException,
    InterruptedException {
    results = batch(actions);
    }

    @Override
    public Result[] get(List<Get> gets) throws IOException {
    List<Result> results = new ArrayList<Result>();
    for (Get g : gets) {
    results.add(get(g));
    }
    return results.toArray(new Result[results.size()]);
    }

    @Override
    public Result increment(Increment increment) throws IOException {
    List<KeyValue> kvs = new ArrayList<KeyValue>();
    Map<byte[], NavigableMap<byte[], Long>> famToVal = increment.getFamilyMap();
    for (Entry<byte[], NavigableMap<byte[], Long>> ef : famToVal.entrySet()) {
    byte[] family = ef.getKey();
    NavigableMap<byte[], Long> qToVal = ef.getValue();
    for (Entry<byte[], Long> eq : qToVal.entrySet()) {
    incrementColumnValue(increment.getRow(), family, eq.getKey(), eq.getValue());
    kvs.add(new KeyValue(increment.getRow(), family, eq.getKey(), Bytes.toBytes(eq.getValue())));
    }
    }
    return new Result(kvs);
    }

    private MockHTable(){}

    /**
    * Default way of constructing a MockHTable
    * @return a new MockHTable
    */
    public static MockHTable create(){
    return new MockHTable();
    }

    /**
    * Create a MockHTable with some pre-loaded data. Parameter should be a map of
    * column-to-data mappings of rows. It can be created with a YAML like
    *
    * <pre>
    * rowid:
    * family1:qualifier1: value1
    * family2:qualifier2: value2
    * </pre>
    *
    * @param dump
    * pre-loaded data
    * @return a new MockHTable loaded with given data
    */
    public static MockHTable with(Map<String, Map<String, String>> dump){
    MockHTable ret = new MockHTable();
    for (String row : dump.keySet()){
    for (String column : dump.get(row).keySet()){
    String val = dump.get(row).get(column);
    put(ret, row, column, val);
    }
    }
    return ret;
    }

    /**
    * Helper method of pre-loaders, adds parameters to data.
    *
    * @param ret
    * data to load into
    * @param row
    * rowid
    * @param column
    * family:qualifier encoded value
    * @param val
    * value
    */
    private static void put(MockHTable ret, String row, String column,
    String val) {
    String[] fq = split(column);
    byte[] family = Bytes.toBytesBinary(fq[0]);
    byte[] qualifier = Bytes.toBytesBinary(fq[1]);
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> families = ret.forceFind(ret.data, Bytes.toBytesBinary(row), new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(Bytes.BYTES_COMPARATOR));
    NavigableMap<byte[], NavigableMap<Long, byte[]>> qualifiers = ret.forceFind(families, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(Bytes.BYTES_COMPARATOR));
    NavigableMap<Long, byte[]> values = ret.forceFind(qualifiers, qualifier, new TreeMap<Long, byte[]>());
    values.put(System.currentTimeMillis(), Bytes.toBytesBinary(val));
    }

    /**
    * Create a MockHTable with some pre-loaded data. Parameter should be an array
    * of string arrays which define every column value individually.
    *
    * <pre>
    * new String[][] {
    * { "&lt;rowid&gt;", "&lt;column&gt;", "&lt;value&gt;" },
    * { "id", "family:qualifier1", "data1" },
    * { "id", "family:qualifier2", "data2" }
    * });
    * </pre>
    *
    * @param dump
    * @return
    */
    public static MockHTable with(String[][] dump){
    MockHTable ret = new MockHTable();
    for(String[] row : dump){
    put(ret, row[0], row[1], row[2]);
    }
    return ret;
    }

    /**
    * Column identification helper
    *
    * @param column
    * column name in the format family:qualifier
    * @return <code>{"family", "qualifier"}</code>
    */
    private static String[] split(String column){
    return new String[]{
    column.substring(0, column.indexOf(':')),
    column.substring(column.indexOf(':')+1)};
    }

    /**
    * Read a value saved in the object. Useful for making assertions in tests.
    *
    * @param rowid
    * rowid of the data to read
    * @param column
    * family:qualifier of the data to read
    * @return value or null if row or column of the row does not exist
    */
    public byte[] read(String rowid, String column){
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> row = data.get(Bytes.toBytesBinary(rowid));
    if (row == null)
    return null;
    String[] fq = split(column);
    byte[] family = Bytes.toBytesBinary(fq[0]);
    byte[] qualifier = Bytes.toBytesBinary(fq[1]);
    if (!row.containsKey(family))
    return null;
    if (!row.get(family).containsKey(qualifier))
    return null;
    return row.get(family).get(qualifier).lastEntry().getValue();
    }

    public static String toEString(boolean val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(double val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(float val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(int val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(long val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(short val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    }
    /**
    * This is all the data for a MockHTable instance
    */
    private NavigableMap<byte[], NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>> data = new TreeMap<byte[], NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>>(Bytes.BYTES_COMPARATOR);

    /**
    * Helper method to convert some data into a list of KeyValue's
    *
    * @param row
    * row value of the KeyValue's
    * @param rowdata
    * data to decode
    * @return List of KeyValue's
    */
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata) {
    return toKeyValue(row, rowdata, 0, Long.MAX_VALUE);
    }

    /**
    * Helper method to convert some data into a list of KeyValue's with
    * timestamp constraint
    *
    * @param row
    * row value of the KeyValue's
    * @param rowdata
    * data to decode
    * @param timestampStart
    * start of the timestamp constraint
    * @param timestampEnd
    * end of the timestamp constraint
    * @return List of KeyValue's
    */
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata, long timestampStart, long timestampEnd) {
    List<KeyValue> ret = new ArrayList<KeyValue>();
    for (byte[] family : rowdata.keySet())
    for (byte[] qualifier : rowdata.get(family).keySet())
    for (Long timestamp : rowdata.get(family).get(qualifier).keySet()) {
    if (timestamp < timestampStart)
    continue;
    if (timestamp > timestampEnd)
    continue;
    byte[] value = rowdata.get(family).get(qualifier).get(timestamp);
    ret.add(new KeyValue(row, family, qualifier, timestamp, value));
    }
    return ret;
    }

    /**
    * Clients should not rely on table names so this returns null.
    *
    * @return null
    */
    @Override
    public byte[] getTableName() {
    return null;
    }

    /**
    * No configuration needed to work so this returns null.
    *
    * @return null
    */
    @Override
    public Configuration getConfiguration() {
    return null;
    }

    /**
    * No table descriptor needed so this returns null.
    *
    * @return null
    */
    @Override
    public HTableDescriptor getTableDescriptor() {
    return null;
    }

    @Override
    public boolean exists(Get get) throws IOException {
    return data.containsKey(get.getRow());
    }

    @Override
    public Result get(Get get) throws IOException {
    if (!exists(get))
    return new Result();
    byte[] row = get.getRow();
    List<KeyValue> kvs = new ArrayList<KeyValue>();
    if (!get.hasFamilies()) {
    kvs = toKeyValue(row, data.get(row));
    } else {
    for (byte[] family : get.getFamilyMap().keySet()) {
    if (data.get(row).get(family) == null)
    continue;
    NavigableSet<byte[]> qualifiers = get.getFamilyMap().get(family);
    if (qualifiers == null)
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers) {
    if (qualifier == null)
    qualifier = "".getBytes();
    if (!data.get(row).containsKey(family) || !data.get(row).get(family).containsKey(qualifier) || data.get(row).get(family).get(qualifier).isEmpty())
    continue;
    Entry<Long, byte[]> timestampAndValue = data.get(row).get(family).get(qualifier).lastEntry();
    kvs.add(new KeyValue(row, family, qualifier, timestampAndValue.getKey(), timestampAndValue.getValue()));
    }
    }
    }
    Filter filter = get.getFilter();
    if (filter != null) {
    filter.reset();
    List<KeyValue> nkvs = new ArrayList<KeyValue>(kvs.size());
    for (KeyValue kv : kvs) {
    if (filter.filterAllRemaining()) {
    break;
    }
    if (filter.filterKeyValue(kv) == ReturnCode.INCLUDE) {
    nkvs.add(kv);
    }
    }
    kvs = nkvs;
    }

    return new Result(kvs);
    }

    @Override
    public Result getRowOrBefore(byte[] row, byte[] family) throws IOException {
    // FIXME: implement
    return null;
    }

    @Override
    public ResultScanner getScanner(Scan scan) throws IOException {
    final List<Result> ret = new ArrayList<Result>();
    byte[] st = scan.getStartRow();
    byte[] sp = scan.getStopRow();
    Filter filter = scan.getFilter();

    for (byte[] row : data.keySet()) {
    // if row is equal to startRow emit it. When startRow (inclusive) and
    // stopRow (exclusive) is the same, it should not be excluded which would
    // happen w/o this control.
    if (st != null && st.length > 0 && Bytes.compareTo(st, row) > 0)
    continue;
    // if row is equal to stopRow or after it do not emit, stop iteration
    if (sp != null && sp.length > 0 && Bytes.compareTo(sp, row) <= 0)
    break;

    List<KeyValue> kvs = null;
    if (!scan.hasFamilies()) {
    kvs = toKeyValue(row, data.get(row), scan.getTimeRange().getMin(), scan.getTimeRange().getMax());
    } else {
    kvs = new ArrayList<KeyValue>();
    for (byte[] family : scan.getFamilyMap().keySet()) {
    if (data.get(row).get(family) == null)
    continue;
    NavigableSet<byte[]> qualifiers = scan.getFamilyMap().get(family);
    if (qualifiers == null)
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers) {
    for (Long timestamp : data.get(row).get(family).get(qualifier).keySet()) {
    if (timestamp < scan.getTimeRange().getMin())
    continue;
    if (timestamp > scan.getTimeRange().getMax())
    continue;
    byte[] value = data.get(row).get(family).get(qualifier).get(timestamp);
    kvs.add(new KeyValue(row, family, qualifier, timestamp, value));
    }
    }
    }
    }
    if (filter != null) {
    filter.reset();
    List<KeyValue> nkvs = new ArrayList<KeyValue>(kvs.size());
    for (KeyValue kv : kvs) {
    if (filter.filterAllRemaining()) {
    break;
    }
    ReturnCode filterResult = filter.filterKeyValue(kv);
    if (filterResult == ReturnCode.INCLUDE) {
    nkvs.add(kv);
    } else if (filterResult == ReturnCode.NEXT_ROW) {
    break;
    }
    }
    kvs = nkvs;
    }
    if (!kvs.isEmpty()) {
    ret.add(new Result(kvs));
    }
    }

    return new ResultScanner() {
    public Iterator<Result> iterator() {
    return ret.iterator();
    }

    public Result[] next(int nbRows) throws IOException {
    ArrayList<Result> resultSets = new ArrayList<Result>(nbRows);
    for (int i = 0; i < nbRows; i++) {
    Result next = next();
    if (next != null) {
    resultSets.add(next);
    } else {
    break;
    }
    }
    return resultSets.toArray(new Result[resultSets.size()]);
    }

    public Result next() throws IOException {
    return iterator().next();
    }

    public void close() {
    }
    };
    }

    @Override
    public ResultScanner getScanner(byte[] family) throws IOException {
    Scan scan = new Scan();
    scan.addFamily(family);
    return getScanner(scan);
    }

    @Override
    public ResultScanner getScanner(byte[] family, byte[] qualifier) throws IOException {
    Scan scan = new Scan();
    scan.addColumn(family, qualifier);
    return getScanner(scan);
    }

    @Override
    public void put(Put put) throws IOException {
    byte[] row = put.getRow();
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowData = forceFind(data, row, new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(Bytes.BYTES_COMPARATOR));
    for (byte[] family : put.getFamilyMap().keySet()) {
    NavigableMap<byte[], NavigableMap<Long, byte[]>> familyData = forceFind(rowData, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(Bytes.BYTES_COMPARATOR));
    for (KeyValue kv : put.getFamilyMap().get(family)) {
    kv.updateLatestStamp(Bytes.toBytes(System.currentTimeMillis()));
    byte[] qualifier = kv.getQualifier();
    NavigableMap<Long, byte[]> qualifierData = forceFind(familyData, qualifier, new TreeMap<Long, byte[]>());
    qualifierData.put(kv.getTimestamp(), kv.getValue());
    }
    }
    }

    /**
    * Helper method to find a key in a map. If key is not found, newObject is
    * added to map and returned
    *
    * @param map
    * map to extract value from
    * @param key
    * key to look for
    * @param newObject
    * set key to this if not found
    * @return found value or newObject if not found
    */
    private <K, V> V forceFind(NavigableMap<K, V> map, K key, V newObject) {
    V data = map.get(key);
    if (data == null) {
    data = newObject;
    map.put(key, data);
    }
    return data;
    }

    @Override
    public void put(List<Put> puts) throws IOException {
    for (Put put : puts)
    put(put);
    }

    /**
    * Checks if the value with given details exists in database, or is
    * non-existent in the case of value being null
    *
    * @param row
    * row
    * @param family
    * family
    * @param qualifier
    * qualifier
    * @param value
    * value
    * @return true if value is not null and exists in db, or value is null and
    * not exists in db, false otherwise
    */
    private boolean check(byte[] row, byte[] family, byte[] qualifier, byte[] value) {
    if (value == null || value.length == 0)
    return !data.containsKey(row) || !data.get(row).containsKey(family) || !data.get(row).get(family).containsKey(qualifier);
    else
    return data.containsKey(row) && data.get(row).containsKey(family) && data.get(row).get(family).containsKey(qualifier) && !data.get(row).get(family).get(qualifier).isEmpty() && Arrays.equals(data.get(row).get(family).get(qualifier).lastEntry().getValue(), value);
    }

    @Override
    public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, byte[] value, Put put) throws IOException {
    if (check(row, family, qualifier, value)) {
    put(put);
    return true;
    }
    return false;
    }

    @Override
    public void delete(Delete delete) throws IOException {
    byte[] row = delete.getRow();
    if (data.get(row) == null)
    return;
    if (delete.getFamilyMap().size() == 0) {
    data.remove(row);
    return;
    }
    for (byte[] family : delete.getFamilyMap().keySet()) {
    if (data.get(row).get(family) == null)
    continue;
    if (delete.getFamilyMap().get(family).isEmpty()) {
    data.get(row).remove(family);
    continue;
    }
    for (KeyValue kv : delete.getFamilyMap().get(family)) {
    data.get(row).get(kv.getFamily()).remove(kv.getQualifier());
    }
    }
    }

    @Override
    public void delete(List<Delete> deletes) throws IOException {
    for (Delete delete : deletes)
    delete(delete);
    }

    @Override
    public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier, byte[] value, Delete delete) throws IOException {
    if (check(row, family, qualifier, value)) {
    delete(delete);
    return true;
    }
    return false;
    }

    @Override
    public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount) throws IOException {
    return incrementColumnValue(row, family, qualifier, amount, true);
    }

    @Override
    public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount, boolean writeToWAL) throws IOException {
    if (check(row, family, qualifier, null)) {
    Put put = new Put(row);
    put.add(family, qualifier, Bytes.toBytes(amount));
    put(put);
    return amount;
    }
    long newValue = Bytes.toLong(data.get(row).get(family).get(qualifier).lastEntry().getValue()) + amount;
    data.get(row).get(family).get(qualifier).put(System.currentTimeMillis(), Bytes.toBytes(newValue));
    return newValue;
    }

    @Override
    public boolean isAutoFlush() {
    return true;
    }

    @Override
    public void flushCommits() throws IOException {
    }

    @Override
    public void close() throws IOException {
    }

    @Override
    public RowLock lockRow(byte[] row) throws IOException {
    return null;
    }

    @Override
    public void unlockRow(RowLock rl) throws IOException {
    }

    @Override
    public Object[] batch(List<Row> actions) throws IOException, InterruptedException {
    List<Result> results = new ArrayList<Result>();
    for (Row r : actions) {
    if (r instanceof Delete) {
    delete((Delete) r);
    continue;
    }
    if (r instanceof Put) {
    put((Put) r);
    continue;
    }
    if (r instanceof Get) {
    results.add(get((Get) r));
    }
    }
    return results.toArray();
    }

    @Override
    public void batch(List<Row> actions, Object[] results) throws IOException, InterruptedException {
    results = batch(actions);
    }

    @Override
    public Result[] get(List<Get> gets) throws IOException {
    List<Result> results = new ArrayList<Result>();
    for (Get g : gets) {
    results.add(get(g));
    }
    return results.toArray(new Result[results.size()]);
    }

    @Override
    public Result increment(Increment increment) throws IOException {
    List<KeyValue> kvs = new ArrayList<KeyValue>();
    Map<byte[], NavigableMap<byte[], Long>> famToVal = increment.getFamilyMap();
    for (Entry<byte[], NavigableMap<byte[], Long>> ef : famToVal.entrySet()) {
    byte[] family = ef.getKey();
    NavigableMap<byte[], Long> qToVal = ef.getValue();
    for (Entry<byte[], Long> eq : qToVal.entrySet()) {
    incrementColumnValue(increment.getRow(), family, eq.getKey(), eq.getValue());
    kvs.add(new KeyValue(increment.getRow(), family, eq.getKey(), Bytes.toBytes(eq.getValue())));
    }
    }
    return new Result(kvs);
    }

    private MockHTable() {
    }

    /**
    * Default way of constructing a MockHTable
    *
    * @return a new MockHTable
    */
    public static MockHTable create() {
    return new MockHTable();
    }

    /**
    * Create a MockHTable with some pre-loaded data. Parameter should be a map
    * of column-to-data mappings of rows. It can be created with a YAML like
    *
    * <pre>
    * rowid:
    * family1:qualifier1: value1
    * family2:qualifier2: value2
    * </pre>
    *
    * @param dump
    * pre-loaded data
    * @return a new MockHTable loaded with given data
    */
    public static MockHTable with(Map<String, Map<String, String>> dump) {
    MockHTable ret = new MockHTable();
    for (String row : dump.keySet()) {
    for (String column : dump.get(row).keySet()) {
    String val = dump.get(row).get(column);
    put(ret, row, column, val);
    }
    }
    return ret;
    }

    /**
    * Helper method of pre-loaders, adds parameters to data.
    *
    * @param ret
    * data to load into
    * @param row
    * rowid
    * @param column
    * family:qualifier encoded value
    * @param val
    * value
    */
    private static void put(MockHTable ret, String row, String column, String val) {
    String[] fq = split(column);
    byte[] family = Bytes.toBytesBinary(fq[0]);
    byte[] qualifier = Bytes.toBytesBinary(fq[1]);
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> families = ret.forceFind(ret.data, Bytes.toBytesBinary(row), new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(Bytes.BYTES_COMPARATOR));
    NavigableMap<byte[], NavigableMap<Long, byte[]>> qualifiers = ret.forceFind(families, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(Bytes.BYTES_COMPARATOR));
    NavigableMap<Long, byte[]> values = ret.forceFind(qualifiers, qualifier, new TreeMap<Long, byte[]>());
    values.put(System.currentTimeMillis(), Bytes.toBytesBinary(val));
    }

    /**
    * Create a MockHTable with some pre-loaded data. Parameter should be an
    * array of string arrays which define every column value individually.
    *
    * <pre>
    * new String[][] {
    * { "&lt;rowid&gt;", "&lt;column&gt;", "&lt;value&gt;" },
    * { "id", "family:qualifier1", "data1" },
    * { "id", "family:qualifier2", "data2" }
    * });
    * </pre>
    *
    * @param dump
    * @return
    */
    public static MockHTable with(String[][] dump) {
    MockHTable ret = new MockHTable();
    for (String[] row : dump) {
    put(ret, row[0], row[1], row[2]);
    }
    return ret;
    }

    /**
    * Column identification helper
    *
    * @param column
    * column name in the format family:qualifier
    * @return <code>{"family", "qualifier"}</code>
    */
    private static String[] split(String column) {
    return new String[] { column.substring(0, column.indexOf(':')), column.substring(column.indexOf(':') + 1) };
    }

    /**
    * Read a value saved in the object. Useful for making assertions in tests.
    *
    * @param rowid
    * rowid of the data to read
    * @param column
    * family:qualifier of the data to read
    * @return value or null if row or column of the row does not exist
    */
    public byte[] read(String rowid, String column) {
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> row = data.get(Bytes.toBytesBinary(rowid));
    if (row == null)
    return null;
    String[] fq = split(column);
    byte[] family = Bytes.toBytesBinary(fq[0]);
    byte[] qualifier = Bytes.toBytesBinary(fq[1]);
    if (!row.containsKey(family))
    return null;
    if (!row.get(family).containsKey(qualifier))
    return null;
    return row.get(family).get(qualifier).lastEntry().getValue();
    }

    public static String toEString(boolean val) {
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }

    public static String toEString(double val) {
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }

    public static String toEString(float val) {
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }

    public static String toEString(int val) {
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }

    public static String toEString(long val) {
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }

    public static String toEString(short val) {
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    }
  12. @agaoglu agaoglu revised this gist Aug 9, 2011. 1 changed file with 16 additions and 6 deletions.
    22 changes: 16 additions & 6 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -245,14 +245,24 @@ public Result getRowOrBefore(byte[] row, byte[] family) throws IOException {
    @Override
    public ResultScanner getScanner(Scan scan) throws IOException {
    final List<Result> ret = new ArrayList<Result>();
    byte[] st = scan.getStartRow();
    byte[] sp = scan.getStopRow();

    for (byte[] row : data.keySet()){
    if (scan.getStartRow() != null && scan.getStartRow().length > 0 &&
    Bytes.toString(scan.getStartRow()).compareTo(Bytes.toString(row)) > 0)
    continue;
    if (scan.getStopRow() != null && scan.getStopRow().length > 0 &&
    Bytes.toString(scan.getStopRow()).compareTo(Bytes.toString(row)) <= 0)
    continue;
    // if row is equal to startRow emit it. When startRow (inclusive) and
    // stopRow (exclusive) is the same, it should not be excluded which would
    // happen w/o this control.
    if (st != null && st.length > 0 &&
    Bytes.toString(st).compareTo(Bytes.toString(row)) != 0) {
    // if row is before startRow do not emit, pass to next row
    if (st != null && st.length > 0 &&
    Bytes.toString(st).compareTo(Bytes.toString(row)) > 0)
    continue;
    // if row is equal to stopRow or after it do not emit, stop iteration
    if (sp != null && sp.length > 0 &&
    Bytes.toString(sp).compareTo(Bytes.toString(row)) <= 0)
    break;
    }

    List<KeyValue> kvs = null;
    if (!scan.hasFamilies()) {
  13. @agaoglu agaoglu revised this gist Aug 4, 2011. 1 changed file with 8 additions and 2 deletions.
    10 changes: 8 additions & 2 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -19,6 +19,7 @@
    import java.util.Map.Entry;
    import java.util.NavigableMap;
    import java.util.NavigableSet;
    import java.util.NoSuchElementException;
    import java.util.TreeMap;
    import java.util.TreeSet;

    @@ -283,8 +284,9 @@ public ResultScanner getScanner(Scan scan) throws IOException {
    }

    return new ResultScanner() {
    private final Iterator<Result> iterator = ret.iterator();
    public Iterator<Result> iterator() {
    return ret.iterator();
    return iterator;
    }
    public Result[] next(int nbRows) throws IOException {
    ArrayList<Result> resultSets = new ArrayList<Result>(nbRows);
    @@ -299,7 +301,11 @@ public Result[] next(int nbRows) throws IOException {
    return resultSets.toArray(new Result[resultSets.size()]);
    }
    public Result next() throws IOException {
    return iterator().next();
    try {
    return iterator().next();
    } catch (NoSuchElementException e) {
    return null;
    }
    }
    public void close() {}
    };
  14. @agaoglu agaoglu revised this gist Aug 3, 2011. 1 changed file with 8 additions and 5 deletions.
    13 changes: 8 additions & 5 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -20,10 +20,12 @@
    import java.util.NavigableMap;
    import java.util.NavigableSet;
    import java.util.TreeMap;
    import java.util.TreeSet;

    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.hbase.HTableDescriptor;
    import org.apache.hadoop.hbase.KeyValue;
    import org.apache.hadoop.hbase.KeyValue.KVComparator;
    import org.apache.hadoop.hbase.client.Delete;
    import org.apache.hadoop.hbase.client.Get;
    import org.apache.hadoop.hbase.client.HTableInterface;
    @@ -114,7 +116,6 @@
    * }
    * </pre>
    * <p>
    * TODO: Don't know if timestamps work correctly
    *
    * @author erdem
    *
    @@ -153,18 +154,19 @@ private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], Naviga
    * @return List of KeyValue's
    */
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata, long timestampStart, long timestampEnd){
    List<KeyValue> ret = new ArrayList<KeyValue>();
    TreeSet<KeyValue> ret = new TreeSet<KeyValue>(new KVComparator());
    for (byte[] family : rowdata.keySet())
    for (byte[] qualifier : rowdata.get(family).keySet())
    for (Long timestamp : rowdata.get(family).get(qualifier).keySet()){
    for (Entry<Long, byte[]> tsToVal : rowdata.get(family).get(qualifier).entrySet()){
    Long timestamp = tsToVal.getKey();
    if (timestamp < timestampStart)
    continue;
    if (timestamp > timestampEnd)
    continue;
    byte[] value = rowdata.get(family).get(qualifier).get(timestamp);
    byte[] value = tsToVal.getValue();
    ret.add(new KeyValue(row, family, qualifier, timestamp, value));
    }
    return ret;
    return new ArrayList<KeyValue>(ret);
    }

    /**
    @@ -325,6 +327,7 @@ public void put(Put put) throws IOException {
    for (byte[] family : put.getFamilyMap().keySet()){
    NavigableMap<byte[], NavigableMap<Long, byte[]>> familyData = forceFind(rowData, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(Bytes.BYTES_COMPARATOR));
    for (KeyValue kv : put.getFamilyMap().get(family)){
    kv.updateLatestStamp(Bytes.toBytes(System.currentTimeMillis()));
    byte[] qualifier = kv.getQualifier();
    NavigableMap<Long, byte[]> qualifierData = forceFind(familyData, qualifier, new TreeMap<Long, byte[]>());
    qualifierData.put(kv.getTimestamp(), kv.getValue());
  15. @agaoglu agaoglu revised this gist Jul 5, 2011. 1 changed file with 29 additions and 19 deletions.
    48 changes: 29 additions & 19 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -11,10 +11,8 @@
    */

    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Comparator;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    @@ -36,6 +34,7 @@
    import org.apache.hadoop.hbase.client.Row;
    import org.apache.hadoop.hbase.client.RowLock;
    import org.apache.hadoop.hbase.client.Scan;
    import org.apache.hadoop.hbase.filter.Filter.ReturnCode;
    import org.apache.hadoop.hbase.util.Bytes;

    /**
    @@ -199,27 +198,38 @@ public Result get(Get get) throws IOException {
    if (!exists(get))
    return new Result();
    byte[] row = get.getRow();
    if (!get.hasFamilies()) {
    return new Result(toKeyValue(row, data.get(row)));
    }
    List<KeyValue> kvs = new ArrayList<KeyValue>();
    for (byte[] family : get.getFamilyMap().keySet()){
    if (data.get(row).get(family) == null)
    continue;
    NavigableSet<byte[]> qualifiers = get.getFamilyMap().get(family);
    if (qualifiers == null)
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers){
    if (qualifier == null)
    qualifier = "".getBytes();
    if (!data.get(row).containsKey(family) ||
    !data.get(row).get(family).containsKey(qualifier) ||
    data.get(row).get(family).get(qualifier).isEmpty())
    if (!get.hasFamilies()) {
    kvs = toKeyValue(row, data.get(row));
    } else {
    for (byte[] family : get.getFamilyMap().keySet()){
    if (data.get(row).get(family) == null)
    continue;
    Entry<Long, byte[]> timestampAndValue = data.get(row).get(family).get(qualifier).lastEntry();
    kvs.add(new KeyValue(row,family, qualifier, timestampAndValue.getKey(), timestampAndValue.getValue()));
    NavigableSet<byte[]> qualifiers = get.getFamilyMap().get(family);
    if (qualifiers == null)
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers){
    if (qualifier == null)
    qualifier = "".getBytes();
    if (!data.get(row).containsKey(family) ||
    !data.get(row).get(family).containsKey(qualifier) ||
    data.get(row).get(family).get(qualifier).isEmpty())
    continue;
    Entry<Long, byte[]> timestampAndValue = data.get(row).get(family).get(qualifier).lastEntry();
    kvs.add(new KeyValue(row,family, qualifier, timestampAndValue.getKey(), timestampAndValue.getValue()));
    }
    }
    }
    if (get.getFilter() != null) {
    List<KeyValue> nkvs = new ArrayList<KeyValue>();
    for (KeyValue kv : kvs) {
    if (get.getFilter().filterKeyValue(kv) == ReturnCode.INCLUDE) {
    nkvs.add(kv);
    }
    }
    kvs = nkvs;
    }

    return new Result(kvs);
    }

  16. Erdem Agaoglu revised this gist Jun 14, 2011. 1 changed file with 5 additions and 16 deletions.
    21 changes: 5 additions & 16 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -121,21 +121,10 @@
    *
    */
    public class MockHTable implements HTableInterface {
    /**
    * Simple class to help create a TreeMap with byte[] key.
    * @author erdem
    *
    */
    private static class BC implements Comparator<byte[]> {
    public int compare(byte[] o1, byte[] o2) {
    return ByteBuffer.wrap(o1).compareTo(ByteBuffer.wrap(o2));
    }
    }

    /**
    * This is all the data for a MockHTable instance
    */
    private NavigableMap<byte[], NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>> data = new TreeMap<byte[], NavigableMap<byte[],NavigableMap<byte[],NavigableMap<Long,byte[]>>>>(new BC());
    private NavigableMap<byte[], NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>> data = new TreeMap<byte[], NavigableMap<byte[],NavigableMap<byte[],NavigableMap<Long,byte[]>>>>(Bytes.BYTES_COMPARATOR);

    /**
    * Helper method to convert some data into a list of KeyValue's
    @@ -322,9 +311,9 @@ public ResultScanner getScanner(byte[] family, byte[] qualifier)
    @Override
    public void put(Put put) throws IOException {
    byte[] row = put.getRow();
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowData = forceFind(data, row, new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(new BC()));
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowData = forceFind(data, row, new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(Bytes.BYTES_COMPARATOR));
    for (byte[] family : put.getFamilyMap().keySet()){
    NavigableMap<byte[], NavigableMap<Long, byte[]>> familyData = forceFind(rowData, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(new BC()));
    NavigableMap<byte[], NavigableMap<Long, byte[]>> familyData = forceFind(rowData, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(Bytes.BYTES_COMPARATOR));
    for (KeyValue kv : put.getFamilyMap().get(family)){
    byte[] qualifier = kv.getQualifier();
    NavigableMap<Long, byte[]> qualifierData = forceFind(familyData, qualifier, new TreeMap<Long, byte[]>());
    @@ -583,8 +572,8 @@ private static void put(MockHTable ret, String row, String column,
    String[] fq = split(column);
    byte[] family = Bytes.toBytesBinary(fq[0]);
    byte[] qualifier = Bytes.toBytesBinary(fq[1]);
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> families = ret.forceFind(ret.data, Bytes.toBytesBinary(row), new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(new BC()));
    NavigableMap<byte[], NavigableMap<Long, byte[]>> qualifiers = ret.forceFind(families, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(new BC()));
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> families = ret.forceFind(ret.data, Bytes.toBytesBinary(row), new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(Bytes.BYTES_COMPARATOR));
    NavigableMap<byte[], NavigableMap<Long, byte[]>> qualifiers = ret.forceFind(families, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(Bytes.BYTES_COMPARATOR));
    NavigableMap<Long, byte[]> values = ret.forceFind(qualifiers, qualifier, new TreeMap<Long, byte[]>());
    values.put(System.currentTimeMillis(), Bytes.toBytesBinary(val));
    }
  17. Erdem Agaoglu revised this gist Mar 25, 2011. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -246,10 +246,10 @@ public ResultScanner getScanner(Scan scan) throws IOException {

    for (byte[] row : data.keySet()){
    if (scan.getStartRow() != null && scan.getStartRow().length > 0 &&
    ByteBuffer.wrap(scan.getStartRow()).compareTo(ByteBuffer.wrap(row)) >= 0)
    Bytes.toString(scan.getStartRow()).compareTo(Bytes.toString(row)) > 0)
    continue;
    if (scan.getStopRow() != null && scan.getStopRow().length > 0 &&
    ByteBuffer.wrap(scan.getStopRow()).compareTo(ByteBuffer.wrap(row)) < 0)
    Bytes.toString(scan.getStopRow()).compareTo(Bytes.toString(row)) <= 0)
    continue;

    List<KeyValue> kvs = null;
  18. Erdem Agaoglu revised this gist Feb 10, 2011. 1 changed file with 56 additions and 3 deletions.
    59 changes: 56 additions & 3 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -29,9 +29,11 @@
    import org.apache.hadoop.hbase.client.Delete;
    import org.apache.hadoop.hbase.client.Get;
    import org.apache.hadoop.hbase.client.HTableInterface;
    import org.apache.hadoop.hbase.client.Increment;
    import org.apache.hadoop.hbase.client.Put;
    import org.apache.hadoop.hbase.client.Result;
    import org.apache.hadoop.hbase.client.ResultScanner;
    import org.apache.hadoop.hbase.client.Row;
    import org.apache.hadoop.hbase.client.RowLock;
    import org.apache.hadoop.hbase.client.Scan;
    import org.apache.hadoop.hbase.util.Bytes;
    @@ -244,10 +246,10 @@ public ResultScanner getScanner(Scan scan) throws IOException {

    for (byte[] row : data.keySet()){
    if (scan.getStartRow() != null && scan.getStartRow().length > 0 &&
    ByteBuffer.wrap(scan.getStartRow()).compareTo(ByteBuffer.wrap(row)) > 0)
    ByteBuffer.wrap(scan.getStartRow()).compareTo(ByteBuffer.wrap(row)) >= 0)
    continue;
    if (scan.getStopRow() != null && scan.getStopRow().length > 0 &&
    ByteBuffer.wrap(scan.getStopRow()).compareTo(ByteBuffer.wrap(row)) <= 0)
    ByteBuffer.wrap(scan.getStopRow()).compareTo(ByteBuffer.wrap(row)) < 0)
    continue;

    List<KeyValue> kvs = null;
    @@ -477,6 +479,58 @@ public RowLock lockRow(byte[] row) throws IOException {
    public void unlockRow(RowLock rl) throws IOException {
    }

    @Override
    public Object[] batch(List<Row> actions)
    throws IOException,
    InterruptedException {
    List<Result> results = new ArrayList<Result>();
    for (Row r : actions) {
    if (r instanceof Delete) {
    delete((Delete) r);
    continue;
    }
    if (r instanceof Put) {
    put((Put) r);
    continue;
    }
    if (r instanceof Get) {
    results.add(get((Get) r));
    }
    }
    return results.toArray();
    }

    @Override
    public void batch(List<Row> actions, Object[] results)
    throws IOException,
    InterruptedException {
    results = batch(actions);
    }

    @Override
    public Result[] get(List<Get> gets) throws IOException {
    List<Result> results = new ArrayList<Result>();
    for (Get g : gets) {
    results.add(get(g));
    }
    return results.toArray(new Result[results.size()]);
    }

    @Override
    public Result increment(Increment increment) throws IOException {
    List<KeyValue> kvs = new ArrayList<KeyValue>();
    Map<byte[], NavigableMap<byte[], Long>> famToVal = increment.getFamilyMap();
    for (Entry<byte[], NavigableMap<byte[], Long>> ef : famToVal.entrySet()) {
    byte[] family = ef.getKey();
    NavigableMap<byte[], Long> qToVal = ef.getValue();
    for (Entry<byte[], Long> eq : qToVal.entrySet()) {
    incrementColumnValue(increment.getRow(), family, eq.getKey(), eq.getValue());
    kvs.add(new KeyValue(increment.getRow(), family, eq.getKey(), Bytes.toBytes(eq.getValue())));
    }
    }
    return new Result(kvs);
    }

    private MockHTable(){}

    /**
    @@ -612,5 +666,4 @@ public static String toEString(long val){
    public static String toEString(short val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }

    }
  19. Erdem Agaoglu revised this gist Oct 15, 2010. 1 changed file with 6 additions and 1 deletion.
    7 changes: 6 additions & 1 deletion MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -213,6 +213,8 @@ public Result get(Get get) throws IOException {
    }
    List<KeyValue> kvs = new ArrayList<KeyValue>();
    for (byte[] family : get.getFamilyMap().keySet()){
    if (data.get(row).get(family) == null)
    continue;
    NavigableSet<byte[]> qualifiers = get.getFamilyMap().get(family);
    if (qualifiers == null)
    qualifiers = data.get(row).get(family).navigableKeySet();
    @@ -254,6 +256,8 @@ public ResultScanner getScanner(Scan scan) throws IOException {
    } else {
    kvs = new ArrayList<KeyValue>();
    for (byte[] family : scan.getFamilyMap().keySet()){
    if (data.get(row).get(family) == null)
    continue;
    NavigableSet<byte[]> qualifiers = scan.getFamilyMap().get(family);
    if (qualifiers == null)
    qualifiers = data.get(row).get(family).navigableKeySet();
    @@ -271,7 +275,8 @@ public ResultScanner getScanner(Scan scan) throws IOException {
    }
    if (scan.getFilter() != null)
    scan.getFilter().filterRow(kvs);
    ret.add(new Result(kvs));
    if (!kvs.isEmpty())
    ret.add(new Result(kvs));
    }

    return new ResultScanner() {
  20. Erdem Agaoglu revised this gist Oct 6, 2010. 1 changed file with 12 additions and 0 deletions.
    12 changes: 12 additions & 0 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,15 @@
    /**
    * This file is licensed to you 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.
    */

    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.util.ArrayList;
  21. @agaoglu agaoglu created this gist Oct 6, 2010.
    599 changes: 599 additions & 0 deletions MockHTable.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,599 @@
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Comparator;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.NavigableMap;
    import java.util.NavigableSet;
    import java.util.TreeMap;

    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.hbase.HTableDescriptor;
    import org.apache.hadoop.hbase.KeyValue;
    import org.apache.hadoop.hbase.client.Delete;
    import org.apache.hadoop.hbase.client.Get;
    import org.apache.hadoop.hbase.client.HTableInterface;
    import org.apache.hadoop.hbase.client.Put;
    import org.apache.hadoop.hbase.client.Result;
    import org.apache.hadoop.hbase.client.ResultScanner;
    import org.apache.hadoop.hbase.client.RowLock;
    import org.apache.hadoop.hbase.client.Scan;
    import org.apache.hadoop.hbase.util.Bytes;

    /**
    * Mock implementation of HTableInterface. Holds any supplied data in a
    * multi-dimensional NavigableMap which acts as a in-memory database. Useful for
    * testing classes that operate on data using an HTableInterface.
    * <p>
    * Instances should be get using <code>MockHTable.create()</code>. So while a
    * DAO with a saving operation like
    *
    * <pre>
    * public class MyDAO {
    * private HTableInterface table;
    *
    * public MyDAO(HTableInterface table) {
    * this.table = table;
    * }
    *
    * public void saveData(byte[] id, byte[] data) throws IOException{
    * Put put = new Put(id)
    * put.add(family, qualifier, data);
    * table.put(put);
    * }
    * }
    * </pre>
    * <p>
    * is used in production like
    *
    * <pre>
    * MyDAO(new HTable(conf, tableName)).saveData(id, data);
    * </pre>
    * <p>
    * can be tested like
    *
    * <pre>
    * &#064;Test
    * public void testSave() {
    * MockHTable table = MockHTable.create();
    * MyDAO(table).saveData(id, data);
    * Get get = new Get(id);
    * Result result = table.get(get);
    * assertArrayEquals(data, result.getValue(family, qualifier));
    * }
    * </pre>
    * <p>
    * MockHTable instances can also be initialized with pre-loaded data using one
    * of the String[][] or Map<String, Map<String, String>> data formats. While
    * String[][] parameter lets directly loading data from source code, Map can be
    * generated from a YAML document, using a parser.
    *
    * <pre>
    * // String[][]
    * MockHTable table = MockHTable.with(new String[][] {
    * { &quot;&lt;rowid&gt;&quot;, &quot;&lt;column&gt;&quot;, &quot;&lt;value&gt;&quot; },
    * { &quot;id&quot;, &quot;family:qualifier1&quot;, &quot;data1&quot; },
    * { &quot;id&quot;, &quot;family:qualifier2&quot;, &quot;data2&quot; }
    * });
    * // YAML
    * String database = "id:\n family:qualifier1: data1\n family:qualifier2: data2\n";
    * MockHTable table = MockHTable.with((Map<String, Map<String, String>) new Yaml().load(database));
    * </pre>
    * <p>
    * If value is not supposed to be a String, but an int, double or anything,
    * <code>MockHTable.toEString()</code> can be used to turn it into a String.
    *
    * <p>
    * In order to simplify assertions for tests that should put anything into
    * database, MockHTable.read() works with two parameters (id and column) and
    * returns anything written to that row/column. So, previous test can be reduced to
    *
    * <pre>
    * &#064;Test
    * public void testSave() {
    * MockHTable table = MockHTable.create();
    * MyDAO(table).saveData(id, data);
    * assertArrayEquals(data, table.read(id, "family:qualifier"));
    * }
    * </pre>
    * <p>
    * TODO: Don't know if timestamps work correctly
    *
    * @author erdem
    *
    */
    public class MockHTable implements HTableInterface {
    /**
    * Simple class to help create a TreeMap with byte[] key.
    * @author erdem
    *
    */
    private static class BC implements Comparator<byte[]> {
    public int compare(byte[] o1, byte[] o2) {
    return ByteBuffer.wrap(o1).compareTo(ByteBuffer.wrap(o2));
    }
    }

    /**
    * This is all the data for a MockHTable instance
    */
    private NavigableMap<byte[], NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>> data = new TreeMap<byte[], NavigableMap<byte[],NavigableMap<byte[],NavigableMap<Long,byte[]>>>>(new BC());

    /**
    * Helper method to convert some data into a list of KeyValue's
    *
    * @param row
    * row value of the KeyValue's
    * @param rowdata
    * data to decode
    * @return List of KeyValue's
    */
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata){
    return toKeyValue(row, rowdata, 0, Long.MAX_VALUE);
    }

    /**
    * Helper method to convert some data into a list of KeyValue's with timestamp
    * constraint
    *
    * @param row
    * row value of the KeyValue's
    * @param rowdata
    * data to decode
    * @param timestampStart
    * start of the timestamp constraint
    * @param timestampEnd
    * end of the timestamp constraint
    * @return List of KeyValue's
    */
    private static List<KeyValue> toKeyValue(byte[] row, NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowdata, long timestampStart, long timestampEnd){
    List<KeyValue> ret = new ArrayList<KeyValue>();
    for (byte[] family : rowdata.keySet())
    for (byte[] qualifier : rowdata.get(family).keySet())
    for (Long timestamp : rowdata.get(family).get(qualifier).keySet()){
    if (timestamp < timestampStart)
    continue;
    if (timestamp > timestampEnd)
    continue;
    byte[] value = rowdata.get(family).get(qualifier).get(timestamp);
    ret.add(new KeyValue(row, family, qualifier, timestamp, value));
    }
    return ret;
    }

    /**
    * Clients should not rely on table names so this returns null.
    * @return null
    */
    @Override
    public byte[] getTableName() { return null; }

    /**
    * No configuration needed to work so this returns null.
    * @return null
    */
    @Override
    public Configuration getConfiguration() { return null; }

    /**
    * No table descriptor needed so this returns null.
    * @return null
    */
    @Override
    public HTableDescriptor getTableDescriptor() { return null; }

    @Override
    public boolean exists(Get get) throws IOException {
    return data.containsKey(get.getRow());
    }

    @Override
    public Result get(Get get) throws IOException {
    if (!exists(get))
    return new Result();
    byte[] row = get.getRow();
    if (!get.hasFamilies()) {
    return new Result(toKeyValue(row, data.get(row)));
    }
    List<KeyValue> kvs = new ArrayList<KeyValue>();
    for (byte[] family : get.getFamilyMap().keySet()){
    NavigableSet<byte[]> qualifiers = get.getFamilyMap().get(family);
    if (qualifiers == null)
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers){
    if (qualifier == null)
    qualifier = "".getBytes();
    if (!data.get(row).containsKey(family) ||
    !data.get(row).get(family).containsKey(qualifier) ||
    data.get(row).get(family).get(qualifier).isEmpty())
    continue;
    Entry<Long, byte[]> timestampAndValue = data.get(row).get(family).get(qualifier).lastEntry();
    kvs.add(new KeyValue(row,family, qualifier, timestampAndValue.getKey(), timestampAndValue.getValue()));
    }
    }
    return new Result(kvs);
    }

    @Override
    public Result getRowOrBefore(byte[] row, byte[] family) throws IOException {
    // FIXME: implement
    return null;
    }

    @Override
    public ResultScanner getScanner(Scan scan) throws IOException {
    final List<Result> ret = new ArrayList<Result>();

    for (byte[] row : data.keySet()){
    if (scan.getStartRow() != null && scan.getStartRow().length > 0 &&
    ByteBuffer.wrap(scan.getStartRow()).compareTo(ByteBuffer.wrap(row)) > 0)
    continue;
    if (scan.getStopRow() != null && scan.getStopRow().length > 0 &&
    ByteBuffer.wrap(scan.getStopRow()).compareTo(ByteBuffer.wrap(row)) <= 0)
    continue;

    List<KeyValue> kvs = null;
    if (!scan.hasFamilies()) {
    kvs = toKeyValue(row, data.get(row), scan.getTimeRange().getMin(), scan.getTimeRange().getMax());
    } else {
    kvs = new ArrayList<KeyValue>();
    for (byte[] family : scan.getFamilyMap().keySet()){
    NavigableSet<byte[]> qualifiers = scan.getFamilyMap().get(family);
    if (qualifiers == null)
    qualifiers = data.get(row).get(family).navigableKeySet();
    for (byte[] qualifier : qualifiers){
    for (Long timestamp : data.get(row).get(family).get(qualifier).keySet()){
    if (timestamp < scan.getTimeRange().getMin())
    continue;
    if (timestamp > scan.getTimeRange().getMax())
    continue;
    byte[] value = data.get(row).get(family).get(qualifier).get(timestamp);
    kvs.add(new KeyValue(row, family, qualifier, timestamp, value));
    }
    }
    }
    }
    if (scan.getFilter() != null)
    scan.getFilter().filterRow(kvs);
    ret.add(new Result(kvs));
    }

    return new ResultScanner() {
    public Iterator<Result> iterator() {
    return ret.iterator();
    }
    public Result[] next(int nbRows) throws IOException {
    ArrayList<Result> resultSets = new ArrayList<Result>(nbRows);
    for(int i = 0; i < nbRows; i++) {
    Result next = next();
    if (next != null) {
    resultSets.add(next);
    } else {
    break;
    }
    }
    return resultSets.toArray(new Result[resultSets.size()]);
    }
    public Result next() throws IOException {
    return iterator().next();
    }
    public void close() {}
    };
    }

    @Override
    public ResultScanner getScanner(byte[] family) throws IOException {
    Scan scan = new Scan();
    scan.addFamily(family);
    return getScanner(scan);
    }

    @Override
    public ResultScanner getScanner(byte[] family, byte[] qualifier)
    throws IOException {
    Scan scan = new Scan();
    scan.addColumn(family, qualifier);
    return getScanner(scan);
    }

    @Override
    public void put(Put put) throws IOException {
    byte[] row = put.getRow();
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> rowData = forceFind(data, row, new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(new BC()));
    for (byte[] family : put.getFamilyMap().keySet()){
    NavigableMap<byte[], NavigableMap<Long, byte[]>> familyData = forceFind(rowData, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(new BC()));
    for (KeyValue kv : put.getFamilyMap().get(family)){
    byte[] qualifier = kv.getQualifier();
    NavigableMap<Long, byte[]> qualifierData = forceFind(familyData, qualifier, new TreeMap<Long, byte[]>());
    qualifierData.put(kv.getTimestamp(), kv.getValue());
    }
    }
    }

    /**
    * Helper method to find a key in a map. If key is not found, newObject is
    * added to map and returned
    *
    * @param map
    * map to extract value from
    * @param key
    * key to look for
    * @param newObject
    * set key to this if not found
    * @return found value or newObject if not found
    */
    private <K, V> V forceFind(NavigableMap<K, V> map, K key, V newObject){
    V data = map.get(key);
    if (data == null){
    data = newObject;
    map.put(key, data);
    }
    return data;
    }

    @Override
    public void put(List<Put> puts) throws IOException {
    for (Put put : puts)
    put(put);
    }

    /**
    * Checks if the value with given details exists in database, or is
    * non-existent in the case of value being null
    *
    * @param row
    * row
    * @param family
    * family
    * @param qualifier
    * qualifier
    * @param value
    * value
    * @return true if value is not null and exists in db, or value is null and
    * not exists in db, false otherwise
    */
    private boolean check(byte[] row, byte[] family, byte[] qualifier, byte[] value){
    if (value == null || value.length == 0)
    return ! data.containsKey(row) ||
    ! data.get(row).containsKey(family) ||
    ! data.get(row).get(family).containsKey(qualifier);
    else
    return data.containsKey(row) &&
    data.get(row).containsKey(family) &&
    data.get(row).get(family).containsKey(qualifier) &&
    ! data.get(row).get(family).get(qualifier).isEmpty() &&
    Arrays.equals(data.get(row).get(family).get(qualifier).lastEntry().getValue(), value);
    }

    @Override
    public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier,
    byte[] value, Put put) throws IOException {
    if (check(row, family, qualifier, value)){
    put(put);
    return true;
    }
    return false;
    }

    @Override
    public void delete(Delete delete) throws IOException {
    byte[] row = delete.getRow();
    if (data.get(row) == null)
    return;
    if (delete.getFamilyMap().size() == 0){
    data.remove(row);
    return;
    }
    for (byte[] family : delete.getFamilyMap().keySet()){
    if (data.get(row).get(family) == null)
    continue;
    if (delete.getFamilyMap().get(family).isEmpty()){
    data.get(row).remove(family);
    continue;
    }
    for (KeyValue kv : delete.getFamilyMap().get(family)){
    data.get(row).get(kv.getFamily()).remove(kv.getQualifier());
    }
    }
    }

    @Override
    public void delete(List<Delete> deletes) throws IOException {
    for (Delete delete : deletes)
    delete(delete);
    }

    @Override
    public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
    byte[] value, Delete delete) throws IOException {
    if(check(row, family, qualifier, value)){
    delete(delete);
    return true;
    }
    return false;
    }

    @Override
    public long incrementColumnValue(byte[] row, byte[] family,
    byte[] qualifier, long amount) throws IOException {
    return incrementColumnValue(row, family, qualifier, amount, true);
    }

    @Override
    public long incrementColumnValue(byte[] row, byte[] family,
    byte[] qualifier, long amount, boolean writeToWAL) throws IOException {
    if (check(row, family, qualifier, null)){
    Put put = new Put(row);
    put.add(family, qualifier, Bytes.toBytes(amount));
    put(put);
    return amount;
    }
    long newValue = Bytes.toLong(data.get(row).get(family).get(qualifier).lastEntry().getValue())+amount;
    data.get(row).get(family).get(qualifier).put(System.currentTimeMillis(),
    Bytes.toBytes(newValue));
    return newValue;
    }

    @Override
    public boolean isAutoFlush() {
    return true;
    }

    @Override
    public void flushCommits() throws IOException {
    }

    @Override
    public void close() throws IOException {
    }

    @Override
    public RowLock lockRow(byte[] row) throws IOException {
    return null;
    }

    @Override
    public void unlockRow(RowLock rl) throws IOException {
    }

    private MockHTable(){}

    /**
    * Default way of constructing a MockHTable
    * @return a new MockHTable
    */
    public static MockHTable create(){
    return new MockHTable();
    }

    /**
    * Create a MockHTable with some pre-loaded data. Parameter should be a map of
    * column-to-data mappings of rows. It can be created with a YAML like
    *
    * <pre>
    * rowid:
    * family1:qualifier1: value1
    * family2:qualifier2: value2
    * </pre>
    *
    * @param dump
    * pre-loaded data
    * @return a new MockHTable loaded with given data
    */
    public static MockHTable with(Map<String, Map<String, String>> dump){
    MockHTable ret = new MockHTable();
    for (String row : dump.keySet()){
    for (String column : dump.get(row).keySet()){
    String val = dump.get(row).get(column);
    put(ret, row, column, val);
    }
    }
    return ret;
    }

    /**
    * Helper method of pre-loaders, adds parameters to data.
    *
    * @param ret
    * data to load into
    * @param row
    * rowid
    * @param column
    * family:qualifier encoded value
    * @param val
    * value
    */
    private static void put(MockHTable ret, String row, String column,
    String val) {
    String[] fq = split(column);
    byte[] family = Bytes.toBytesBinary(fq[0]);
    byte[] qualifier = Bytes.toBytesBinary(fq[1]);
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> families = ret.forceFind(ret.data, Bytes.toBytesBinary(row), new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(new BC()));
    NavigableMap<byte[], NavigableMap<Long, byte[]>> qualifiers = ret.forceFind(families, family, new TreeMap<byte[], NavigableMap<Long, byte[]>>(new BC()));
    NavigableMap<Long, byte[]> values = ret.forceFind(qualifiers, qualifier, new TreeMap<Long, byte[]>());
    values.put(System.currentTimeMillis(), Bytes.toBytesBinary(val));
    }

    /**
    * Create a MockHTable with some pre-loaded data. Parameter should be an array
    * of string arrays which define every column value individually.
    *
    * <pre>
    * new String[][] {
    * { "&lt;rowid&gt;", "&lt;column&gt;", "&lt;value&gt;" },
    * { "id", "family:qualifier1", "data1" },
    * { "id", "family:qualifier2", "data2" }
    * });
    * </pre>
    *
    * @param dump
    * @return
    */
    public static MockHTable with(String[][] dump){
    MockHTable ret = new MockHTable();
    for(String[] row : dump){
    put(ret, row[0], row[1], row[2]);
    }
    return ret;
    }

    /**
    * Column identification helper
    *
    * @param column
    * column name in the format family:qualifier
    * @return <code>{"family", "qualifier"}</code>
    */
    private static String[] split(String column){
    return new String[]{
    column.substring(0, column.indexOf(':')),
    column.substring(column.indexOf(':')+1)};
    }

    /**
    * Read a value saved in the object. Useful for making assertions in tests.
    *
    * @param rowid
    * rowid of the data to read
    * @param column
    * family:qualifier of the data to read
    * @return value or null if row or column of the row does not exist
    */
    public byte[] read(String rowid, String column){
    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> row = data.get(Bytes.toBytesBinary(rowid));
    if (row == null)
    return null;
    String[] fq = split(column);
    byte[] family = Bytes.toBytesBinary(fq[0]);
    byte[] qualifier = Bytes.toBytesBinary(fq[1]);
    if (!row.containsKey(family))
    return null;
    if (!row.get(family).containsKey(qualifier))
    return null;
    return row.get(family).get(qualifier).lastEntry().getValue();
    }

    public static String toEString(boolean val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(double val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(float val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(int val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(long val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }
    public static String toEString(short val){
    return Bytes.toStringBinary(Bytes.toBytes(val));
    }

    }