/*
* Copyright (C) 2012, 2016 higherfrequencytrading.com
* Copyright (C) 2016 Roman Leventov
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.openhft.chronicle.hash.impl.stage.entry;
import net.openhft.chronicle.algo.bytes.Access;
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.hash.ChecksumEntry;
import net.openhft.chronicle.hash.Data;
import net.openhft.chronicle.hash.HashEntry;
import net.openhft.chronicle.hash.impl.LocalLockState;
import net.openhft.chronicle.hash.impl.VanillaChronicleHashHolder;
import net.openhft.chronicle.hash.impl.stage.data.bytes.EntryKeyBytesData;
import net.openhft.chronicle.hash.impl.stage.hash.CheckOnEachPublicOperation;
import net.openhft.sg.Stage;
import net.openhft.sg.StageRef;
import net.openhft.sg.Staged;
import org.jetbrains.annotations.NotNull;
import static net.openhft.chronicle.algo.bytes.Access.checkedBytesStoreAccess;
import static net.openhft.chronicle.algo.bytes.Access.nativeAccess;
@Staged
public abstract class HashEntryStages<K> implements HashEntry<K>, ChecksumEntry {
@StageRef public VanillaChronicleHashHolder<?> hh;
@StageRef public SegmentStages s;
@StageRef public CheckOnEachPublicOperation checkOnEachPublicOperation;
@StageRef public HashLookupPos hlp;
public long pos = -1;
public void initPos(long pos) {
this.pos = pos;
}
public abstract void closePos();
@Stage("EntryOffset") public long keySizeOffset = -1;
public abstract boolean entryOffsetInit();
public void initEntryOffset(long keySizeOffset) {
this.keySizeOffset = keySizeOffset;
}
public void initEntryOffset() {
keySizeOffset = s.entrySpaceOffset + pos * hh.h().chunkSize;
}
public abstract void closeEntryOffset();
public long keySize = -1;
public void initKeySize(long keySize) {
this.keySize = keySize;
}
public abstract void closeKeySize();
public long keyOffset = -1;
public void initKeyOffset(long keyOffset) {
this.keyOffset = keyOffset;
}
public abstract void closeKeyOffset();
public void readExistingEntry(long pos) {
initPos(pos);
Bytes segmentBytes = s.segmentBytesForRead();
segmentBytes.readPosition(keySizeOffset);
initKeySize(hh.h().keySizeMarshaller.readSize(segmentBytes));
initKeyOffset(segmentBytes.readPosition());
}
public void readFoundEntry(long pos, long keySizeOffset, long keySize, long keyOffset) {
initPos(pos);
initEntryOffset(keySizeOffset);
initKeySize(keySize);
initKeyOffset(keyOffset);
}
public void closeEntry() {
closePos();
closeEntryOffset();
closeKeySize();
closeKeyOffset();
}
public void writeNewEntry(long pos, Data<?> key) {
initPos(pos);
initKeySize(key.size());
Bytes segmentBytes = s.segmentBytesForWrite();
segmentBytes.writePosition(keySizeOffset);
hh.h().keySizeMarshaller.writeSize(segmentBytes, keySize);
initKeyOffset(segmentBytes.writePosition());
key.writeTo(s.segmentBS, keyOffset);
}
public void copyExistingEntry(
long newPos, long bytesToCopy, long oldKeyAddr, long oldKeySizeAddr) {
initPos(newPos);
initKeyOffset(keySizeOffset + (oldKeyAddr - oldKeySizeAddr));
// Calling Access.copy() which is probably slower because not of abstractions,
// because there is no BytesStore.write(off, addr, len) method. Alternative is
// to make a final BytesStore rawMemoryStore = new PointerBytesStore().set(0, Long.MAX_V)
// and here: s.segmentBS.write(keySizeOffset, rawMemoryStore, keySizeAddr, bytesToCopy)
Access.copy(
nativeAccess(), null, oldKeySizeAddr,
checkedBytesStoreAccess(), s.segmentBS, keySizeOffset,
bytesToCopy);
}
public long keyEnd() {
return keyOffset + keySize;
}
public long entryEnd() {
return keyEnd();
}
@StageRef HashEntryChecksumStrategy hashEntryChecksumStrategy;
public final ChecksumStrategy checksumStrategy = hh.h().checksumEntries ?
hashEntryChecksumStrategy : NoChecksumStrategy.INSTANCE;
public boolean delayedUpdateChecksum = false;
public void initDelayedUpdateChecksum(boolean delayedUpdateChecksum) {
// makes delayedUpdateChecksum dependent on keySizeOffset and Locks stages, to trigger
// delayedUpdateChecksum close on these stages' close
assert entryOffsetInit() && keySizeOffset >= 0;
assert s.locksInit() && s.localLockState != LocalLockState.UNLOCKED;
assert delayedUpdateChecksum; // doesn't make sense to init to "uninit" false value
this.delayedUpdateChecksum = true;
}
abstract boolean delayedUpdateChecksumInit();
public void closeDelayedUpdateChecksum() {
if (hh.h().checksumEntries)
hashEntryChecksumStrategy.computeAndStoreChecksum();
delayedUpdateChecksum = false;
}
@Override
public void updateChecksum() {
checkOnEachPublicOperation.checkOnEachPublicOperation();
if (!hh.h().checksumEntries) {
throw new UnsupportedOperationException(hh.h().toIdentityString() +
": Checksum is not stored in this Chronicle Hash");
}
s.innerUpdateLock.lock();
initDelayedUpdateChecksum(true);
}
@Override
public boolean checkSum() {
checkOnEachPublicOperation.checkOnEachPublicOperation();
if (!hh.h().checksumEntries) {
throw new UnsupportedOperationException(hh.h().toIdentityString() +
": Checksum is not stored in this Chronicle Hash");
}
// This is needed, because a concurrent update lock holder might perform an entry update,
// but not yet written a checksum (because checksum write is delayed to the update unlock).
// So checkSum() on read lock level might result to false negative results.
s.innerUpdateLock.lock();
return delayedUpdateChecksumInit() || checksumStrategy.innerCheckSum();
}
long entrySize() {
return checksumStrategy.extraEntryBytes() + entryEnd() - keySizeOffset;
}
@StageRef public EntryKeyBytesData<K> entryKey;
@NotNull
@Override
public Data<K> key() {
checkOnEachPublicOperation.checkOnEachPublicOperation();
return entryKey;
}
@Stage("EntrySizeInChunks") public int entrySizeInChunks = 0;
void initEntrySizeInChunks() {
entrySizeInChunks = hh.h().inChunks(entrySize());
}
public void initEntrySizeInChunks(int actuallyUsedChunks) {
entrySizeInChunks = actuallyUsedChunks;
}
public void innerRemoveEntryExceptHashLookupUpdate() {
s.free(pos, entrySizeInChunks);
s.incrementModCount();
}
}