/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.github.geophile.erdo;
import com.github.geophile.erdo.apiimpl.ErdoId;
import com.github.geophile.erdo.map.Factory;
import com.github.geophile.erdo.transaction.Transaction;
import com.github.geophile.erdo.util.Transferrable;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
/**
* AbstractKey is the base class for Erdo keys. Applications need to provide subclasses that contain key state,
* compare and hash keys, serialize and deserialize, copy a key, and provide an estimate of serialized size.
*
* This class contains several undocumented methods. These need to be public for implementation reasons, but are not
* intended to be used by applications or by AbstractKey subclasses.
*/
public abstract class AbstractKey implements Comparable<AbstractKey>, Transferrable
{
// Object interface
@Override
public String toString()
{
return
erdoId == ERDO_ID_UNSET ? "?" :
erdoId == ERDO_ID_UNSET_DELETED ? "?D" :
deleted() ? String.format("%sD", erdoId()) : Integer.toString(erdoId());
}
@Override
public boolean equals(Object obj)
{
return
obj != null &&
obj instanceof AbstractKey &&
this.compareTo((AbstractKey) obj) == 0;
}
@Override
public int hashCode()
{
return erdoId;
}
// Comparable interface
/**
* Indicates whether this key appears in key order before, after, or at the same position as the given key.
* @param that key to compare to.
* @return negative int if this precedes that; positive int if this follows that;
* zero if neither precedes the other.
*/
public int compareTo(AbstractKey that)
{
int c = (this.erdoId & ~ERDO_ID_DELETED_MASK) - (that.erdoId & ~ERDO_ID_DELETED_MASK);
if (c == 0 && this != that) {
if (this instanceof ErdoId) {
c = ((ErdoId) this).lowest() ? -1 : 1;
} else if (that instanceof ErdoId) {
c = ((ErdoId) that).lowest() ? 1 : -1;
}
}
return c;
}
// Transferrable interface
/**
* Read the state of this key from the given buffer.
* @param buffer contains the serialized state of the key.
* @throws BufferUnderflowException
*/
public void readFrom(ByteBuffer buffer) throws BufferUnderflowException
{
deleted(buffer.get() != 0);
}
/**
* Write the state of this key to the given buffer.
* @param buffer container of the serialized key.
* @throws BufferOverflowException
*/
public void writeTo(ByteBuffer buffer) throws BufferOverflowException
{
buffer.put((byte) (deleted() ? 1 : 0));
}
public final int recordCount()
{
return 1;
}
// AbstractKey interface
/**
* An estimate of the size of the serialized key.
* @return estimate of the serialized key size, in bytes.
*/
public abstract int estimatedSizeBytes();
public final long transactionTimestamp()
{
if (timestamp == TIMESTAMP_NOT_SET || timestamp == Transaction.UNCOMMITTED_TIMESTAMP) {
assert transaction != null;
timestamp = transaction.timestamp();
}
return timestamp;
}
// TODO: These methods need to be public for access by internals,
// TODO: but should not be exposed to users.
public final void erdoId(int erdoId)
{
assert erdoId >= 1 : erdoId;
if (this.erdoId == ERDO_ID_UNSET) {
this.erdoId = erdoId;
} else if (this.erdoId == ERDO_ID_UNSET_DELETED) {
this.erdoId = erdoId | ERDO_ID_DELETED_MASK;
} else {
assert erdoId() == erdoId : erdoId;
}
}
public final void transaction(Transaction transaction)
{
assert this.transaction == null : transaction;
this.transaction = transaction;
this.timestamp = TIMESTAMP_NOT_SET;
}
public final void deleted(boolean deleted)
{
if (deleted) {
if (erdoId == ERDO_ID_UNSET) {
erdoId = ERDO_ID_UNSET_DELETED;
} else if (erdoId == ERDO_ID_UNSET_DELETED) {
// Nothing to do
} else {
erdoId |= ERDO_ID_DELETED_MASK;
}
} else {
if (erdoId == ERDO_ID_UNSET) {
// Nothing to do
} else if (erdoId == ERDO_ID_UNSET_DELETED) {
erdoId = ERDO_ID_UNSET;
} else {
erdoId &= ~ERDO_ID_DELETED_MASK;
}
}
}
public final void clearTransactionState()
{
transaction = null;
timestamp = TIMESTAMP_NOT_SET;
}
public static AbstractKey deserialize(Factory factory, ByteBuffer keyBuffer, int erdoId)
{
RecordFactory recordFactory = factory.recordFactory(erdoId);
AbstractKey key = recordFactory.newKey();
key.erdoId(erdoId);
key.readFrom(keyBuffer);
return key;
}
public static AbstractKey deserialize(Factory factory, ByteBuffer keyBuffer, int erdoId, long timestamp)
{
RecordFactory recordFactory = factory.recordFactory(erdoId);
AbstractKey key = recordFactory.newKey();
key.erdoId(erdoId);
key.readFrom(keyBuffer);
key.transactionTimestamp(timestamp);
return key;
}
public static int erdoId(ByteBuffer buffer)
{
int erdoId = buffer.getInt(buffer.position());
assert erdoId != ERDO_ID_UNSET;
return erdoId;
}
public final Transaction transaction()
{
return transaction;
}
/**
* Returns a copy of this key. The original and the copy should not share mutable state, so that if one is changed
* in any way, the other is not affected.
* @return A copy of this key.
*/
public abstract AbstractKey copy();
public final void transactionTimestamp(long timestamp)
{
assert this.timestamp == TIMESTAMP_NOT_SET : timestamp;
this.timestamp = timestamp;
}
public final boolean deleted()
{
return (erdoId & ERDO_ID_DELETED_MASK) != 0;
}
public final int erdoId()
{
assert !(erdoId == ERDO_ID_UNSET || erdoId == ERDO_ID_UNSET_DELETED) : this;
return erdoId & ~ERDO_ID_DELETED_MASK;
}
// For use by subclasses
protected AbstractKey(AbstractKey key)
{
this.erdoId = key.erdoId;
this.transaction = key.transaction;
this.timestamp = key.timestamp;
}
protected AbstractKey()
{}
// Class state
private static final long TIMESTAMP_NOT_SET = -1L;
private static final int ERDO_ID_UNSET = 0;
private static final int ERDO_ID_UNSET_DELETED = Integer.MIN_VALUE;
private static final int ERDO_ID_DELETED_MASK = 0x80000000;
// Object state
private volatile Transaction transaction;
private volatile long timestamp = TIMESTAMP_NOT_SET;
// A valid erdoId is between 1 and Integer.MAX_VALUE inclusive. The stored value is encoded as follows:
// - High bit is 1 if the key is deleted.
// - 0 indicates that erdoId has not been assigned, and deleted state has not been set.
// - MIN_VALUE indicates that erdoId has not been assigned, but deleted state has been set.
private int erdoId;
}