org.apache.accumulo.core.data.Key.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.accumulo.core.data.Key.java

Source

/*
 * 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;

/**
 * This is the Key used to store and access individual values in Accumulo.  A Key is a tuple composed of a row, column family, column qualifier,
 * column visibility, timestamp, and delete marker.
 *
 * Keys are comparable and therefore have a sorted order defined by {@link #compareTo(Key)}.
 *
 */

import static org.apache.accumulo.core.util.ByteBufferUtil.toBytes;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;

import org.apache.accumulo.core.Constants;
import org.apache.accumulo.core.data.thrift.TKey;
import org.apache.accumulo.core.data.thrift.TKeyValue;
import org.apache.accumulo.core.security.ColumnVisibility;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
import org.apache.hadoop.io.WritableUtils;

public class Key implements WritableComparable<Key>, Cloneable {

    protected byte[] row;
    protected byte[] colFamily;
    protected byte[] colQualifier;
    protected byte[] colVisibility;
    protected long timestamp;
    protected boolean deleted;

    /**
     * Create a {@link Key} builder.
     *
     * @since 2.0
     * @param copyBytes
     *          if the bytes of the {@link Key} components should be copied
     * @return the builder at the {@link KeyBuilder.RowStep}
     */
    public static KeyBuilder.RowStep builder(boolean copyBytes) {
        return new KeyBuilder.KeyBuilderImpl(copyBytes);
    }

