package com.ctriposs.bigcache.storage;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import com.ctriposs.bigcache.CacheConfig.StorageMode;
/**
* The Class StorageBlock.
*/
public class StorageBlock implements IStorageBlock {
/** The index. */
private final int index;
/** The capacity. */
private final int capacity;
/** The underlying storage. */
private IStorage underlyingStorage;
/** The offset within the storage block. */
private final AtomicInteger currentOffset = new AtomicInteger(0);
/** The dirty storage. */
private final AtomicInteger dirtyStorage = new AtomicInteger(0);
/** The used storage. */
private final AtomicInteger usedStorage = new AtomicInteger(0);
/**
* Instantiates a new storage block.
*
* @param dir the directory
* @param index the index
* @param capacity the capacity
* @throws IOException exception throws when failing to create the storage block
*/
public StorageBlock(String dir, int index, int capacity, StorageMode storageMode) throws IOException{
this.index = index;
this.capacity = capacity;
switch (storageMode) {
case PureFile:
underlyingStorage = new FileChannelStorage(dir, index, capacity);
break;
case MemoryMappedPlusFile:
underlyingStorage = new MemoryMappedStorage(dir, index, capacity);
break;
case OffHeapPlusFile:
underlyingStorage = new OffHeapStorage(capacity);
break;
}
}
@Override
public byte[] retrieve(Pointer pointer) throws IOException {
byte [] payload = new byte[pointer.getLength()];
underlyingStorage.get(pointer.getPosition(), payload);
return payload;
}
@Override
public byte[] remove(Pointer pointer) throws IOException {
byte [] payload = retrieve(pointer);
dirtyStorage.addAndGet(pointer.getLength());
usedStorage.addAndGet(-1 * pointer.getLength());
return payload;
}
@Override
public void removeLight(Pointer pointer) throws IOException {
dirtyStorage.addAndGet(pointer.getLength());
usedStorage.addAndGet(-1 * pointer.getLength());
}
@Override
public Pointer store(byte[] payload) throws IOException {
Allocation allocation = allocate(payload);
if (allocation == null) return null; // not enough storage available
Pointer pointer = store(allocation, payload);
return pointer;
}
/**
* Allocates storage for the payload, return null if not enough storage available.
*
* @param payload the payload
* @return the allocation
*/
protected Allocation allocate(byte[] payload) {
int payloadLength = payload.length;
int allocationOffset = currentOffset.addAndGet(payloadLength);
if(this.capacity < allocationOffset){
return null;
}
Allocation allocation = new Allocation(allocationOffset - payloadLength, payloadLength);
return allocation;
}
/**
* Stores the payload by the help of allocation.
*
* @param allocation the allocation
* @param payload the payload
* @return the pointer
* @throws IOException
*/
public Pointer store(Allocation allocation, byte[] payload) throws IOException {
Pointer pointer = new Pointer(allocation.getOffset(), allocation.getLength(), this);
underlyingStorage.put(allocation.getOffset(), payload);
usedStorage.addAndGet(payload.length);
return pointer;
}
@Override
public Pointer update(Pointer pointer, byte[] payload) throws IOException {
if (pointer.getLength() >= payload.length) { // has enough space to reuse
dirtyStorage.addAndGet(pointer.getLength() - payload.length);
usedStorage.addAndGet(-1 * pointer.getLength());
Allocation allocation = new Allocation(pointer.getPosition(), payload.length);
return store(allocation, payload); // should always return a new pointer
} else { // make a move
dirtyStorage.addAndGet(pointer.getLength());
usedStorage.addAndGet(-1 * pointer.getLength());
return store(payload); // may return null because not enough space available
}
}
@Override
public long getDirty() {
return this.dirtyStorage.get();
}
@Override
public double getDirtyRatio() {
return (this.getDirty() * 1.0) / this.getCapacity();
}
@Override
public long getCapacity() {
return capacity;
}
@Override
public long getUsed() {
return this.usedStorage.get();
}
@Override
public void free() {
currentOffset.set(0);
dirtyStorage.set(0);
usedStorage.set(0);
underlyingStorage.free();
}
/**
* The Class Allocation.
*/
private static class Allocation {
/** The offset. */
private int offset;
/** The length. */
private int length;
/**
* Instantiates a new allocation.
*
* @param offset the offset
* @param length the length
*/
public Allocation(int offset, int length) {
this.offset = offset;
this.length = length;
}
/**
* Gets the offset.
*
* @return the offset
*/
public int getOffset() {
return offset;
}
/**
* Gets the length.
*
* @return the length
*/
public int getLength() {
return length;
}
}
/**
* Gets the index.
*
* @return the index
*/
public int getIndex() {
return index;
}
@Override
public void close() throws IOException {
if (this.underlyingStorage != null) {
this.underlyingStorage.close();
}
}
@Override
public int compareTo(IStorageBlock o) {
if (this.getIndex() < o.getIndex()) return -1;
else if (this.getIndex() == o.getIndex()) return 0;
else return 1;
}
}