/*
* 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.model;
import java.nio.ByteBuffer;
import java.util.Objects;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import com.cinchapi.concourse.server.io.Byteable;
import com.cinchapi.concourse.util.ByteBuffers;
import com.google.common.base.Preconditions;
/**
* A Position is an abstraction for the association between a
* relative location and a {@link PrimaryKey} that is used in a
* {@link SearchRecord} to specify the location of a term in a record.
*
* @author Jeff Nelson
*/
@Immutable
public final class Position implements Byteable, Comparable<Position> {
/**
* Return the Position encoded in {@code bytes} so long as those bytes
* adhere to the format specified by the {@link #getBytes()} method. This
* method assumes that all the bytes in the {@code bytes} belong to the
* Position. In general, it is necessary to get the appropriate Position
* slice from the parent ByteBuffer using
* {@link ByteBuffers#slice(ByteBuffer, int, int)}.
*
* @param bytes
* @return the Position
*/
public static Position fromByteBuffer(ByteBuffer bytes) {
PrimaryKey primaryKey = PrimaryKey.fromByteBuffer(ByteBuffers.get(
bytes, PrimaryKey.SIZE));
int index = bytes.getInt();
return new Position(primaryKey, index);
}
/**
* Return a Position that is backed by {@code primaryKey} and {@code index}.
*
* @param primaryKey
* @param index
* @return the Position
*/
public static Position wrap(PrimaryKey primaryKey, int index) {
return new Position(primaryKey, index);
}
/**
* The total number of bytes used to store each Position
*/
public static final int SIZE = PrimaryKey.SIZE + 4; // index
/**
* A cached copy of the binary representation that is returned from
* {@link #getBytes()}.
*/
private transient ByteBuffer bytes;
/**
* The index that this Position represents.
*/
private final int index;
/**
* The PrimaryKey of the record that this Position represents.
*/
private final PrimaryKey primaryKey;
/**
* Construct a new instance.
*
* @param primaryKey
* @param index
*/
private Position(PrimaryKey primaryKey, int index) {
this(primaryKey, index, null);
}
/**
* Construct a new instance.
*
* @param primaryKey
* @param index
* @param bytes;
*/
private Position(PrimaryKey primaryKey, int index,
@Nullable ByteBuffer bytes) {
Preconditions
.checkArgument(index >= 0, "Cannot have an negative index");
this.primaryKey = primaryKey;
this.index = index;
this.bytes = bytes;
}
@Override
public int compareTo(Position other) {
int comparison;
return (comparison = primaryKey.compareTo(other.primaryKey)) != 0 ? comparison
: Integer.compare(index, other.index);
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Position) {
Position other = (Position) obj;
return primaryKey.equals(other.primaryKey) && index == other.index;
}
return false;
}
/**
* Return a byte buffer that represents this Value with the following order:
* <ol>
* <li><strong>primaryKey</strong> - position 0</li>
* <li><strong>index</strong> - position 8</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 associated {@code index}.
*
* @return the index
*/
public int getIndex() {
return index;
}
/**
* Return the associated {@code primaryKey}.
*
* @return the primaryKey
*/
public PrimaryKey getPrimaryKey() {
return primaryKey;
}
@Override
public int hashCode() {
return Objects.hash(primaryKey, index);
}
@Override
public int size() {
return SIZE;
}
@Override
public String toString() {
return "Position " + index + " in Record " + primaryKey;
}
@Override
public void copyTo(ByteBuffer buffer) {
// NOTE: Storing the index as an int instead of some size aware
// variable length is probably overkill since most indexes will be
// smaller than Byte.MAX_SIZE or Short.MAX_SIZE, but having variable
// size indexes means that the size of the entire Position (as an
// int) must be stored before the Position for proper
// deserialization. By storing the index as an int, the size of each
// Position is constant so we won't need to store the overall size
// prior to the Position to deserialize it, which is actually more
// space efficient.
primaryKey.copyTo(buffer);
buffer.putInt(index);
}
}