    /**
     * Create a {@link Key} builder. Copy bytes defaults to true.
     *
     * @since 2.0
     * @return the builder at the {@link KeyBuilder.RowStep}
     */
    public static KeyBuilder.RowStep builder() {
        return new KeyBuilder.KeyBuilderImpl(true);
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof Key)
            return this.equals((Key) o, PartialKey.ROW_COLFAM_COLQUAL_COLVIS_TIME_DEL);
        return false;
    }

    private static final byte EMPTY_BYTES[] = new byte[0];

    static byte[] copyIfNeeded(byte ba[], int off, int len, boolean copyData) {
        if (len == 0)
            return EMPTY_BYTES;

        if (!copyData && ba.length == len && off == 0)
            return ba;

        byte[] copy = new byte[len];
        System.arraycopy(ba, off, copy, 0, len);
        return copy;
    }

    private final void init(byte r[], int rOff, int rLen, byte cf[], int cfOff, int cfLen, byte cq[], int cqOff,
            int cqLen, byte cv[], int cvOff, int cvLen, long ts, boolean del, boolean copy) {
        row = copyIfNeeded(r, rOff, rLen, copy);
        colFamily = copyIfNeeded(cf, cfOff, cfLen, copy);
        colQualifier = copyIfNeeded(cq, cqOff, cqLen, copy);
        colVisibility = copyIfNeeded(cv, cvOff, cvLen, copy);
        timestamp = ts;
        deleted = del;
    }

    /**
     * Creates a key with empty row, empty column family, empty column qualifier, empty column visibility, timestamp {@link Long#MAX_VALUE}, and delete marker
     * false.
     */
    public Key() {
        row = EMPTY_BYTES;
        colFamily = EMPTY_BYTES;
        colQualifier = EMPTY_BYTES;
        colVisibility = EMPTY_BYTES;
        timestamp = Long.MAX_VALUE;
        deleted = false;
    }

    /**
     * Creates a key with the specified row, empty column family, empty column qualifier, empty column visibility, timestamp {@link Long#MAX_VALUE}, and delete
     * marker false.
     *
     * @param row
     *          row ID
     */
    public Key(Text row) {
        init(row.getBytes(), 0, row.getLength(), EMPTY_BYTES, 0, 0, EMPTY_BYTES, 0, 0, EMPTY_BYTES, 0, 0,
                Long.MAX_VALUE, false, true);
    }

    /**
     * Creates a key with the specified row, empty column family, empty column qualifier, empty column visibility, timestamp {@link Long#MAX_VALUE}, and delete
     * marker false. This constructor creates a copy of row. If you don't want to create a copy of row, you should call
     * {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead.
     *
     * @param row
     *          row ID
     * @since 1.8.0
     */
    public Key(byte[] row) {
        init(row, 0, row.length, EMPTY_BYTES, 0, 0, EMPTY_BYTES, 0, 0, EMPTY_BYTES, 0, 0, Long.MAX_VALUE, false,
                true);
    }

    /**
     * Creates a key with the specified row, empty column family, empty column qualifier, empty column visibility, the specified timestamp, and delete marker
     * false.
     *
     * @param row
     *          row ID
     * @param ts
     *          timestamp
     */
    public Key(Text row, long ts) {
        this(row);
        timestamp = ts;
    }

    /**
     * Creates a key with the specified row, empty column family, empty column qualifier, empty column visibility, the specified timestamp, and delete marker
     * false. This constructor creates a copy of row. If you don't want to create a copy, you should call
     * {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead.
     *
     * @param row
     *          row ID
     * @param ts
     *          timestamp
     * @since 1.8.0
     */
    public Key(byte[] row, long ts) {
        this(row);
        timestamp = ts;
    }

    /**
     * Creates a key. The delete marker defaults to false. This constructor creates a copy of each specified array. If you don't want to create a copy of the
     * arrays, you should call {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead.
     *
     * @param row
     *          bytes containing row ID
     * @param rOff
     *          offset into row where key's row ID begins (inclusive)
     * @param rLen
     *          length of row ID in row
     * @param cf
     *          bytes containing column family
     * @param cfOff
     *          offset into cf where key's column family begins (inclusive)
     * @param cfLen
     *          length of column family in cf
     * @param cq
     *          bytes containing column qualifier
     * @param cqOff
     *          offset into cq where key's column qualifier begins (inclusive)
     * @param cqLen
     *          length of column qualifier in cq
     * @param cv
     *          bytes containing column visibility
     * @param cvOff
     *          offset into cv where key's column visibility begins (inclusive)
     * @param cvLen
     *          length of column visibility in cv
     * @param ts
     *          timestamp
     */
    public Key(byte row[], int rOff, int rLen, byte cf[], int cfOff, int cfLen, byte cq[], int cqOff, int cqLen,
            byte cv[], int cvOff, int cvLen, long ts) {
        init(row, rOff, rLen, cf, cfOff, cfLen, cq, cqOff, cqLen, cv, cvOff, cvLen, ts, false, true);
    }

    /**
     * Creates a key. The delete marker defaults to false. This constructor creates a copy of each specified array. If you don't want to create a copy of the
     * arrays, you should call {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead.
     *
     * @param row
     *          bytes containing row ID
     * @param rOff
     *          offset into row where key's row ID begins (inclusive)
     * @param rLen
     *          length of row ID in row
     * @param cf
     *          bytes containing column family
     * @param cfOff
     *          offset into cf where key's column family begins (inclusive)
     * @param cfLen
     *          length of column family in cf
     * @param cq
     *          bytes containing column qualifier
     * @param cqOff
     *          offset into cq where key's column qualifier begins (inclusive)
     * @param cqLen
     *          length of column qualifier in cq
     * @param cv
     *          bytes containing column visibility
     * @param cvOff
     *          offset into cv where key's column visibility begins (inclusive)
     * @param cvLen
     *          length of column visibility in cv
     * @param ts
     *          timestamp
     * @param deleted
     *          delete marker
     * @param copy
     *          if true, forces copy of byte array values into key
     */
    Key(byte row[], int rOff, int rLen, byte cf[], int cfOff, int cfLen, byte cq[], int cqOff, int cqLen, byte cv[],
            int cvOff, int cvLen, long ts, boolean deleted, boolean copy) {
        init(row, rOff, rLen, cf, cfOff, cfLen, cq, cqOff, cqLen, cv, cvOff, cvLen, ts, deleted, copy);
    }

    /**
     * Creates a key. The delete marker defaults to false. This constructor creates a copy of each specified array. If you don't want to create a copy of the
     * arrays, you should call {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead.
     *
     * @param row
     *          row ID
     * @param colFamily
     *          column family
     * @param colQualifier
     *          column qualifier
     * @param colVisibility
     *          column visibility
     * @param timestamp
     *          timestamp
     */
    public Key(byte[] row, byte[] colFamily, byte[] colQualifier, byte[] colVisibility, long timestamp) {
        this(row, colFamily, colQualifier, colVisibility, timestamp, false, true);
    }

    /**
     * Creates a key. This constructor creates a copy of each specified arrays. If you don't want to create a copy, you should call
     * {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead.
     *
     * @param row
     *          row ID
     * @param cf
     *          column family
     * @param cq
     *          column qualifier
     * @param cv
     *          column visibility
     * @param ts
     *          timestamp
     * @param deleted
     *          delete marker
     */
    public Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted) {
        this(row, cf, cq, cv, ts, deleted, true);
    }

    /**
     * Creates a key.
     *
     * @param row
     *          row ID
     * @param cf
     *          column family
     * @param cq
     *          column qualifier
     * @param cv
     *          column visibility
     * @param ts
     *          timestamp
     * @param deleted
     *          delete marker
     * @param copy
     *          if true, forces copy of byte array values into key
     */
    public Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy) {
        init(row, 0, row.length, cf, 0, cf.length, cq, 0, cq.length, cv, 0, cv.length, ts, deleted, copy);
    }

    /**
     * Creates a key with the specified row, the specified column family, empty column qualifier, empty column visibility, timestamp {@link Long#MAX_VALUE}, and
     * delete marker false.
     */
    public Key(Text row, Text cf) {
        init(row.getBytes(), 0, row.getLength(), cf.getBytes(), 0, cf.getLength(), EMPTY_BYTES, 0, 0, EMPTY_BYTES,
                0, 0, Long.MAX_VALUE, false, true);
    }

    /**
     * Creates a key with the specified row, the specified column family, empty column qualifier, empty column visibility, timestamp {@link Long#MAX_VALUE}, and
     * delete marker false. This constructor creates a copy of each specified array. If you don't want to create a copy of the arrays, you should call
     * {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead.
     *
     * @since 1.8.0
     */
    public Key(byte[] row, byte[] cf) {
        init(row, 0, row.length, cf, 0, cf.length, EMPTY_BYTES, 0, 0, EMPTY_BYTES, 0, 0, Long.MAX_VALUE, false,
                true);
    }

    /**
     * Creates a key with the specified row, the specified column family, the specified column qualifier, empty column visibility, timestamp
     * {@link Long#MAX_VALUE}, and delete marker false.
     */
    public Key(Text row, Text cf, Text cq) {
        init(row.getBytes(), 0, row.getLength(), cf.getBytes(), 0, cf.getLength(), cq.getBytes(), 0, cq.getLength(),
                EMPTY_BYTES, 0, 0, Long.MAX_VALUE, false, true);
    }

    /**
     * Creates a key with the specified row, the specified column family, the specified column qualifier, empty column visibility, timestamp
     * {@link Long#MAX_VALUE}, and delete marker false. This constructor creates a copy of each specified array. If you don't want to create a copy of the arrays,
     * you should call {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead.
     *
     * @since 1.8.0
     */
    public Key(byte[] row, byte[] cf, byte[] cq) {
        init(row, 0, row.length, cf, 0, cf.length, cq, 0, cq.length, EMPTY_BYTES, 0, 0, Long.MAX_VALUE, false,
                true);
    }

    /**
     * Creates a key with the specified row, the specified column family, the specified column qualifier, the specified column visibility, timestamp
     * {@link Long#MAX_VALUE}, and delete marker false.
     */
    public Key(Text row, Text cf, Text cq, Text cv) {
        init(row.getBytes(), 0, row.getLength(), cf.getBytes(), 0, cf.getLength(), cq.getBytes(), 0, cq.getLength(),
                cv.getBytes(), 0, cv.getLength(), Long.MAX_VALUE, false, true);
    }

    /**
     * Creates a key with the specified row, the specified column family, the specified column qualifier, the specified column visibility, timestamp
     * {@link Long#MAX_VALUE}, and delete marker false. This constructor creates a copy of each specified array. If you don't want to create a copy of the arrays,
     * you should call {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead.
     *
     * @since 1.8.0
     */
    public Key(byte[] row, byte[] cf, byte[] cq, byte[] cv) {
        init(row, 0, row.length, cf, 0, cf.length, cq, 0, cq.length, cv, 0, cv.length, Long.MAX_VALUE, false, true);
    }

    /**
     * Creates a key with the specified row, the specified column family, the specified column qualifier, empty column visibility, the specified timestamp, and
     * delete marker false.
     */
    public Key(Text row, Text cf, Text cq, long ts) {
        init(row.getBytes(), 0, row.getLength(), cf.getBytes(), 0, cf.getLength(), cq.getBytes(), 0, cq.getLength(),
                EMPTY_BYTES, 0, 0, ts, false, true);
    }

    /**
     * Creates a key with the specified row, the specified column family, the specified column qualifier, empty column visibility, the specified timestamp, and
     * delete marker false. This constructor creates a copy of each specified array. If you don't want to create a copy of the arrays, you should call
     * {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead.
     *
     * @since 1.8.0
     */
    public Key(byte[] row, byte[] cf, byte[] cq, long ts) {
        init(row, 0, row.length, cf, 0, cf.length, cq, 0, cq.length, EMPTY_BYTES, 0, 0, ts, false, true);
    }

    /**
     * Creates a key with the specified row, the specified column family, the specified column qualifier, the specified column visibility, the specified
     * timestamp, and delete marker false.
     */
    public Key(Text row, Text cf, Text cq, Text cv, long ts) {
        init(row.getBytes(), 0, row.getLength(), cf.getBytes(), 0, cf.getLength(), cq.getBytes(), 0, cq.getLength(),
                cv.getBytes(), 0, cv.getLength(), ts, false, true);
    }

    /**
     * Creates a key with the specified row, the specified column family, the specified column qualifier, the specified column visibility, the specified
     * timestamp, and delete marker false.
     */
    public Key(Text row, Text cf, Text cq, ColumnVisibility cv, long ts) {
        byte[] expr = cv.getExpression();
        init(row.getBytes(), 0, row.getLength(), cf.getBytes(), 0, cf.getLength(), cq.getBytes(), 0, cq.getLength(),
                expr, 0, expr.length, ts, false, true);
    }

    /**
     * Creates a key with the specified row, the specified column family, the specified column qualifier, the specified column visibility, the specified
     * timestamp, and delete marker false. This constructor creates a copy of each specified array. If you don't want to create a copy of the arrays, you should
     * call {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead.
     *
     * @since 1.8.0
     */
    public Key(byte[] row, byte[] cf, byte[] cq, ColumnVisibility cv, long ts) {
        byte[] expr = cv.getExpression();
        init(row, 0, row.length, cf, 0, cf.length, cq, 0, cq.length, expr, 0, expr.length, ts, false, true);
    }

    /**
     * Converts CharSequence to Text and creates a Key using {@link #Key(Text)}.
     */
    public Key(CharSequence row) {
        this(new Text(row.toString()));
    }

    /**
     * Converts CharSequence to Text and creates a Key using {@link #Key(Text,Text)}.
     */
    public Key(CharSequence row, CharSequence cf) {
        this(new Text(row.toString()), new Text(cf.toString()));
    }

    /**
     * Converts CharSequence to Text and creates a Key using {@link #Key(Text,Text,Text)}.
     */
    public Key(CharSequence row, CharSequence cf, CharSequence cq) {
        this(new Text(row.toString()), new Text(cf.toString()), new Text(cq.toString()));
    }

    /**
     * Converts CharSequence to Text and creates a Key using {@link #Key(Text,Text,Text,Text)}.
     */
    public Key(CharSequence row, CharSequence cf, CharSequence cq, CharSequence cv) {
        this(new Text(row.toString()), new Text(cf.toString()), new Text(cq.toString()), new Text(cv.toString()));
    }

    /**
     * Converts CharSequence to Text and creates a Key using {@link #Key(Text,Text,Text,long)}.
     */
    public Key(CharSequence row, CharSequence cf, CharSequence cq, long ts) {
        this(new Text(row.toString()), new Text(cf.toString()), new Text(cq.toString()), ts);
    }

    /**
     * Converts CharSequence to Text and creates a Key using {@link #Key(Text,Text,Text,Text,long)}.
     */
    public Key(CharSequence row, CharSequence cf, CharSequence cq, CharSequence cv, long ts) {
        this(new Text(row.toString()), new Text(cf.toString()), new Text(cq.toString()), new Text(cv.toString()),
                ts);
    }

    /**
     * Converts CharSequence to Text and creates a Key using {@link #Key(Text,Text,Text,ColumnVisibility,long)}.
     */
    public Key(CharSequence row, CharSequence cf, CharSequence cq, ColumnVisibility cv, long ts) {
        this(new Text(row.toString()), new Text(cf.toString()), new Text(cq.toString()),
                new Text(cv.getExpression()), ts);
    }

    private byte[] followingArray(byte ba[]) {
        byte[] fba = new byte[ba.length + 1];
        System.arraycopy(ba, 0, fba, 0, ba.length);
        fba[ba.length] = (byte) 0x00;
        return fba;
    }

    /**
     * Returns a key that will sort immediately after this key.
     *
     * @param part
     *          PartialKey except {@link PartialKey#ROW_COLFAM_COLQUAL_COLVIS_TIME_DEL}
     */
    public Key followingKey(PartialKey part) {
        Key returnKey = new Key();
        switch (part) {
        case ROW:
            returnKey.row = followingArray(row);
            break;
        case ROW_COLFAM:
            returnKey.row = row;
            returnKey.colFamily = followingArray(colFamily);
            break;
        case ROW_COLFAM_COLQUAL:
            returnKey.row = row;
            returnKey.colFamily = colFamily;
            returnKey.colQualifier = followingArray(colQualifier);
            break;
        case ROW_COLFAM_COLQUAL_COLVIS:
            // This isn't useful for inserting into accumulo, but may be useful for lookups.
            returnKey.row = row;
            returnKey.colFamily = colFamily;
            returnKey.colQualifier = colQualifier;
            returnKey.colVisibility = followingArray(colVisibility);
            break;
        case ROW_COLFAM_COLQUAL_COLVIS_TIME:
            returnKey.row = row;
            returnKey.colFamily = colFamily;
            returnKey.colQualifier = colQualifier;
            returnKey.colVisibility = colVisibility;
            returnKey.setTimestamp(timestamp - 1);
            returnKey.deleted = false;
            break;
        default:
            throw new IllegalArgumentException("Partial key specification " + part + " disallowed");
        }
        return returnKey;
    }

    /**
     * Creates a key with the same row, column family, column qualifier, column visibility, timestamp, and delete marker as the given key.
     */
    public Key(Key other) {
        set(other);
    }

    /**
     * Creates a key from Thrift.
     *
     * @param tkey
     *          Thrift key
     */
    public Key(TKey tkey) {
        this.row = toBytes(tkey.row);
        this.colFamily = toBytes(tkey.colFamily);
        this.colQualifier = toBytes(tkey.colQualifier);
        this.colVisibility = toBytes(tkey.colVisibility);
        this.timestamp = tkey.timestamp;
        this.deleted = false;

        if (row == null) {
            throw new IllegalArgumentException("null row");
        }
        if (colFamily == null) {
            throw new IllegalArgumentException("null column family");
        }
        if (colQualifier == null) {
            throw new IllegalArgumentException("null column qualifier");
        }
        if (colVisibility == null) {
            throw new IllegalArgumentException("null column visibility");
        }
    }

    /**
     * Writes the row ID into the given <code>Text</code>. This method gives users control over allocation of Text objects by copying into the passed in text.
     *
     * @param r
     *          <code>Text</code> object to copy into
     * @return the <code>Text</code> that was passed in
     */
    public Text getRow(Text r) {
        r.set(row, 0, row.length);
        return r;
    }

    /**
     * Returns the row ID as a byte sequence. This method returns a pointer to the key's internal data and does not copy it.
     *
     * @return ByteSequence that points to the internal key row ID data
     */
    public ByteSequence getRowData() {
        return new ArrayByteSequence(row);
    }

    /**
     * Gets the row ID as a <code>Text</code> object.
     *
     * @return Text containing the row ID
     */
    public Text getRow() {
        return getRow(new Text());
    }

    /**
     * Compares this key's row ID with another.
     *
     * @param r
     *          row ID to compare
     * @return same as {@link #getRow()}.compareTo(r)
     */
    public int compareRow(Text r) {
        return WritableComparator.compareBytes(row, 0, row.length, r.getBytes(), 0, r.getLength());
    }

    /**
     * Returns the column family as a byte sequence. This method returns a pointer to the key's internal data and does not copy it.
     *
     * @return ByteSequence that points to the internal key column family data
     */
    public ByteSequence getColumnFamilyData() {
        return new ArrayByteSequence(colFamily);
    }

    /**
     * Writes the column family into the given <code>Text</code>. This method gives users control over allocation of Text objects by copying into the passed in
     * text.
     *
     * @param cf
     *          <code>Text</code> object to copy into
     * @return the <code>Text</code> that was passed in
     */
    public Text getColumnFamily(Text cf) {
        cf.set(colFamily, 0, colFamily.length);
        return cf;
    }

    /**
     * Gets the column family as a <code>Text</code> object.
     *
     * @return Text containing the column family
     */
    public Text getColumnFamily() {
        return getColumnFamily(new Text());
    }

    /**
     * Compares this key's column family with another.
     *
     * @param cf
     *          column family to compare
     * @return same as {@link #getColumnFamily()}.compareTo(cf)
     */

    public int compareColumnFamily(Text cf) {
        return WritableComparator.compareBytes(colFamily, 0, colFamily.length, cf.getBytes(), 0, cf.getLength());
    }

    /**
     * Returns the column qualifier as a byte sequence. This method returns a pointer to the key's internal data and does not copy it.
     *
     * @return ByteSequence that points to the internal key column qualifier data
     */
    public ByteSequence getColumnQualifierData() {
        return new ArrayByteSequence(colQualifier);
    }

    /**
     * Writes the column qualifier into the given <code>Text</code>. This method gives users control over allocation of Text objects by copying into the passed in
     * text.
     *
     * @param cq
     *          <code>Text</code> object to copy into
     * @return the <code>Text</code> that was passed in
     */
    public Text getColumnQualifier(Text cq) {
        cq.set(colQualifier, 0, colQualifier.length);
        return cq;
    }

    /**
     * Gets the column qualifier as a <code>Text</code> object.
     *
     * @return Text containing the column qualifier
     */
    public Text getColumnQualifier() {
        return getColumnQualifier(new Text());
    }

    /**
     * Compares this key's column qualifier with another.
     *
     * @param cq
     *          column qualifier to compare
     * @return same as {@link #getColumnQualifier()}.compareTo(cq)
     */
    public int compareColumnQualifier(Text cq) {
        return WritableComparator.compareBytes(colQualifier, 0, colQualifier.length, cq.getBytes(), 0,
                cq.getLength());
    }

    /**
     * Sets the timestamp.
     *
     * @param ts
     *          timestamp
     */
    public void setTimestamp(long ts) {
        this.timestamp = ts;
    }

    /**
     * Gets the timestamp.
     *
     * @return timestamp
     */
    public long getTimestamp() {
        return timestamp;
    }

    /**
     * Determines if this key is deleted (i.e., has a delete marker = true).
     *
     * @return true if key is deleted, false if not
     */
    public boolean isDeleted() {
        return deleted;
    }

    /**
     * Sets the delete marker on this key.
     *
     * @param deleted
     *          delete marker (true to delete)
     */
    public void setDeleted(boolean deleted) {
        this.deleted = deleted;
    }

    /**
     * Returns the column visibility as a byte sequence. This method returns a pointer to the key's internal data and does not copy it.
     *
     * @return ByteSequence that points to the internal key column visibility data
     */
    public ByteSequence getColumnVisibilityData() {
        return new ArrayByteSequence(colVisibility);
    }

    /**
     * Gets the column visibility as a <code>Text</code> object.
     *
     * @return Text containing the column visibility
     */
    public final Text getColumnVisibility() {
        return getColumnVisibility(new Text());
    }

    /**
     * Writes the column visibvility into the given <code>Text</code>. This method gives users control over allocation of Text objects by copying into the passed
     * in text.
     *
     * @param cv
     *          <code>Text</code> object to copy into
     * @return the <code>Text</code> that was passed in
     */
    public final Text getColumnVisibility(Text cv) {
        cv.set(colVisibility, 0, colVisibility.length);
        return cv;
    }

    /**
     * Gets the column visibility. <b>WARNING:</b> using this method may inhibit performance since a new ColumnVisibility object is created on every call.
     *
     * @return ColumnVisibility representing the column visibility
     * @since 1.5.0
     */
    public final ColumnVisibility getColumnVisibilityParsed() {
        return new ColumnVisibility(colVisibility);
    }

    /**
     * Sets this key's row, column family, column qualifier, column visibility, timestamp, and delete marker to be the same as another key's. This method does not
     * copy data from the other key, but only references to it.
     *
     * @param k
     *          key to set from
     */
    public void set(Key k) {
        row = k.row;
        colFamily = k.colFamily;
        colQualifier = k.colQualifier;
        colVisibility = k.colVisibility;
        timestamp = k.timestamp;
        deleted = k.deleted;

    }

    @Override
    public void readFields(DataInput in) throws IOException {
        // this method is a little screwy so it will be compatible with older
        // code that serialized data

        int colFamilyOffset = WritableUtils.readVInt(in);
        int colQualifierOffset = WritableUtils.readVInt(in);
        int colVisibilityOffset = WritableUtils.readVInt(in);
        int totalLen = WritableUtils.readVInt(in);

        row = new byte[colFamilyOffset];
        colFamily = new byte[colQualifierOffset - colFamilyOffset];
        colQualifier = new byte[colVisibilityOffset - colQualifierOffset];
        colVisibility = new byte[totalLen - colVisibilityOffset];

        in.readFully(row);
        in.readFully(colFamily);
        in.readFully(colQualifier);
        in.readFully(colVisibility);

        timestamp = WritableUtils.readVLong(in);
        deleted = in.readBoolean();
    }

    @Override
    public void write(DataOutput out) throws IOException {

        int colFamilyOffset = row.length;
        int colQualifierOffset = colFamilyOffset + colFamily.length;
        int colVisibilityOffset = colQualifierOffset + colQualifier.length;
        int totalLen = colVisibilityOffset + colVisibility.length;

        WritableUtils.writeVInt(out, colFamilyOffset);
        WritableUtils.writeVInt(out, colQualifierOffset);
        WritableUtils.writeVInt(out, colVisibilityOffset);

        WritableUtils.writeVInt(out, totalLen);

        out.write(row);
        out.write(colFamily);
        out.write(colQualifier);
        out.write(colVisibility);

        WritableUtils.writeVLong(out, timestamp);
        out.writeBoolean(deleted);
    }

    /**
     * Compares part of a key. For example, compares just the row and column family, and if those are equal then return true.
     *
     * @param other
     *          key to compare to
     * @param part
     *          part of key to compare
     * @return true if specified parts of keys match, false otherwise
     */
    public boolean equals(Key other, PartialKey part) {
        switch (part) {
        case ROW:
            return isEqual(row, other.row);
        case ROW_COLFAM:
            return isEqual(row, other.row) && isEqual(colFamily, other.colFamily);
        case ROW_COLFAM_COLQUAL:
            return isEqual(row, other.row) && isEqual(colFamily, other.colFamily)
                    && isEqual(colQualifier, other.colQualifier);
        case ROW_COLFAM_COLQUAL_COLVIS:
            return isEqual(row, other.row) && isEqual(colFamily, other.colFamily)
                    && isEqual(colQualifier, other.colQualifier) && isEqual(colVisibility, other.colVisibility);
        case ROW_COLFAM_COLQUAL_COLVIS_TIME:
            return isEqual(row, other.row) && isEqual(colFamily, other.colFamily)
                    && isEqual(colQualifier, other.colQualifier) && isEqual(colVisibility, other.colVisibility)
                    && timestamp == other.timestamp;
        case ROW_COLFAM_COLQUAL_COLVIS_TIME_DEL:
            return isEqual(row, other.row) && isEqual(colFamily, other.colFamily)
                    && isEqual(colQualifier, other.colQualifier) && isEqual(colVisibility, other.colVisibility)
                    && timestamp == other.timestamp && deleted == other.deleted;
        default:
            throw new IllegalArgumentException("Unrecognized partial key specification " + part);
        }
    }

    /**
     * Compares elements of a key given by a {@link PartialKey}. The corresponding elements (row, column family, column qualifier, column visibility, timestamp,
     * and delete marker) are compared in order until unequal elements are found. The row, column family, column qualifier, and column visibility are compared
     * lexographically and sorted ascending. The timestamps are compared numerically and sorted descending so that the most recent data comes first. Lastly, a
     * delete marker of true sorts before a delete marker of false. The result of the first unequal comparison is returned.
     *
     * For example, for {@link PartialKey#ROW_COLFAM}, this method compares just the row and column family. If the row IDs are not equal, return the result of the
     * row comparison; otherwise, returns the result of the column family comparison.
     *
     * @param other
     *          key to compare to
     * @param part
     *          part of key to compare
     * @return comparison result
     * @see #compareTo(Key)
     */
    public int compareTo(Key other, PartialKey part) {
        // check for matching row
        int result = WritableComparator.compareBytes(row, 0, row.length, other.row, 0, other.row.length);
        if (result != 0 || part.equals(PartialKey.ROW))
            return result;

        // check for matching column family
        result = WritableComparator.compareBytes(colFamily, 0, colFamily.length, other.colFamily, 0,
                other.colFamily.length);
        if (result != 0 || part.equals(PartialKey.ROW_COLFAM))
            return result;

        // check for matching column qualifier
        result = WritableComparator.compareBytes(colQualifier, 0, colQualifier.length, other.colQualifier, 0,
                other.colQualifier.length);
        if (result != 0 || part.equals(PartialKey.ROW_COLFAM_COLQUAL))
            return result;

        // check for matching column visibility
        result = WritableComparator.compareBytes(colVisibility, 0, colVisibility.length, other.colVisibility, 0,
                other.colVisibility.length);
        if (result != 0 || part.equals(PartialKey.ROW_COLFAM_COLQUAL_COLVIS))
            return result;

        // check for matching timestamp
        if (timestamp < other.timestamp)
            result = 1;
        else if (timestamp > other.timestamp)
            result = -1;
        else
            result = 0;

        if (result != 0 || part.equals(PartialKey.ROW_COLFAM_COLQUAL_COLVIS_TIME))
            return result;

        // check for matching deleted flag
        if (deleted)
            result = other.deleted ? 0 : -1;
        else
            result = other.deleted ? 1 : 0;

        return result;
    }

    @Override
    public int compareTo(Key other) {
        return compareTo(other, PartialKey.ROW_COLFAM_COLQUAL_COLVIS_TIME_DEL);
    }

    @Override
    public int hashCode() {
        return WritableComparator.hashBytes(row, row.length)
                + WritableComparator.hashBytes(colFamily, colFamily.length)
                + WritableComparator.hashBytes(colQualifier, colQualifier.length)
                + WritableComparator.hashBytes(colVisibility, colVisibility.length)
                + (int) (timestamp ^ (timestamp >>> 32));
    }

    /**
     * Returns an ASCII printable string form of the given byte array, treating the bytes as ASCII characters. See
     * {@link #appendPrintableString(byte[], int, int, int, StringBuilder)} for caveats.
     *
     * @param ba
     *          byte array
     * @param offset
     *          offset to start with in byte array (inclusive)
     * @param len
     *          number of bytes to print
     * @param maxLen
     *          maximum number of bytes to convert to printable form
     * @return printable string
     * @see #appendPrintableString(byte[], int, int, int, StringBuilder)
     */
    public static String toPrintableString(byte ba[], int offset, int len, int maxLen) {
        return appendPrintableString(ba, offset, len, maxLen, new StringBuilder()).toString();
    }

    /**
     * Appends ASCII printable characters to a string, based on the given byte array, treating the bytes as ASCII characters. If a byte can be converted to a
     * ASCII printable character it is appended as is; otherwise, it is appended as a character code, e.g., %05; for byte value 5. If len &gt; maxlen, the string
     * includes a "TRUNCATED" note at the end.
     *
     * @param ba
     *          byte array
     * @param offset
     *          offset to start with in byte array (inclusive)
     * @param len
     *          number of bytes to print
     * @param maxLen
     *          maximum number of bytes to convert to printable form
     * @param sb
     *          <code>StringBuilder</code> to append to
     * @return given <code>StringBuilder</code>
     */
    public static StringBuilder appendPrintableString(byte ba[], int offset, int len, int maxLen,
            StringBuilder sb) {
        int plen = Math.min(len, maxLen);

        for (int i = 0; i < plen; i++) {
            int c = 0xff & ba[offset + i];
            if (c >= 32 && c <= 126)
                sb.append((char) c);
            else
                sb.append("%" + String.format("%02x;", c));
        }

        if (len > maxLen) {
            sb.append("... TRUNCATED");
        }

        return sb;
    }

    private StringBuilder rowColumnStringBuilder() {
        return rowColumnStringBuilder(Constants.MAX_DATA_TO_PRINT);
    }

    private StringBuilder rowColumnStringBuilder(int maxComponentLength) {
        StringBuilder sb = new StringBuilder();
        appendPrintableString(row, 0, row.length, maxComponentLength, sb);
        sb.append(" ");
        appendPrintableString(colFamily, 0, colFamily.length, maxComponentLength, sb);
        sb.append(":");
        appendPrintableString(colQualifier, 0, colQualifier.length, maxComponentLength, sb);
        sb.append(" [");
        appendPrintableString(colVisibility, 0, colVisibility.length, maxComponentLength, sb);
        sb.append("]");
        return sb;
    }

    @Override
    public String toString() {
        StringBuilder sb = rowColumnStringBuilder();
        sb.append(" ");
        sb.append(Long.toString(timestamp));
        sb.append(" ");
        sb.append(deleted);
        return sb.toString();
    }

    /**
     * Stringify this {@link Key}, avoiding truncation of each component, only limiting each component to a length of {@link Integer#MAX_VALUE}
     *
     * @since 1.7.0
     */
    public String toStringNoTruncate() {
        StringBuilder sb = rowColumnStringBuilder(Integer.MAX_VALUE);
        sb.append(" ");
        sb.append(Long.toString(timestamp));
        sb.append(" ");
        sb.append(deleted);
        return sb.toString();
    }

    /**
     * Converts this key to a string, not including timestamp or delete marker.
     *
     * @return string form of key
     */
    public String toStringNoTime() {
        return rowColumnStringBuilder().toString();
    }

    /**
     * Returns the sums of the lengths of the row, column family, column qualifier, and column visibility.
     *
     * @return sum of key field lengths
     */
    public int getLength() {
        return row.length + colFamily.length + colQualifier.length + colVisibility.length;
    }

    /**
     * Same as {@link #getLength()}.
     *
     * @return sum of key field lengths
     */
    public int getSize() {
        return getLength();
    }

    private static boolean isEqual(byte a1[], byte a2[]) {
        if (a1 == a2)
            return true;

        int last = a1.length;

        if (last != a2.length)
            return false;

        if (last == 0)
            return true;

        // since sorted data is usually compared in accumulo,
        // the prefixes will normally be the same... so compare
        // the last two charachters first.. the most likely place
        // to have disorder is at end of the strings when the
        // data is sorted... if those are the same compare the rest
        // of the data forward... comparing backwards is slower
        // (compiler and cpu optimized for reading data forward)..
        // do not want slower comparisons when data is equal...
        // sorting brings equals data together

        last--;

        if (a1[last] == a2[last]) {
            for (int i = 0; i < last; i++)
                if (a1[i] != a2[i])
                    return false;
        } else {
            return false;
        }

        return true;

    }

    /**
     * Compresses a list of key/value pairs before sending them via thrift.
     *
     * @param param
     *          list of key/value pairs
     * @return list of Thrift key/value pairs
     */
    public static List<TKeyValue> compress(List<? extends KeyValue> param) {

        List<TKeyValue> tkvl = Arrays.asList(new TKeyValue[param.size()]);

        if (param.size() > 0)
            tkvl.set(0, new TKeyValue(param.get(0).getKey().toThrift(),
                    ByteBuffer.wrap(param.get(0).getValue().get())));

        for (int i = param.size() - 1; i > 0; i--) {
            Key prevKey = param.get(i - 1).getKey();
            KeyValue kv = param.get(i);
            Key key = kv.getKey();

            TKey newKey = null;

            if (isEqual(prevKey.row, key.row)) {
                newKey = key.toThrift();
                newKey.row = null;
            }

            if (isEqual(prevKey.colFamily, key.colFamily)) {
                if (newKey == null)
                    newKey = key.toThrift();
                newKey.colFamily = null;
            }

            if (isEqual(prevKey.colQualifier, key.colQualifier)) {
                if (newKey == null)
                    newKey = key.toThrift();
                newKey.colQualifier = null;
            }

            if (isEqual(prevKey.colVisibility, key.colVisibility)) {
                if (newKey == null)
                    newKey = key.toThrift();
                newKey.colVisibility = null;
            }

            if (newKey == null)
                newKey = key.toThrift();

            tkvl.set(i, new TKeyValue(newKey, ByteBuffer.wrap(kv.getValue().get())));
        }

        return tkvl;
    }

    /**
     * Decompresses a list of key/value pairs received from thrift. Decompression occurs in place, in the list.
     *
     * @param param
     *          list of Thrift key/value pairs
     */
    public static void decompress(List<TKeyValue> param) {
        for (int i = 1; i < param.size(); i++) {
            TKey prevKey = param.get(i - 1).key;
            TKey key = param.get(i).key;

            if (key.row == null) {
                key.row = prevKey.row;
            }
            if (key.colFamily == null) {
                key.colFamily = prevKey.colFamily;
            }
            if (key.colQualifier == null) {
                key.colQualifier = prevKey.colQualifier;
            }
            if (key.colVisibility == null) {
                key.colVisibility = prevKey.colVisibility;
            }
        }
    }

    /**
     * Gets the row ID as a byte array.
     *
     * @return row ID
     */
    byte[] getRowBytes() {
        return row;
    }

    /**
     * Gets the column family as a byte array.
     *
     * @return column family
     */
    byte[] getColFamily() {
        return colFamily;
    }

    /**
     * Gets the column qualifier as a byte array.
     *
     * @return column qualifier
     */
    byte[] getColQualifier() {
        return colQualifier;
    }

    /**
     * Gets the column visibility as a byte array.
     *
     * @return column visibility
     */
    byte[] getColVisibility() {
        return colVisibility;
    }

    /**
     * Converts this key to Thrift.
     *
     * @return Thrift key
     */
    public TKey toThrift() {
        return new TKey(ByteBuffer.wrap(row), ByteBuffer.wrap(colFamily), ByteBuffer.wrap(colQualifier),
                ByteBuffer.wrap(colVisibility), timestamp);
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        Key r = (Key) super.clone();
        r.row = Arrays.copyOf(row, row.length);
        r.colFamily = Arrays.copyOf(colFamily, colFamily.length);
        r.colQualifier = Arrays.copyOf(colQualifier, colQualifier.length);
        r.colVisibility = Arrays.copyOf(colVisibility, colVisibility.length);
        return r;
    }
}