package org.dcache.ftp.data;
import java.text.MessageFormat;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* A log of transferred blocks. Adjacent blocks are merged, so for a
* sequential transfer only information about a single block will be
* maintained.
*
* Overlapping blocks are detected and reported via an exception.
*
* The class is thread safe.
*/
public class BlockLog
{
private final SortedMap<Long,Long> _blocks = new TreeMap<>();
private boolean _eof;
private static final String _overlapMsg
= "Overlapping block detected between ({0}-{1}) and ({2}-{3}).";
private long _limit;
public BlockLog()
{
_limit = Long.MAX_VALUE;
}
/**
* Adds a block to the log. Blocks do not need to be added
* sequentially.
*
* @param position A non-negative starting posisiton
* @param size A non-negative block size
* @throws FTPException if blocks overlap
*/
public synchronized void addBlock(long position, long size)
throws FTPException
{
if (size == 0) {
return;
}
long begin = position;
long end = position + size;
SortedMap<Long,Long> headMap = _blocks.headMap(position);
SortedMap<Long,Long> tailMap = _blocks.tailMap(position);
if (!headMap.isEmpty()) {
long prevBegin = headMap.lastKey();
long prevEnd = _blocks.get(prevBegin);
/* Consistency check.
*/
if (prevEnd > begin) {
String err =
MessageFormat.format(_overlapMsg,
begin, end, prevBegin, prevEnd);
throw new FTPException(err);
}
/* Merge blocks.
*/
if (prevEnd == begin) {
begin = prevBegin;
_blocks.remove(prevBegin);
}
}
if (!tailMap.isEmpty()) {
long nextBegin = tailMap.firstKey();
long nextEnd = _blocks.get(nextBegin);
/* Consistency check.
*/
if (end > nextBegin) {
String err =
MessageFormat.format(_overlapMsg,
begin, end, nextBegin, nextEnd);
throw new FTPException(err);
}
/* Merge blocks.
*/
if (end == nextBegin) {
end = nextEnd;
_blocks.remove(nextBegin);
}
}
_blocks.put(begin, end);
notifyAll();
/* The transfer can be throttled by setting a limit. Once
* everything up to that limit has been received, we block
* until the limit is raised. This assumes that addBlock was
* called from the same thread, which transfers the data.
*/
try {
while (_limit <= getCompleted()) {
wait();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* Indicates that the end of the transfer has been reached. The
* main purpose is to make sure that waitCompleted() returns.
*/
public synchronized void setEof()
{
_eof = true;
notifyAll();
}
/**
* Returns true when setEof() has been called.
*/
public synchronized boolean isEof()
{
return _eof;
}
/**
* Returns the number of fragments received, i.e. continous areas
* of the file for which either the byte before or the byte after
* the area has not been received.
*/
public synchronized int getFragments()
{
return _blocks.size();
}
/**
* Returns true when the complete file has been received. This is
* the case if EOF has been set and the complete file only
* contains one fragment.
*/
public synchronized boolean isComplete()
{
return isEof() && getFragments() <= 1;
}
/**
* Returns the number of consecutive bytes from the beginning of
* the file that have been transferred.
*/
public synchronized long getCompleted()
{
return !_blocks.containsKey(0L) ? 0L : _blocks.get(0L);
}
/**
* Blocks until getCompleted() returns a value larger than or
* equal to position or until setEof() has been called.
*/
public synchronized void waitCompleted(long position)
throws InterruptedException
{
if (_limit < position) {
setLimit(position);
}
while (getCompleted() < position && !isEof()) {
wait();
}
}
/**
* Returns the current transfer limit.
*/
public synchronized long getLimit()
{
return _limit;
}
/**
* Sets the current transfer limit.
*
* The <code>addBlock</code> method will block as soon as
* <code>getCompleted</code> would return something not smaller
* than <code>limit</code>. The idea is that the limit will
* throttle the transfer as soon as everything up to
* <code>limit</code> has been received.
*
* Using Long.MAX_VALUE for the limit effectively disables
* transfer throttling.
*
* @param limit the new limit
*/
public synchronized void setLimit(long limit)
{
_limit = limit;
notifyAll();
}
}