You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@accumulo.apache.org by kt...@apache.org on 2013/07/31 18:52:03 UTC

[1/3] git commit: ACCUMULO-1626 fixed equals method in mutation

Updated Branches:
  refs/heads/master 0b85105a4 -> a3d2673b3


ACCUMULO-1626 fixed equals method in mutation


Project: http://git-wip-us.apache.org/repos/asf/accumulo/repo
Commit: http://git-wip-us.apache.org/repos/asf/accumulo/commit/acc4fd16
Tree: http://git-wip-us.apache.org/repos/asf/accumulo/tree/acc4fd16
Diff: http://git-wip-us.apache.org/repos/asf/accumulo/diff/acc4fd16

Branch: refs/heads/master
Commit: acc4fd16f6390579c82e9635a3fa614f3822bfba
Parents: 3e97167
Author: Keith Turner <ke...@deenlo.com>
Authored: Wed Jul 31 11:48:15 2013 -0400
Committer: Keith Turner <ke...@deenlo.com>
Committed: Wed Jul 31 11:48:15 2013 -0400

----------------------------------------------------------------------
 .../org/apache/accumulo/core/data/Mutation.java |  1 +
 .../apache/accumulo/core/data/MutationTest.java | 69 ++++++++++++++++++++
 2 files changed, 70 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/accumulo/blob/acc4fd16/src/core/src/main/java/org/apache/accumulo/core/data/Mutation.java
