/*
* Copyright (c) 2013-2017 Cinchapi Inc.
*
* Licensed 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 com.cinchapi.concourse.server.storage.db;
import java.nio.ByteBuffer;
import java.util.Objects;
import javax.annotation.concurrent.Immutable;
import com.cinchapi.concourse.annotate.DoNotInvoke;
import com.cinchapi.concourse.server.io.Byteable;
import com.cinchapi.concourse.server.io.Byteables;
import com.cinchapi.concourse.server.model.Position;
import com.cinchapi.concourse.server.model.PrimaryKey;
import com.cinchapi.concourse.server.model.Text;
import com.cinchapi.concourse.server.model.Value;
import com.cinchapi.concourse.server.storage.Action;
import com.cinchapi.concourse.server.storage.Versioned;
import com.cinchapi.concourse.util.ByteBuffers;
import com.google.common.base.Preconditions;
/**
* A Revision represents a modification involving a {@code locator}, {@code key}
* and {@code value} at a {@code version} and is used to organize indexed data
* that is permanently stored in a {@link Block} or viewed in a {@link Record}.
*
*
* @author Jeff Nelson
* @param <L> - the locator type
* @param <K> - the key type
* @param <V> - the value type
*/
@Immutable
public abstract class Revision<L extends Comparable<L> & Byteable, K extends Comparable<K> & Byteable, V extends Comparable<V> & Byteable> implements
Byteable,
Versioned {
/**
* Create a PrimaryRevision for {@code key} as {@code value} in
* {@code record} at {@code version}.
*
* @param record
* @param key
* @param value
* @param version
* @param type
*
* @return the PrimaryRevision
*/
public static PrimaryRevision createPrimaryRevision(PrimaryKey record,
Text key, Value value, long version, Action type) {
return new PrimaryRevision(record, key, value, version, type);
}
/**
* Create a SearchRevision for {@code word} at {@code position} for
* {@code key} at {@code version}.
*
* @param key
* @param word
* @param position
* @param version
* @param type
* @return the SearchRevision
*/
public static SearchRevision createSearchRevision(Text key, Text word,
Position position, long version, Action type) {
return new SearchRevision(key, word, position, version, type);
}
/**
* Create a SecondaryRevision for {@code key} as {@code value} in
* {@code record} at {@code version}.
*
* @param key
* @param value
* @param record
* @param version
* @param type
* @return the SecondaryRevision
*/
public static SecondaryRevision createSecondaryRevision(Text key,
Value value, PrimaryKey record, long version, Action type) {
return new SecondaryRevision(key, value, record, version, type);
}
/**
* Indicates that a component of the class has variable length and therefore
* must encode the size of that component for each instance.
*/
static final int VARIABLE_SIZE = -1;
/**
* A cached copy of the binary representation that is returned from
* {@link #getBytes()}.
*/
private transient ByteBuffer bytes = null;
/**
* The secondary component used to locate the field in the index, to which
* this Revision belongs.
*/
private final K key;
/**
* The primary component used to locate the index, to which this Revision
* belongs.
*/
private final L locator;
/**
* The number of bytes used to store the Revision. This value depends on
* the number of variable sized components.
*/
private transient final int size;
/**
* An field indicating the action performed to generate this Revision. This
* information is recorded so that we can efficiently purge history while
* maintaining consistent state.
*/
private final Action type;
/**
* The tertiary component that typically represents the payload for what
* this Revision represents.
*/
private final V value;
/**
* The unique version that identifies this Revision. Versions are assumed to
* be an atomically increasing values (i.e. timestamps).
*/
private final long version;
/**
* Construct an instance that represents an existing Revision from a
* ByteBuffer. This constructor is public so as to comply with the
* {@link Byteable} interface. Calling this constructor directly is not
* recommend. Use {@link #fromByteBuffer(ByteBuffer)} instead to take
* advantage of reference caching.
*
* @param bytes
*/
/*
* (non-Javadoc)
* This constructor exists and is public so that subclass instances can be
* dynamically deserialized using the Byteables#read() method.
*/
@DoNotInvoke
public Revision(ByteBuffer bytes) {
this.bytes = bytes;
this.type = Action.values()[bytes.get()];
this.version = bytes.getLong();
this.locator = Byteables.readStatic(ByteBuffers.get(bytes,
xLocatorSize() == VARIABLE_SIZE ? bytes.getInt()
: xLocatorSize()), xLocatorClass());
this.key = Byteables.readStatic(ByteBuffers.get(bytes,
xKeySize() == VARIABLE_SIZE ? bytes.getInt() : xKeySize()),
xKeyClass());
this.value = Byteables.readStatic(
ByteBuffers.get(bytes, bytes.remaining()), xValueClass());
this.size = bytes.capacity();
}
/**
* Construct a new instance.
*
* @param locator
* @param key
* @param value
* @param version
* @param type
*/
protected Revision(L locator, K key, V value, long version, Action type) {
Preconditions.checkArgument(type != Action.COMPARE);
this.type = type;
this.locator = locator;
this.key = key;
this.value = value;
this.version = version;
this.size = 1 + 8 + (xLocatorSize() == VARIABLE_SIZE ? 4 : 0)
+ (xKeySize() == VARIABLE_SIZE ? 4 : 0) + locator.size()
+ key.size() + value.size();
}
/**
* Return a {@link CompactRevision} that is appropriate to store in a
* {@link Record records} history collection. After calling this method, it
* is okay to set this instance equal to {@code null}.
*
* @return the compact form of this Revision.
*/
public CompactRevision<V> compact() {
return new CompactRevision<V>(value, version, type);
}
/**
* {@inheritDoc}
* <p>
* <strong>NOTE: The Revision type is NOT considered when determining
* equality.</strong>
* </p>
*/
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object obj) {
if(obj.getClass() == this.getClass()) {
Revision<L, K, V> other = (Revision<L, K, V>) obj;
return locator.equals(other.locator) && key.equals(other.key)
&& value.equals(other.value);
}
return false;
}
/**
* Return a byte buffer that represents this Revision with the following
* order:
* <ol>
* <li><strong>version</strong></li>
* <li><strong>locatorSize</strong> -
* <em>if {@link #xLocatorSize()} == {@link #VARIABLE_SIZE}</em></li>
* <li><strong>locator</strong></li>
* <li><strong>keySize</strong> -
* <em>if {@link #xKeySize()} == {@link #VARIABLE_SIZE}</em></li>
* <li><strong>key</strong></li>
* <li><strong>value</strong></li>
*
* </ol>
*
* @return the ByteBuffer representation
*/
@Override
public ByteBuffer getBytes() {
if(bytes == null) {
bytes = ByteBuffer.allocate(size());
copyTo(bytes);
bytes.rewind();
}
return ByteBuffers.asReadOnlyBuffer(bytes);
}
/**
* Return the {@link #key} associated with this Revision.
*
* @return the key
*/
public K getKey() {
return key;
}
/**
* Return the {@link #locator} associated with this Revision.
*
* @return the locator
*/
public L getLocator() {
return locator;
}
/**
* Return the {@link #type} associated with this Revision.
*
* @return the type
*/
public Action getType() {
return type;
}
/**
* Return the {@link #value} associated with this Revision.
*
* @return the value
*/
public V getValue() {
return value;
}
@Override
public long getVersion() {
return version;
}
@Override
public int hashCode() {
return Objects.hash(locator, key, value);
}
@Override
public boolean isStorable() {
return version != NO_VERSION;
}
@Override
public int size() {
return size;
}
@Override
public String toString() {
return type + " " + key + " AS " + value + " IN " + locator + " AT "
+ version;
}
@Override
public void copyTo(ByteBuffer buffer) {
buffer.put((byte) type.ordinal());
buffer.putLong(version);
if(xLocatorSize() == VARIABLE_SIZE) {
buffer.putInt(locator.size());
}
locator.copyTo(buffer);
if(xKeySize() == VARIABLE_SIZE) {
buffer.putInt(key.size());
}
key.copyTo(buffer);
value.copyTo(buffer);
}
/**
* Return the class of the {@link #key} type.
*
* @return they key class
*/
protected abstract Class<K> xKeyClass();
/**
* Return the size used to store each {@link #key}. If this value is not
* fixed, return {@link #VARIABLE_SIZE}.
*
* @return the key size
*/
protected abstract int xKeySize();
/**
* Return the class of the {@link locator} type.
*
* @return the locator class
*/
protected abstract Class<L> xLocatorClass();
/**
* Return the size used to store each {@link #locator}. If this value is not
* fixed, return {@link #VARIABLE_SIZE}.
*
* @return the locator size
*/
protected abstract int xLocatorSize();
/**
* Return the class of the {@link #value} type.
*
* @return the value class
*/
protected abstract Class<V> xValueClass();
}