----------------------------------------------------------------------
diff --git a/src/core/src/main/java/org/apache/accumulo/core/data/Mutation.java b/src/core/src/main/java/org/apache/accumulo/core/data/Mutation.java
index 4e21b6d..3979da9 100644
--- a/src/core/src/main/java/org/apache/accumulo/core/data/Mutation.java
+++ b/src/core/src/main/java/org/apache/accumulo/core/data/Mutation.java
@@ -495,6 +495,7 @@ public class Mutation implements Writable {
   
   public boolean equals(Mutation m) {
     serialize();
+    m.serialize();
     if (Arrays.equals(row, m.row) && entries == m.entries && Arrays.equals(data, m.data)) {
       if (values == null && m.values == null)
         return true;

http://git-wip-us.apache.org/repos/asf/accumulo/blob/acc4fd16/src/core/src/test/java/org/apache/accumulo/core/data/MutationTest.java
----------------------------------------------------------------------
diff --git a/src/core/src/test/java/org/apache/accumulo/core/data/MutationTest.java b/src/core/src/test/java/org/apache/accumulo/core/data/MutationTest.java
index d50c15e..38ddcad 100644
--- a/src/core/src/test/java/org/apache/accumulo/core/data/MutationTest.java
+++ b/src/core/src/test/java/org/apache/accumulo/core/data/MutationTest.java
@@ -347,4 +347,73 @@ public class MutationTest extends TestCase {
     assertEquals(m2.getUpdates().get(1), "cf2", "cq2", "cv2", 0l, false, false, "v2");
     
   }
+  
+  public void testEquals() {
+    Mutation m1 = new Mutation("r1");
+    
+    m1.put("cf1", "cq1", "v1");
+    m1.put("cf1", "cq1", new ColumnVisibility("A&B"), "v2");
+    m1.put("cf1", "cq1", 3, "v3");
+    m1.put("cf1", "cq1", new ColumnVisibility("A&B&C"), 4, "v4");
+    m1.putDelete("cf2", "cf3");
+    m1.putDelete("cf2", "cf4", 3);
+    m1.putDelete("cf2", "cf4", new ColumnVisibility("A&B&C"), 3);
+    
+    // m2 has same data as m1
+    Mutation m2 = new Mutation("r1");
+    
+    m2.put("cf1", "cq1", "v1");
+    m2.put("cf1", "cq1", new ColumnVisibility("A&B"), "v2");
+    m2.put("cf1", "cq1", 3, "v3");
+    m2.put("cf1", "cq1", new ColumnVisibility("A&B&C"), 4, "v4");
+    m2.putDelete("cf2", "cf3");
+    m2.putDelete("cf2", "cf4", 3);
+    m2.putDelete("cf2", "cf4", new ColumnVisibility("A&B&C"), 3);
+    
+    // m3 has differnt row than m1
+    Mutation m3 = new Mutation("r2");
+    
+    m3.put("cf1", "cq1", "v1");
+    m3.put("cf1", "cq1", new ColumnVisibility("A&B"), "v2");
+    m3.put("cf1", "cq1", 3, "v3");
+    m3.put("cf1", "cq1", new ColumnVisibility("A&B&C"), 4, "v4");
+    m3.putDelete("cf2", "cf3");
+    m3.putDelete("cf2", "cf4", 3);
+    m3.putDelete("cf2", "cf4", new ColumnVisibility("A&B&C"), 3);
+
+    // m4 has a different column than m1
+    Mutation m4 = new Mutation("r1");
+    
+    m4.put("cf2", "cq1", "v1");
+    m4.put("cf1", "cq1", new ColumnVisibility("A&B"), "v2");
+    m4.put("cf1", "cq1", 3, "v3");
+    m4.put("cf1", "cq1", new ColumnVisibility("A&B&C"), 4, "v4");
+    m4.putDelete("cf2", "cf3");
+    m4.putDelete("cf2", "cf4", 3);
+    m4.putDelete("cf2", "cf4", new ColumnVisibility("A&B&C"), 3);
+    
+    // m5 has a different value than m1
+    Mutation m5 = new Mutation("r1");
+    
+    m5.put("cf1", "cq1", "v1");
+    m5.put("cf1", "cq1", new ColumnVisibility("A&B"), "v2");
+    m5.put("cf1", "cq1", 3, "v4");
+    m5.put("cf1", "cq1", new ColumnVisibility("A&B&C"), 4, "v4");
+    m5.putDelete("cf2", "cf3");
+    m5.putDelete("cf2", "cf4", 3);
+    m5.putDelete("cf2", "cf4", new ColumnVisibility("A&B&C"), 3);
+
+    assertEquals(m1, m1);
+    assertEquals(m1, m2);
+    assertEquals(m2, m1);
+    assertFalse(m1.equals(m3));
+    assertFalse(m3.equals(m1));
+    assertFalse(m1.equals(m4));
+    assertFalse(m4.equals(m1));
+    assertFalse(m3.equals(m4));
+    assertFalse(m1.equals(m5));
+    assertFalse(m5.equals(m1));
+    assertFalse(m3.equals(m5));
+    assertFalse(m4.equals(m5));
+  }
 }


[3/3] git commit: Merge remote-tracking branch 'origin/1.5.1-SNAPSHOT'

Posted by kt...@apache.org.
Merge remote-tracking branch 'origin/1.5.1-SNAPSHOT'


Project: http://git-wip-us.apache.org/repos/asf/accumulo/repo
Commit: http://git-wip-us.apache.org/repos/asf/accumulo/commit/a3d2673b
Tree: http://git-wip-us.apache.org/repos/asf/accumulo/tree/a3d2673b
Diff: http://git-wip-us.apache.org/repos/asf/accumulo/diff/a3d2673b

Branch: refs/heads/master
Commit: a3d2673b312e4eba7c9518e70f552cc2e4764f70
Parents: 0b85105 6eddb39
Author: Keith Turner <kt...@apache.org>
Authored: Wed Jul 31 12:22:55 2013 -0400
Committer: Keith Turner <kt...@apache.org>
Committed: Wed Jul 31 12:22:55 2013 -0400

----------------------------------------------------------------------
 .../org/apache/accumulo/core/data/Mutation.java |  1 +
 .../apache/accumulo/core/data/MutationTest.java | 68 ++++++++++++++++++++
 2 files changed, 69 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/accumulo/blob/a3d2673b/core/src/main/java/org/apache/accumulo/core/data/Mutation.java
----------------------------------------------------------------------


[2/3] git commit: Merge remote-tracking branch 'origin/1.4.4-SNAPSHOT' into 1.5.1-SNAPSHOT

Posted by kt...@apache.org.
Merge remote-tracking branch 'origin/1.4.4-SNAPSHOT' into 1.5.1-SNAPSHOT

Conflicts:
	core/src/test/java/org/apache/accumulo/core/data/MutationTest.java
	core/src/test/java/org/apache/accumulo/core/data/OldMutation.java


Project: http://git-wip-us.apache.org/repos/asf/accumulo/repo
Commit: http://git-wip-us.apache.org/repos/asf/accumulo/commit/6eddb392
Tree: http://git-wip-us.apache.org/repos/asf/accumulo/tree/6eddb392
Diff: http://git-wip-us.apache.org/repos/asf/accumulo/diff/6eddb392

Branch: refs/heads/master
Commit: 6eddb392a715436be7ce71ef8c383d8133e394c8
Parents: 8622b4d acc4fd1
Author: Keith Turner <kt...@apache.org>
Authored: Wed Jul 31 12:15:57 2013 -0400
Committer: Keith Turner <kt...@apache.org>
Committed: Wed Jul 31 12:15:57 2013 -0400

----------------------------------------------------------------------
 .../org/apache/accumulo/core/data/Mutation.java |  1 +
 .../apache/accumulo/core/data/MutationTest.java | 68 ++++++++++++++++++++
 2 files changed, 69 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/accumulo/blob/6eddb392/core/src/main/java/org/apache/accumulo/core/data/Mutation.java
----------------------------------------------------------------------
diff --cc core/src/main/java/org/apache/accumulo/core/data/Mutation.java
index 00cefbf,0000000..407dbc0
mode 100644,000000..100644
--- a/core/src/main/java/org/apache/accumulo/core/data/Mutation.java
+++ b/core/src/main/java/org/apache/accumulo/core/data/Mutation.java
@@@ -1,721 -1,0 +1,722 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one or more
 + * contributor license agreements.  See the NOTICE file distributed with
 + * this work for additional information regarding copyright ownership.
 + * The ASF licenses this file 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.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +package org.apache.accumulo.core.data;
 +
 +import java.io.DataInput;
 +import java.io.DataOutput;
 +import java.io.IOException;
 +import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.Collections;
 +import java.util.List;
 +
 +import org.apache.accumulo.core.data.thrift.TMutation;
 +import org.apache.accumulo.core.security.ColumnVisibility;
 +import org.apache.accumulo.core.util.ByteBufferUtil;
 +import org.apache.hadoop.io.Text;
 +import org.apache.hadoop.io.Writable;
 +import org.apache.hadoop.io.WritableUtils;
 +
 +/**
 + * <p>
 + * Mutation represents an action that manipulates a row in a table. A mutation holds a list of column/value pairs that represent an atomic set of modifications
 + * to make to a row.
 + * 
 + * <p>
 + * Convenience methods which takes columns and value as CharSequence (String implements CharSequence) are provided. CharSequence is converted to UTF-8 by
 + * constructing a new Text object.
 + * 
 + * <p>
 + * When always passing in the same data as a CharSequence/String, its probably more efficient to call the Text put methods. This way the data is only encoded
 + * once and only one Text object is created.
 + * 
 + * <p>
 + * All of the put methods append data to the mutation, they do not overwrite anything that was previously put. The mutation holds a list of all column/values
 + * that were put into it. The putDelete() methods do not remove something that was previously added to the mutation, rather they indicate that Accumulo should
 + * insert a delete marker for that row column.
 + * 
 + */
 +
 +public class Mutation implements Writable {
 +  
 +  static final int VALUE_SIZE_COPY_CUTOFF = 1 << 15;
 +  
 +  public static enum SERIALIZED_FORMAT {
 +     VERSION1,
 +     VERSION2
 +  };
 +  
 +  private boolean useOldDeserialize = false;
 +  private byte[] row;
 +  private byte[] data;
 +  private int entries;
 +  private List<byte[]> values;
 +
 +  // created this little class instead of using ByteArrayOutput stream and DataOutputStream
 +  // because both are synchronized... lots of small syncs slow things down
 +  private static class ByteBuffer {
 +    
 +    int offset;
 +    byte data[] = new byte[64];
 +    
 +    private void reserve(int l) {
 +      if (offset + l > data.length) {
 +        int newSize = data.length * 2;
 +        while (newSize <= offset + l)
 +          newSize = newSize * 2;
 +        
 +        byte[] newData = new byte[newSize];
 +        System.arraycopy(data, 0, newData, 0, offset);
 +        data = newData;
 +      }
 +      
 +    }
 +    
 +    public void add(byte[] bytes, int off, int length) {
 +      reserve(length);
 +      System.arraycopy(bytes, off, data, offset, length);
 +      offset += length;
 +    }
 +    
 +    void add(boolean b) {
 +      reserve(1);
 +      if (b)
 +        data[offset++] = 1;
 +      else
 +        data[offset++] = 0;
 +    }
 +    
 +    public byte[] toArray() {
 +      byte ret[] = new byte[offset];
 +      System.arraycopy(data, 0, ret, 0, offset);
 +      return ret;
 +    }
 +    
 +    public void writeVLong(long i) {
 +      reserve(9);
 +      if (i >= -112 && i <= 127) {
 +        data[offset++] = (byte)i;
 +        return;
 +      }
 +        
 +      int len = -112;
 +      if (i < 0) {
 +        i ^= -1L; // take one's complement'
 +        len = -120;
 +      }
 +        
 +      long tmp = i;
 +      while (tmp != 0) {
 +        tmp = tmp >> 8;
 +        len--;
 +      }
 +        
 +      data[offset++] = (byte)len;
 +        
 +      len = (len < -120) ? -(len + 120) : -(len + 112);
 +        
 +      for (int idx = len; idx != 0; idx--) {
 +        int shiftbits = (idx - 1) * 8;
 +        long mask = 0xFFL << shiftbits;
 +        data[offset++] = (byte)((i & mask) >> shiftbits);
 +      }
 +    }
 +  }
 +  
 +  private static class SimpleReader {
 +    int offset;
 +    byte data[];
 +    
 +    SimpleReader(byte b[]) {
 +      this.data = b;
 +    }
 +
 +    int readInt() {
 +      return (data[offset++] << 24) + ((data[offset++] & 255) << 16) + ((data[offset++] & 255) << 8) + ((data[offset++] & 255) << 0);
 +    }
 +    
 +    long readLong() {
 +      return (((long) data[offset++] << 56) + ((long) (data[offset++] & 255) << 48) + ((long) (data[offset++] & 255) << 40)
 +          + ((long) (data[offset++] & 255) << 32) + ((long) (data[offset++] & 255) << 24) + ((data[offset++] & 255) << 16) + ((data[offset++] & 255) << 8) + ((data[offset++] & 255) << 0));
 +    }
 +    
 +    void readBytes(byte b[]) {
 +      System.arraycopy(data, offset, b, 0, b.length);
 +      offset += b.length;
 +    }
 +    
 +    boolean readBoolean() {
 +      return (data[offset++] == 1);
 +    }
 +    
 +    long readVLong() {
 +      byte firstByte = data[offset++];
 +      int len =  WritableUtils.decodeVIntSize(firstByte);
 +      if (len == 1) {
 +        return firstByte;
 +      }
 +      long i = 0;
 +      for (int idx = 0; idx < len-1; idx++) {
 +        byte b = data[offset++];
 +        i = i << 8;
 +        i = i | (b & 0xFF);
 +      }
 +      return (WritableUtils.isNegativeVInt(firstByte) ? (i ^ -1L) : i);
 +    }
 +  }
 +  
 +  private ByteBuffer buffer;
 +  
 +  private List<ColumnUpdate> updates;
 +  
 +  private static final byte[] EMPTY_BYTES = new byte[0];
 +  
 +  private void serialize() {
 +    if (buffer != null) {
 +      data = buffer.toArray();
 +      buffer = null;
 +    }
 +  }
 +  
 +  /**
 +   * @since 1.5.0
 +   */
 +  public Mutation(byte[] row) {
 +    this(row, 0, row.length);
 +  }
 +  
 +  /**
 +   * @since 1.5.0
 +   */
 +  public Mutation(byte[] row, int start, int length) {
 +    this.row = new byte[length];
 +    System.arraycopy(row, start, this.row, 0, length);
 +    buffer = new ByteBuffer();
 +  }
 +  
 +  public Mutation(Text row) {
 +    this(row.getBytes(), 0, row.getLength());
 +  }
 +  
 +  public Mutation(CharSequence row) {
 +    this(new Text(row.toString()));
 +  }
 +  
 +  public Mutation() {}
 +  
 +  public Mutation(TMutation tmutation) {
 +    this.row = ByteBufferUtil.toBytes(tmutation.row);
 +    this.data = ByteBufferUtil.toBytes(tmutation.data);
 +    this.entries = tmutation.entries;
 +    this.values = ByteBufferUtil.toBytesList(tmutation.values);
 +  }
 +  
 +  public Mutation(Mutation m) {
 +    m.serialize();
 +    this.row = m.row;
 +    this.data = m.data;
 +    this.entries = m.entries;
 +    this.values = m.values;
 +  }
 +  
 +  public byte[] getRow() {
 +    return row;
 +  }
 +  
 +  private void put(byte b[]) {
 +    put(b, b.length);
 +  }
 +  
 +  private void put(byte b[], int length) {
 +    buffer.writeVLong(length);
 +    buffer.add(b, 0, length);
 +  }
 +  
 +  private void put(boolean b) {
 +    buffer.add(b);
 +  }
 +  
 +  private void put(int i) {
 +    buffer.writeVLong(i);
 +  }
 +  
 +  private void put(long l) {
 +    buffer.writeVLong(l);
 +  }
 +  
 +  private void put(byte[] cf, byte[] cq, byte[] cv, boolean hasts, long ts, boolean deleted, byte[] val) {
 +    put(cf, cf.length, cq, cq.length, cv, hasts, ts, deleted, val, val.length);
 +  }
 +
 +  /*
 +   * When dealing with Text object the length must be gotten from the object, not from the byte array.
 +   */
 +  private void put(Text cf, Text cq, byte[] cv, boolean hasts, long ts, boolean deleted, byte[] val) {
 +    put(cf.getBytes(), cf.getLength(), cq.getBytes(), cq.getLength(), cv, hasts, ts, deleted, val, val.length);
 +  }
 +  
 +  private void put(byte[] cf, int cfLength, byte[] cq, int cqLength, byte[] cv, boolean hasts, long ts, boolean deleted, byte[] val, int valLength) {
 +    if (buffer == null) {
 +      throw new IllegalStateException("Can not add to mutation after serializing it");
 +    }
 +    put(cf, cfLength);
 +    put(cq, cqLength);
 +    put(cv);
 +    put(hasts);
 +    if (hasts) {
 +      put(ts);
 +    }
 +    put(deleted);
 +    
 +    if (valLength < VALUE_SIZE_COPY_CUTOFF) {
 +      put(val, valLength);
 +    } else {
 +      if (values == null) {
 +        values = new ArrayList<byte[]>();
 +      }
 +      byte copy[] = new byte[valLength];
 +      System.arraycopy(val, 0, copy, 0, valLength);
 +      values.add(copy);
 +      put(-1 * values.size());
 +    }
 +    
 +    entries++;
 +  }
 +  
 +  private void put(CharSequence cf, CharSequence cq, byte[] cv, boolean hasts, long ts, boolean deleted, byte[] val) {
 +    put(new Text(cf.toString()), new Text(cq.toString()), cv, hasts, ts, deleted, val);
 +  }
 +  
 +  private void put(Text cf, Text cq, byte[] cv, boolean hasts, long ts, boolean deleted, Text val) {
 +    put(cf.getBytes(), cf.getLength(), cq.getBytes(), cq.getLength(), cv, hasts, ts, deleted, val.getBytes(), val.getLength());
 +  }
 +
 +  private void put(CharSequence cf, CharSequence cq, byte[] cv, boolean hasts, long ts, boolean deleted, CharSequence val) {
 +    put(new Text(cf.toString()), new Text(cq.toString()), cv, hasts, ts, deleted, new Text(val.toString()));
 +  }
 +
 +  public void put(Text columnFamily, Text columnQualifier, Value value) {
 +    put(columnFamily, columnQualifier, EMPTY_BYTES, false, 0l, false, value.get());
 +  }
 +  
 +  public void put(Text columnFamily, Text columnQualifier, ColumnVisibility columnVisibility, Value value) {
 +    put(columnFamily, columnQualifier, columnVisibility.getExpression(), false, 0l, false, value.get());
 +  }
 +  
 +  public void put(Text columnFamily, Text columnQualifier, long timestamp, Value value) {
 +    put(columnFamily, columnQualifier, EMPTY_BYTES, true, timestamp, false, value.get());
 +  }
 +  
 +  public void put(Text columnFamily, Text columnQualifier, ColumnVisibility columnVisibility, long timestamp, Value value) {
 +    put(columnFamily, columnQualifier, columnVisibility.getExpression(), true, timestamp, false, value.get());
 +  }
 +  
 +  public void putDelete(Text columnFamily, Text columnQualifier) {
 +    put(columnFamily, columnQualifier, EMPTY_BYTES, false, 0l, true, EMPTY_BYTES);
 +  }
 +  
 +  public void putDelete(Text columnFamily, Text columnQualifier, ColumnVisibility columnVisibility) {
 +    put(columnFamily, columnQualifier, columnVisibility.getExpression(), false, 0l, true, EMPTY_BYTES);
 +  }
 +  
 +  public void putDelete(Text columnFamily, Text columnQualifier, long timestamp) {
 +    put(columnFamily, columnQualifier, EMPTY_BYTES, true, timestamp, true, EMPTY_BYTES);
 +  }
 +  
 +  public void putDelete(Text columnFamily, Text columnQualifier, ColumnVisibility columnVisibility, long timestamp) {
 +    put(columnFamily, columnQualifier, columnVisibility.getExpression(), true, timestamp, true, EMPTY_BYTES);
 +  }
 +  
 +  public void put(CharSequence columnFamily, CharSequence columnQualifier, Value value) {
 +    put(columnFamily, columnQualifier, EMPTY_BYTES, false, 0l, false, value.get());
 +  }
 +  
 +  public void put(CharSequence columnFamily, CharSequence columnQualifier, ColumnVisibility columnVisibility, Value value) {
 +    put(columnFamily, columnQualifier, columnVisibility.getExpression(), false, 0l, false, value.get());
 +  }
 +  
 +  public void put(CharSequence columnFamily, CharSequence columnQualifier, long timestamp, Value value) {
 +    put(columnFamily, columnQualifier, EMPTY_BYTES, true, timestamp, false, value.get());
 +  }
 +  
 +  public void put(CharSequence columnFamily, CharSequence columnQualifier, ColumnVisibility columnVisibility, long timestamp, Value value) {
 +    put(columnFamily, columnQualifier, columnVisibility.getExpression(), true, timestamp, false, value.get());
 +  }
 +  
 +  public void putDelete(CharSequence columnFamily, CharSequence columnQualifier) {
 +    put(columnFamily, columnQualifier, EMPTY_BYTES, false, 0l, true, EMPTY_BYTES);
 +  }
 +  
 +  public void putDelete(CharSequence columnFamily, CharSequence columnQualifier, ColumnVisibility columnVisibility) {
 +    put(columnFamily, columnQualifier, columnVisibility.getExpression(), false, 0l, true, EMPTY_BYTES);
 +  }
 +  
 +  public void putDelete(CharSequence columnFamily, CharSequence columnQualifier, long timestamp) {
 +    put(columnFamily, columnQualifier, EMPTY_BYTES, true, timestamp, true, EMPTY_BYTES);
 +  }
 +  
 +  public void putDelete(CharSequence columnFamily, CharSequence columnQualifier, ColumnVisibility columnVisibility, long timestamp) {
 +    put(columnFamily, columnQualifier, columnVisibility.getExpression(), true, timestamp, true, EMPTY_BYTES);
 +  }
 +  
 +  public void put(CharSequence columnFamily, CharSequence columnQualifier, CharSequence value) {
 +    put(columnFamily, columnQualifier, EMPTY_BYTES, false, 0l, false, value);
 +  }
 +  
 +  public void put(CharSequence columnFamily, CharSequence columnQualifier, ColumnVisibility columnVisibility, CharSequence value) {
 +    put(columnFamily, columnQualifier, columnVisibility.getExpression(), false, 0l, false, value);
 +  }
 +  
 +  public void put(CharSequence columnFamily, CharSequence columnQualifier, long timestamp, CharSequence value) {
 +    put(columnFamily, columnQualifier, EMPTY_BYTES, true, timestamp, false, value);
 +  }
 +  
 +  public void put(CharSequence columnFamily, CharSequence columnQualifier, ColumnVisibility columnVisibility, long timestamp, CharSequence value) {
 +    put(columnFamily, columnQualifier, columnVisibility.getExpression(), true, timestamp, false, value);
 +  }
 +  
 +  /**
 +   * @since 1.5.0
 +   */
 +  public void put(byte[] columnFamily, byte[] columnQualifier, byte[] value) {
 +    put(columnFamily, columnQualifier, EMPTY_BYTES, false, 0l, false, value);
 +  }
 +  
 +  /**
 +   * @since 1.5.0
 +   */
 +  public void put(byte[] columnFamily, byte[] columnQualifier, ColumnVisibility columnVisibility, byte[] value) {
 +    put(columnFamily, columnQualifier, columnVisibility.getExpression(), false, 0l, false, value);
 +  }
 +  
 +  /**
 +   * @since 1.5.0
 +   */
 +  public void put(byte[] columnFamily, byte[] columnQualifier, long timestamp, byte[] value) {
 +    put(columnFamily, columnQualifier, EMPTY_BYTES, true, timestamp, false, value);
 +  }
 +  
 +  /**
 +   * @since 1.5.0
 +   */
 +  public void put(byte[] columnFamily, byte[] columnQualifier, ColumnVisibility columnVisibility, long timestamp, byte[] value) {
 +    put(columnFamily, columnQualifier, columnVisibility.getExpression(), true, timestamp, false, value);
 +  }
 +  
 +  /**
 +   * @since 1.5.0
 +   */
 +  public void putDelete(byte[] columnFamily, byte[] columnQualifier) {
 +    put(columnFamily, columnQualifier, EMPTY_BYTES, false, 0l, true, EMPTY_BYTES);
 +  }
 +  
 +  /**
 +   * @since 1.5.0
 +   */
 +  public void putDelete(byte[] columnFamily, byte[] columnQualifier, ColumnVisibility columnVisibility) {
 +    put(columnFamily, columnQualifier, columnVisibility.getExpression(), false, 0l, true, EMPTY_BYTES);
 +  }
 +  
 +  /**
 +   * @since 1.5.0
 +   */
 +  public void putDelete(byte[] columnFamily, byte[] columnQualifier, long timestamp) {
 +    put(columnFamily, columnQualifier, EMPTY_BYTES, true, timestamp, true, EMPTY_BYTES);
 +  }
 +  
 +  /**
 +   * @since 1.5.0
 +   */
 +  public void putDelete(byte[] columnFamily, byte[] columnQualifier, ColumnVisibility columnVisibility, long timestamp) {
 +    put(columnFamily, columnQualifier, columnVisibility.getExpression(), true, timestamp, true, EMPTY_BYTES);
 +  }
 +
 +  private byte[] oldReadBytes(SimpleReader in) {
 +    int len = in.readInt();
 +    if (len == 0)
 +      return EMPTY_BYTES;
 +    
 +    byte bytes[] = new byte[len];
 +    in.readBytes(bytes);
 +    return bytes;
 +  }
 +  
 +  private byte[] readBytes(SimpleReader in) {
 +    int len = (int)in.readVLong();
 +    if (len == 0)
 +      return EMPTY_BYTES;
 +    
 +    byte bytes[] = new byte[len];
 +    in.readBytes(bytes);
 +    return bytes;
 +  }
 +  
 +  public List<ColumnUpdate> getUpdates() {
 +    serialize();
 +    
 +    SimpleReader in = new SimpleReader(data);
 +    
 +    if (updates == null) {
 +      if (entries == 1) {
 +        updates = Collections.singletonList(deserializeColumnUpdate(in));
 +      } else {
 +        ColumnUpdate[] tmpUpdates = new ColumnUpdate[entries];
 +        
 +        for (int i = 0; i < entries; i++)
 +          tmpUpdates[i] = deserializeColumnUpdate(in);
 +        
 +        updates = Arrays.asList(tmpUpdates);
 +      }
 +    }
 +    
 +    return updates;
 +  }
 +  
 +  protected ColumnUpdate newColumnUpdate(byte[] cf, byte[] cq, byte[] cv, boolean hasts, long ts, boolean deleted, byte[] val) {
 +    return new ColumnUpdate(cf, cq, cv, hasts, ts, deleted, val);
 +  }
 +
 +  private ColumnUpdate deserializeColumnUpdate(SimpleReader in) {
 +    byte[] cf = readBytes(in);
 +    byte[] cq = readBytes(in);
 +    byte[] cv = readBytes(in);
 +    boolean hasts = in.readBoolean();
 +    long ts = 0;
 +    if (hasts)
 +      ts = in.readVLong();
 +    boolean deleted = in.readBoolean();
 +    
 +    byte[] val;
 +    int valLen = (int)in.readVLong();
 +    
 +    if (valLen < 0) {
 +      val = values.get((-1 * valLen) - 1);
 +    } else if (valLen == 0) {
 +      val = EMPTY_BYTES;
 +    } else {
 +      val = new byte[valLen];
 +      in.readBytes(val);
 +    }
 +    
 +    return newColumnUpdate(cf, cq, cv, hasts, ts, deleted, val);
 +  }
 +  
 +  private int cachedValLens = -1;
 +  
 +  long getValueLengths() {
 +    if (values == null)
 +      return 0;
 +    
 +    if (cachedValLens == -1) {
 +      int tmpCVL = 0;
 +      for (byte[] val : values)
 +        tmpCVL += val.length;
 +      
 +      cachedValLens = tmpCVL;
 +    }
 +    
 +    return cachedValLens;
 +    
 +  }
 +  
 +  public long numBytes() {
 +    serialize();
 +    return row.length + data.length + getValueLengths();
 +  }
 +  
 +  public long estimatedMemoryUsed() {
 +    return numBytes() + 238;
 +  }
 +  
 +  /**
 +   * @return the number of column value pairs added to the mutation
 +   */
 +  public int size() {
 +    return entries;
 +  }
 +  
 +  @Override
 +  public void readFields(DataInput in) throws IOException {
 +    
 +    // Clear out cached column updates and value lengths so
 +    // that we recalculate them based on the (potentially) new
 +    // data we are about to read in.
 +    updates = null;
 +    cachedValLens = -1;
 +    buffer = null;
 +    useOldDeserialize = false;
 +    
 +    byte first = in.readByte();
 +    if ((first & 0x80) != 0x80) {
 +      oldReadFields(first, in);
 +      useOldDeserialize = true;
 +      return;
 +    }
 +
 +    int len = WritableUtils.readVInt(in);
 +    row = new byte[len];
 +    in.readFully(row);
 +    len = WritableUtils.readVInt(in);
 +    data = new byte[len];
 +    in.readFully(data);
 +    entries = WritableUtils.readVInt(in);
 +    
 +    boolean valuesPresent = (first & 0x01) == 0x01;
 +    if (!valuesPresent) {
 +      values = null;
 +    } else {
 +      values = new ArrayList<byte[]>();
 +      int numValues = WritableUtils.readVInt(in);
 +      for (int i = 0; i < numValues; i++) {
 +        len = WritableUtils.readVInt(in);
 +        byte val[] = new byte[len];
 +        in.readFully(val);
 +        values.add(val);
 +      }
 +    }
 +  }
 +  
 +  protected void droppingOldTimestamp(long ts) {}
 +
 +  private void oldReadFields(byte first, DataInput in) throws IOException {
 +
 +    byte b = (byte)in.readByte();
 +    byte c = (byte)in.readByte();
 +    byte d = (byte)in.readByte();
 +    
 +    int len = (((first & 0xff) << 24) | ((b & 0xff) << 16) |
 +        ((c & 0xff) << 8) | (d & 0xff));
 +    row = new byte[len];
 +    in.readFully(row);
 +    len = in.readInt();
 +    byte[] localData = new byte[len];
 +    in.readFully(localData);
 +    int localEntries = in.readInt();
 +    
 +    List<byte[]> localValues;
 +    boolean valuesPresent = in.readBoolean();
 +    if (!valuesPresent) {
 +      localValues = null;
 +    } else {
 +      localValues = new ArrayList<byte[]>();
 +      int numValues = in.readInt();
 +      for (int i = 0; i < numValues; i++) {
 +        len = in.readInt();
 +        byte val[] = new byte[len];
 +        in.readFully(val);
 +        localValues.add(val);
 +      }
 +    }
 +    
 +    // convert data to new format
 +    SimpleReader din = new SimpleReader(localData);
 +    buffer = new ByteBuffer();
 +    for (int i = 0; i < localEntries; i++) {
 +      byte[] cf = oldReadBytes(din);
 +      byte[] cq = oldReadBytes(din);
 +      byte[] cv = oldReadBytes(din);
 +      boolean hasts = din.readBoolean();
 +      long ts = din.readLong();
 +      boolean deleted = din.readBoolean();
 +      
 +      byte[] val;
 +      int valLen = din.readInt();
 +      
 +      if (valLen < 0) {
 +        val = localValues.get((-1 * valLen) - 1);
 +      } else if (valLen == 0) {
 +        val = EMPTY_BYTES;
 +      } else {
 +        val = new byte[valLen];
 +        din.readBytes(val);
 +      }
 +      
 +      put(cf, cq, cv, hasts, ts, deleted, val);
 +      if (!hasts)
 +        droppingOldTimestamp(ts);
 +    }
 +
 +    serialize();
 +
 +  }
 +  
 +  @Override
 +  public void write(DataOutput out) throws IOException {
 +    serialize();
 +    byte hasValues = (values == null) ? 0 : (byte)1; 
 +    out.write((byte)(0x80 | hasValues));
 +    
 +    WritableUtils.writeVInt(out, row.length);
 +    out.write(row);
 +
 +    WritableUtils.writeVInt(out, data.length);
 +    out.write(data);
 +    WritableUtils.writeVInt(out, entries);
 +    
 +    if (hasValues > 0) {
 +      WritableUtils.writeVInt(out, values.size());
 +      for (int i = 0; i < values.size(); i++) {
 +        byte val[] = values.get(i);
 +        WritableUtils.writeVInt(out, val.length);
 +        out.write(val);
 +      }
 +    }
 +  }
 +  
 +  @Override
 +  public boolean equals(Object o) {
 +    if (o instanceof Mutation)
 +      return equals((Mutation) o);
 +    return false;
 +  }
 +  
 +  @Override
 +  public int hashCode() {
 +    return toThrift().hashCode();
 +  }
 +  
 +  public boolean equals(Mutation m) {
 +    serialize();
++    m.serialize();
 +    if (Arrays.equals(row, m.row) && entries == m.entries && Arrays.equals(data, m.data)) {
 +      if (values == null && m.values == null)
 +        return true;
 +      
 +      if (values != null && m.values != null && values.size() == m.values.size()) {
 +        for (int i = 0; i < values.size(); i++) {
 +          if (!Arrays.equals(values.get(i), m.values.get(i)))
 +            return false;
 +        }
 +        
 +        return true;
 +      }
 +      
 +    }
 +    
 +    return false;
 +  }
 +  
 +  public TMutation toThrift() {
 +    serialize();
 +    return new TMutation(java.nio.ByteBuffer.wrap(row), java.nio.ByteBuffer.wrap(data), ByteBufferUtil.toByteBuffers(values), entries);
 +  }
 +  
 +  protected SERIALIZED_FORMAT getSerializedFormat() {
 +    return this.useOldDeserialize ? SERIALIZED_FORMAT.VERSION1 : SERIALIZED_FORMAT.VERSION2;
 +  }
 +
 +}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/6eddb392/core/src/test/java/org/apache/accumulo/core/data/MutationTest.java
----------------------------------------------------------------------
diff --cc core/src/test/java/org/apache/accumulo/core/data/MutationTest.java
index 30acf85,0000000..09026f7
mode 100644,000000..100644
--- a/core/src/test/java/org/apache/accumulo/core/data/MutationTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/data/MutationTest.java
@@@ -1,511 -1,0 +1,579 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one or more
 + * contributor license agreements.  See the NOTICE file distributed with
 + * this work for additional information regarding copyright ownership.
 + * The ASF licenses this file 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.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +package org.apache.accumulo.core.data;
 +
 +import java.io.ByteArrayInputStream;
 +import java.io.ByteArrayOutputStream;
 +import java.io.DataInputStream;
 +import java.io.DataOutputStream;
 +import java.io.IOException;
 +import java.util.Arrays;
 +import java.util.List;
 +
 +import junit.framework.TestCase;
 +
 +import org.apache.accumulo.core.security.ColumnVisibility;
 +import org.apache.hadoop.io.Text;
 +
 +public class MutationTest extends TestCase {
 +  
 +  private static String toHexString(byte[] ba) {
 +    StringBuilder str = new StringBuilder();
 +    for (int i = 0; i < ba.length; i++) {
 +      str.append(String.format("%x", ba[i]));
 +    }
 +    return str.toString();
 +  }
 +
 +  /* Test constructing a Mutation using a byte buffer. The byte array
 +   * returned as the row is converted to a hexadecimal string for easy
 +   * comparision.
 +   */
 +  public void testByteConstructor() {
 +    Mutation m = new Mutation("0123456789".getBytes());
 +    assertEquals("30313233343536373839", toHexString(m.getRow()));
 +  }
 +  
 +  public void testLimitedByteConstructor() {
 +    Mutation m = new Mutation("0123456789".getBytes(), 2, 5);
 +    assertEquals("3233343536", toHexString(m.getRow()));
 +  }
 +  
 +  public void test1() {
 +    Mutation m = new Mutation(new Text("r1"));
 +    m.put(new Text("cf1"), new Text("cq1"), new Value("v1".getBytes()));
 +    
 +    List<ColumnUpdate> updates = m.getUpdates();
 +    
 +    assertEquals(1, updates.size());
 +    
 +    ColumnUpdate cu = updates.get(0);
 +    
 +    assertEquals("cf1", new String(cu.getColumnFamily()));
 +    assertEquals("cq1", new String(cu.getColumnQualifier()));
 +    assertEquals("", new String(cu.getColumnVisibility()));
 +    assertFalse(cu.hasTimestamp());
 +    
 +  }
 +  
 +  public void test2() throws IOException {
 +    Mutation m = new Mutation(new Text("r1"));
 +    m.put(new Text("cf1"), new Text("cq1"), new Value("v1".getBytes()));
 +    m.put(new Text("cf2"), new Text("cq2"), 56, new Value("v2".getBytes()));
 +    
 +    List<ColumnUpdate> updates = m.getUpdates();
 +    
 +    assertEquals(2, updates.size());
 +    
 +    assertEquals("r1", new String(m.getRow()));
 +    ColumnUpdate cu = updates.get(0);
 +    
 +    assertEquals("cf1", new String(cu.getColumnFamily()));
 +    assertEquals("cq1", new String(cu.getColumnQualifier()));
 +    assertEquals("", new String(cu.getColumnVisibility()));
 +    assertFalse(cu.hasTimestamp());
 +    
 +    cu = updates.get(1);
 +    
 +    assertEquals("cf2", new String(cu.getColumnFamily()));
 +    assertEquals("cq2", new String(cu.getColumnQualifier()));
 +    assertEquals("", new String(cu.getColumnVisibility()));
 +    assertTrue(cu.hasTimestamp());
 +    assertEquals(56, cu.getTimestamp());
 +    
 +    m = cloneMutation(m);
 +    
 +    assertEquals("r1", new String(m.getRow()));
 +    updates = m.getUpdates();
 +    
 +    assertEquals(2, updates.size());
 +    
 +    cu = updates.get(0);
 +    
 +    assertEquals("cf1", new String(cu.getColumnFamily()));
 +    assertEquals("cq1", new String(cu.getColumnQualifier()));
 +    assertEquals("", new String(cu.getColumnVisibility()));
 +    assertFalse(cu.hasTimestamp());
 +    
 +    cu = updates.get(1);
 +    
 +    assertEquals("cf2", new String(cu.getColumnFamily()));
 +    assertEquals("cq2", new String(cu.getColumnQualifier()));
 +    assertEquals("", new String(cu.getColumnVisibility()));
 +    assertTrue(cu.hasTimestamp());
 +    assertEquals(56, cu.getTimestamp());
 +    
 +  }
 +  
 +  private Mutation cloneMutation(Mutation m) throws IOException {
 +    ByteArrayOutputStream baos = new ByteArrayOutputStream();
 +    DataOutputStream dos = new DataOutputStream(baos);
 +    m.write(dos);
 +    dos.close();
 +    
 +    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
 +    DataInputStream dis = new DataInputStream(bais);
 +    
 +    m = new Mutation();
 +    m.readFields(dis);
 +    return m;
 +  }
 +  
 +  public void test3() throws IOException {
 +    Mutation m = new Mutation(new Text("r1"));
 +    for (int i = 0; i < 5; i++) {
 +      int len = Mutation.VALUE_SIZE_COPY_CUTOFF - 2 + i;
 +      byte val[] = new byte[len];
 +      for (int j = 0; j < len; j++)
 +        val[j] = (byte) i;
 +      
 +      m.put(new Text("cf" + i), new Text("cq" + i), new Value(val));
 +      
 +    }
 +    
 +    for (int r = 0; r < 3; r++) {
 +      assertEquals("r1", new String(m.getRow()));
 +      List<ColumnUpdate> updates = m.getUpdates();
 +      assertEquals(5, updates.size());
 +      for (int i = 0; i < 5; i++) {
 +        ColumnUpdate cu = updates.get(i);
 +        assertEquals("cf" + i, new String(cu.getColumnFamily()));
 +        assertEquals("cq" + i, new String(cu.getColumnQualifier()));
 +        assertEquals("", new String(cu.getColumnVisibility()));
 +        assertFalse(cu.hasTimestamp());
 +        
 +        byte[] val = cu.getValue();
 +        int len = Mutation.VALUE_SIZE_COPY_CUTOFF - 2 + i;
 +        assertEquals(len, val.length);
 +        for (int j = 0; j < len; j++)
 +          assertEquals(i, val[j]);
 +      }
 +      
 +      m = cloneMutation(m);
 +    }
 +  }
 +  
 +  private Text nt(String s) {
 +    return new Text(s);
 +  }
 +  
 +  private Value nv(String s) {
 +    return new Value(s.getBytes());
 +  }
 +  
 +  public void testPuts() {
 +    Mutation m = new Mutation(new Text("r1"));
 +    
 +    m.put(nt("cf1"), nt("cq1"), nv("v1"));
 +    m.put(nt("cf2"), nt("cq2"), new ColumnVisibility("cv2"), nv("v2"));
 +    m.put(nt("cf3"), nt("cq3"), 3l, nv("v3"));
 +    m.put(nt("cf4"), nt("cq4"), new ColumnVisibility("cv4"), 4l, nv("v4"));
 +    
 +    m.putDelete(nt("cf5"), nt("cq5"));
 +    m.putDelete(nt("cf6"), nt("cq6"), new ColumnVisibility("cv6"));
 +    m.putDelete(nt("cf7"), nt("cq7"), 7l);
 +    m.putDelete(nt("cf8"), nt("cq8"), new ColumnVisibility("cv8"), 8l);
 +    
 +    assertEquals(8, m.size());
 +    
 +    List<ColumnUpdate> updates = m.getUpdates();
 +    
 +    assertEquals(8, m.size());
 +    assertEquals(8, updates.size());
 +    
 +    assertEquals(updates.get(0), "cf1", "cq1", "", 0l, false, false, "v1");
 +    assertEquals(updates.get(1), "cf2", "cq2", "cv2", 0l, false, false, "v2");
 +    assertEquals(updates.get(2), "cf3", "cq3", "", 3l, true, false, "v3");
 +    assertEquals(updates.get(3), "cf4", "cq4", "cv4", 4l, true, false, "v4");
 +    
 +    assertEquals(updates.get(4), "cf5", "cq5", "", 0l, false, true, "");
 +    assertEquals(updates.get(5), "cf6", "cq6", "cv6", 0l, false, true, "");
 +    assertEquals(updates.get(6), "cf7", "cq7", "", 7l, true, true, "");
 +    assertEquals(updates.get(7), "cf8", "cq8", "cv8", 8l, true, true, "");
 +    
 +  }
 +  
 +  public void testPutsString() {
 +    Mutation m = new Mutation("r1");
 +    
 +    m.put("cf1", "cq1", nv("v1"));
 +    m.put("cf2", "cq2", new ColumnVisibility("cv2"), nv("v2"));
 +    m.put("cf3", "cq3", 3l, nv("v3"));
 +    m.put("cf4", "cq4", new ColumnVisibility("cv4"), 4l, nv("v4"));
 +    
 +    m.putDelete("cf5", "cq5");
 +    m.putDelete("cf6", "cq6", new ColumnVisibility("cv6"));
 +    m.putDelete("cf7", "cq7", 7l);
 +    m.putDelete("cf8", "cq8", new ColumnVisibility("cv8"), 8l);
 +    
 +    assertEquals(8, m.size());
 +    
 +    List<ColumnUpdate> updates = m.getUpdates();
 +    
 +    assertEquals(8, m.size());
 +    assertEquals(8, updates.size());
 +    
 +    assertEquals(updates.get(0), "cf1", "cq1", "", 0l, false, false, "v1");
 +    assertEquals(updates.get(1), "cf2", "cq2", "cv2", 0l, false, false, "v2");
 +    assertEquals(updates.get(2), "cf3", "cq3", "", 3l, true, false, "v3");
 +    assertEquals(updates.get(3), "cf4", "cq4", "cv4", 4l, true, false, "v4");
 +    
 +    assertEquals(updates.get(4), "cf5", "cq5", "", 0l, false, true, "");
 +    assertEquals(updates.get(5), "cf6", "cq6", "cv6", 0l, false, true, "");
 +    assertEquals(updates.get(6), "cf7", "cq7", "", 7l, true, true, "");
 +    assertEquals(updates.get(7), "cf8", "cq8", "cv8", 8l, true, true, "");
 +  }
 +  
 +  public void testPutsStringString() {
 +    Mutation m = new Mutation("r1");
 +    
 +    m.put("cf1", "cq1", "v1");
 +    m.put("cf2", "cq2", new ColumnVisibility("cv2"), "v2");
 +    m.put("cf3", "cq3", 3l, "v3");
 +    m.put("cf4", "cq4", new ColumnVisibility("cv4"), 4l, "v4");
 +    
 +    m.putDelete("cf5", "cq5");
 +    m.putDelete("cf6", "cq6", new ColumnVisibility("cv6"));
 +    m.putDelete("cf7", "cq7", 7l);
 +    m.putDelete("cf8", "cq8", new ColumnVisibility("cv8"), 8l);
 +    
 +    assertEquals(8, m.size());
 +    assertEquals("r1", new String(m.getRow()));
 +    
 +    List<ColumnUpdate> updates = m.getUpdates();
 +    
 +    assertEquals(8, m.size());
 +    assertEquals(8, updates.size());
 +    
 +    assertEquals(updates.get(0), "cf1", "cq1", "", 0l, false, false, "v1");
 +    assertEquals(updates.get(1), "cf2", "cq2", "cv2", 0l, false, false, "v2");
 +    assertEquals(updates.get(2), "cf3", "cq3", "", 3l, true, false, "v3");
 +    assertEquals(updates.get(3), "cf4", "cq4", "cv4", 4l, true, false, "v4");
 +    
 +    assertEquals(updates.get(4), "cf5", "cq5", "", 0l, false, true, "");
 +    assertEquals(updates.get(5), "cf6", "cq6", "cv6", 0l, false, true, "");
 +    assertEquals(updates.get(6), "cf7", "cq7", "", 7l, true, true, "");
 +    assertEquals(updates.get(7), "cf8", "cq8", "cv8", 8l, true, true, "");
 +  }
 +  
 +  public void testByteArrays() {
 +    Mutation m = new Mutation("r1".getBytes());
 +    
 +    m.put("cf1".getBytes(), "cq1".getBytes(), "v1".getBytes());
 +    m.put("cf2".getBytes(), "cq2".getBytes(), new ColumnVisibility("cv2"), "v2".getBytes());
 +    m.put("cf3".getBytes(), "cq3".getBytes(), 3l, "v3".getBytes());
 +    m.put("cf4".getBytes(), "cq4".getBytes(), new ColumnVisibility("cv4"), 4l, "v4".getBytes());
 +    
 +    m.putDelete("cf5".getBytes(), "cq5".getBytes());
 +    m.putDelete("cf6".getBytes(), "cq6".getBytes(), new ColumnVisibility("cv6"));
 +    m.putDelete("cf7".getBytes(), "cq7".getBytes(), 7l);
 +    m.putDelete("cf8".getBytes(), "cq8".getBytes(), new ColumnVisibility("cv8"), 8l);
 +    
 +    assertEquals(8, m.size());
 +    
 +    List<ColumnUpdate> updates = m.getUpdates();
 +    
 +    assertEquals(8, m.size());
 +    assertEquals(8, updates.size());
 +    
 +    assertEquals(updates.get(0), "cf1", "cq1", "", 0l, false, false, "v1");
 +    assertEquals(updates.get(1), "cf2", "cq2", "cv2", 0l, false, false, "v2");
 +    assertEquals(updates.get(2), "cf3", "cq3", "", 3l, true, false, "v3");
 +    assertEquals(updates.get(3), "cf4", "cq4", "cv4", 4l, true, false, "v4");
 +    
 +    assertEquals(updates.get(4), "cf5", "cq5", "", 0l, false, true, "");
 +    assertEquals(updates.get(5), "cf6", "cq6", "cv6", 0l, false, true, "");
 +    assertEquals(updates.get(6), "cf7", "cq7", "", 7l, true, true, "");
 +    assertEquals(updates.get(7), "cf8", "cq8", "cv8", 8l, true, true, "");
 +  }
 +
 +  /**
 +   * Test for regression on bug 3422. If a {@link Mutation} object is reused for multiple calls to readFields, the mutation would previously be "locked in" to
 +   * the first set of column updates (and value lengths). Hadoop input formats reuse objects when reading, so if Mutations are used with an input format (or as
 +   * the input to a combiner or reducer) then they will be used in this fashion.
 +   */
 +  public void testMultipleReadFieldsCalls() throws IOException {
 +    // Create test mutations and write them to a byte output stream
 +    Mutation m1 = new Mutation("row1");
 +    m1.put("cf1.1", "cq1.1", new ColumnVisibility("A|B"), "val1.1");
 +    m1.put("cf1.2", "cq1.2", new ColumnVisibility("C|D"), "val1.2");
 +    byte[] val1_3 = new byte[Mutation.VALUE_SIZE_COPY_CUTOFF + 3];
 +    Arrays.fill(val1_3, (byte) 3);
 +    m1.put("cf1.3", "cq1.3", new ColumnVisibility("E|F"), new String(val1_3));
 +    int size1 = m1.size();
 +    long nb1 = m1.numBytes();
 +    
 +    Mutation m2 = new Mutation("row2");
 +    byte[] val2 = new byte[Mutation.VALUE_SIZE_COPY_CUTOFF + 2];
 +    Arrays.fill(val2, (byte) 2);
 +    m2.put("cf2", "cq2", new ColumnVisibility("G|H"), 1234, new String(val2));
 +    int size2 = m2.size();
 +    long nb2 = m2.numBytes();
 +    
 +    ByteArrayOutputStream bos = new ByteArrayOutputStream();
 +    DataOutputStream dos = new DataOutputStream(bos);
 +    m1.write(dos);
 +    m2.write(dos);
 +    dos.close();
 +    
 +    // Now read the mutations back in from the byte array, making sure to
 +    // reuse the same mutation object, and make sure everything is correct.
 +    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
 +    DataInputStream dis = new DataInputStream(bis);
 +    
 +    Mutation m = new Mutation();
 +    m.readFields(dis);
 +    
 +    assertEquals("row1", new String(m.getRow()));
 +    assertEquals(size1, m.size());
 +    assertEquals(nb1, m.numBytes());
 +    assertEquals(3, m.getUpdates().size());
 +    assertEquals(m.getUpdates().get(0), "cf1.1", "cq1.1", "A|B", 0L, false, false, "val1.1");
 +    assertEquals(m.getUpdates().get(1), "cf1.2", "cq1.2", "C|D", 0L, false, false, "val1.2");
 +    assertEquals(m.getUpdates().get(2), "cf1.3", "cq1.3", "E|F", 0L, false, false, new String(val1_3));
 +    
 +    // Reuse the same mutation object (which is what happens in the hadoop framework
 +    // when objects are read by an input format)
 +    m.readFields(dis);
 +    
 +    assertEquals("row2", new String(m.getRow()));
 +    assertEquals(size2, m.size());
 +    assertEquals(nb2, m.numBytes());
 +    assertEquals(1, m.getUpdates().size());
 +    assertEquals(m.getUpdates().get(0), "cf2", "cq2", "G|H", 1234L, true, false, new String(val2));
 +  }
 +  
 +  private void assertEquals(ColumnUpdate cu, String cf, String cq, String cv, long ts, boolean timeSet, boolean deleted, String val) {
 +    
 +    assertEquals(cf, new String(cu.getColumnFamily()));
 +    assertEquals(cq, new String(cu.getColumnQualifier()));
 +    assertEquals(cv, new String(cu.getColumnVisibility()));
 +    assertEquals(timeSet, cu.hasTimestamp());
 +    if (timeSet)
 +      assertEquals(ts, cu.getTimestamp());
 +    assertEquals(deleted, cu.isDeleted());
 +    assertEquals(val, new String(cu.getValue()));
 +  }
 +  
 +  public void test4() throws Exception {
 +    Mutation m1 = new Mutation(new Text("r1"));
 +    
 +    m1.put(nt("cf1"), nt("cq1"), nv("v1"));
 +    m1.put(nt("cf2"), nt("cq2"), new ColumnVisibility("cv2"), nv("v2"));
 +    
 +    ByteArrayOutputStream bos = new ByteArrayOutputStream();
 +    DataOutputStream dos = new DataOutputStream(bos);
 +    m1.write(dos);
 +    dos.close();
 +    
 +    Mutation m2 = new Mutation(new Text("r2"));
 +    
 +    m2.put(nt("cf3"), nt("cq3"), nv("v3"));
 +    m2.put(nt("cf4"), nt("cq4"), new ColumnVisibility("cv2"), nv("v4"));
 +    
 +    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
 +    DataInputStream dis = new DataInputStream(bis);
 +    
 +    // used to be a bug where puts done before readFields would be seen
 +    // after readFields
 +    m2.readFields(dis);
 +    
 +    assertEquals("r1", new String(m2.getRow()));
 +    assertEquals(2, m2.getUpdates().size());
 +    assertEquals(2, m2.size());
 +    assertEquals(m2.getUpdates().get(0), "cf1", "cq1", "", 0l, false, false, "v1");
 +    assertEquals(m2.getUpdates().get(1), "cf2", "cq2", "cv2", 0l, false, false, "v2");
 +  }
 +  
 +  Mutation convert(OldMutation old) throws IOException {
 +    ByteArrayOutputStream bos = new ByteArrayOutputStream();
 +    DataOutputStream dos = new DataOutputStream(bos);
 +    old.write(dos);
 +    dos.close();
 +    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
 +    DataInputStream dis = new DataInputStream(bis);
 +    Mutation m = new Mutation();
 +    m.readFields(dis);
 +    dis.close();
 +    return m;
 +  }
 +  
 +  
 +  public void testNewSerialization() throws Exception {
 +    // write an old mutation
 +    OldMutation m2 = new OldMutation("r1");
 +    m2.put("cf1", "cq1", "v1");
 +    m2.put("cf2", "cq2", new ColumnVisibility("cv2"), "v2");
 +    m2.putDelete("cf3", "cq3");
 +    ByteArrayOutputStream bos = new ByteArrayOutputStream();
 +    DataOutputStream dos = new DataOutputStream(bos);
 +    m2.write(dos);
 +    dos.close();
 +    long oldSize = dos.size(); 
 +    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
 +    DataInputStream dis = new DataInputStream(bis);
 +    m2.readFields(dis);
 +    dis.close();
 +    
 +    // check it
 +    assertEquals("r1", new String(m2.getRow()));
 +    assertEquals(3, m2.getUpdates().size());
 +    assertEquals(3, m2.size());
 +    assertEquals(m2.getUpdates().get(0), "cf1", "cq1", "", 0l, false, false, "v1");
 +    assertEquals(m2.getUpdates().get(1), "cf2", "cq2", "cv2", 0l, false, false, "v2");
 +    assertEquals(m2.getUpdates().get(2), "cf3", "cq3", "", 0l, false, true, "");
 +
 +    Mutation m1 = convert(m2);
 +    
 +    assertEquals("r1", new String(m1.getRow()));
 +    assertEquals(3, m1.getUpdates().size());
 +    assertEquals(3, m1.size());
 +    assertEquals(m1.getUpdates().get(0), "cf1", "cq1", "", 0l, false, false, "v1");
 +    assertEquals(m1.getUpdates().get(1), "cf2", "cq2", "cv2", 0l, false, false, "v2");
 +    assertEquals(m1.getUpdates().get(2), "cf3", "cq3", "", 0l, false, true, "");
 +    
 +    Text exampleRow = new Text(" 123456789 123456789 123456789 123456789 123456789");
 +    int exampleLen = exampleRow.getLength();
 +    m1 = new Mutation(exampleRow);
 +    m1.put("", "", "");
 +
 +    bos = new ByteArrayOutputStream();
 +    dos = new DataOutputStream(bos);
 +    m1.write(dos);
 +    dos.close();
 +    long newSize = dos.size();
 +    assertTrue(newSize < oldSize);
 +    assertEquals(10, newSize - exampleLen);
 +    assertEquals(68, oldSize - exampleLen);
 +    // I am converting to integer to avoid comparing floats which are inaccurate
 +    assertEquals(14705, (int)(((newSize-exampleLen) * 100. / (oldSize - exampleLen)) * 1000));
 +    StringBuilder sb = new StringBuilder();
 +    byte[] ba = bos.toByteArray();
 +    for (int i = 0; i < bos.size(); i += 4) {
 +      for (int j = i; j < bos.size() && j < i + 4; j++) {
 +        sb.append(String.format("%02x", ba[j]));
 +      }
 +      sb.append(" ");
 +    }
 +    assertEquals("80322031 32333435 36373839 20313233 34353637 38392031 32333435 36373839 20313233 34353637 38392031 32333435 36373839 06000000 00000001 ", sb.toString());
 +    
 +  }
 +  
 +  public void testReserialize() throws Exception {
 +    // test reading in a new mutation from an old mutation and reserializing the new mutation... this was failing
 +    OldMutation om = new OldMutation("r1");
 +    om.put("cf1", "cq1", "v1");
 +    om.put("cf2", "cq2", new ColumnVisibility("cv2"), "v2");
 +    om.putDelete("cf3", "cq3");
 +    StringBuilder bigVal = new StringBuilder();
 +    for (int i = 0; i < 100000; i++) {
 +      bigVal.append('a');
 +    }
 +    om.put("cf2", "big", bigVal);
 +
 +    
 +    Mutation m1 = convert(om);
 +
 +    ByteArrayOutputStream bos = new ByteArrayOutputStream();
 +    DataOutputStream dos = new DataOutputStream(bos);
 +    m1.write(dos);
 +    dos.close();
 +    
 +    Mutation m2 = new Mutation();
 +    
 +    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
 +    DataInputStream dis = new DataInputStream(bis);
 +    m2.readFields(dis);
 +    
 +    assertEquals("r1", new String(m1.getRow()));
 +    assertEquals(4, m2.getUpdates().size());
 +    assertEquals(4, m2.size());
 +    assertEquals(m2.getUpdates().get(0), "cf1", "cq1", "", 0l, false, false, "v1");
 +    assertEquals(m2.getUpdates().get(1), "cf2", "cq2", "cv2", 0l, false, false, "v2");
 +    assertEquals(m2.getUpdates().get(2), "cf3", "cq3", "", 0l, false, true, "");
 +    assertEquals(m2.getUpdates().get(3), "cf2", "big", "", 0l, false, false, bigVal.toString());
 +  }
 +
++  public void testEquals() {
++    Mutation m1 = new Mutation("r1");
++    
++    m1.put("cf1", "cq1", "v1");
++    m1.put("cf1", "cq1", new ColumnVisibility("A&B"), "v2");
++    m1.put("cf1", "cq1", 3, "v3");
++    m1.put("cf1", "cq1", new ColumnVisibility("A&B&C"), 4, "v4");
++    m1.putDelete("cf2", "cf3");
++    m1.putDelete("cf2", "cf4", 3);
++    m1.putDelete("cf2", "cf4", new ColumnVisibility("A&B&C"), 3);
++    
++    // m2 has same data as m1
++    Mutation m2 = new Mutation("r1");
++    
++    m2.put("cf1", "cq1", "v1");
++    m2.put("cf1", "cq1", new ColumnVisibility("A&B"), "v2");
++    m2.put("cf1", "cq1", 3, "v3");
++    m2.put("cf1", "cq1", new ColumnVisibility("A&B&C"), 4, "v4");
++    m2.putDelete("cf2", "cf3");
++    m2.putDelete("cf2", "cf4", 3);
++    m2.putDelete("cf2", "cf4", new ColumnVisibility("A&B&C"), 3);
++    
++    // m3 has differnt row than m1
++    Mutation m3 = new Mutation("r2");
++    
++    m3.put("cf1", "cq1", "v1");
++    m3.put("cf1", "cq1", new ColumnVisibility("A&B"), "v2");
++    m3.put("cf1", "cq1", 3, "v3");
++    m3.put("cf1", "cq1", new ColumnVisibility("A&B&C"), 4, "v4");
++    m3.putDelete("cf2", "cf3");
++    m3.putDelete("cf2", "cf4", 3);
++    m3.putDelete("cf2", "cf4", new ColumnVisibility("A&B&C"), 3);
++
++    // m4 has a different column than m1
++    Mutation m4 = new Mutation("r1");
++    
++    m4.put("cf2", "cq1", "v1");
++    m4.put("cf1", "cq1", new ColumnVisibility("A&B"), "v2");
++    m4.put("cf1", "cq1", 3, "v3");
++    m4.put("cf1", "cq1", new ColumnVisibility("A&B&C"), 4, "v4");
++    m4.putDelete("cf2", "cf3");
++    m4.putDelete("cf2", "cf4", 3);
++    m4.putDelete("cf2", "cf4", new ColumnVisibility("A&B&C"), 3);
++    
++    // m5 has a different value than m1
++    Mutation m5 = new Mutation("r1");
++    
++    m5.put("cf1", "cq1", "v1");
++    m5.put("cf1", "cq1", new ColumnVisibility("A&B"), "v2");
++    m5.put("cf1", "cq1", 3, "v4");
++    m5.put("cf1", "cq1", new ColumnVisibility("A&B&C"), 4, "v4");
++    m5.putDelete("cf2", "cf3");
++    m5.putDelete("cf2", "cf4", 3);
++    m5.putDelete("cf2", "cf4", new ColumnVisibility("A&B&C"), 3);
++
++    assertEquals(m1, m1);
++    assertEquals(m1, m2);
++    assertEquals(m2, m1);
++    assertFalse(m1.equals(m3));
++    assertFalse(m3.equals(m1));
++    assertFalse(m1.equals(m4));
++    assertFalse(m4.equals(m1));
++    assertFalse(m3.equals(m4));
++    assertFalse(m1.equals(m5));
++    assertFalse(m5.equals(m1));
++    assertFalse(m3.equals(m5));
++    assertFalse(m4.equals(m5));
++  }
 +}