/*
* Copyright 1999-2006 University of Chicago
*
* 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 org.dcache.ftp.client;
import java.util.Vector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents list of ranges of integers (ByteRange objects).
* The name reflects the fact that in FTP extended mode restart markers, such
* structure represent a list of ranges of transfered bytes.
* The list has following characteristic:
* <ul>
* <li> no ranges from the list are adjacent nor have any common subset.
* In other words, for any two list members, r1.merge(r2) always
* returns ByteRange.THIS_ABOVE or ByteRange.THIS_BELOW
* <li> ranges in the list are ordered by the value of "from" field
* (or "to" field; it's the same)
* </ul>
* You cannot just add new ranges to the list, because that would violate
* the contract above. New ranges can be merge()d to the list.
*
* @see GridFTPRestartMarker
**/
public class ByteRangeList implements RestartData
{
private static final Logger logger =
LoggerFactory.getLogger(ByteRangeList.class);
/**
* vector of ByteRanges. It is guaranteed
* that any two ranges are not adjacent to each other,
* nor have a common subset.
* They are unordered, however.
**/
protected final Vector vector;
public ByteRangeList()
{
vector = new Vector();
}
/**
* @return true if this list logically represents the same range list,
* although the object instances may be different.
**/
public boolean equals(Object other)
{
if (this == other) {
return true;
}
if (other instanceof ByteRangeList) {
ByteRangeList otherObj = (ByteRangeList) other;
if (this.vector.size() != otherObj.vector.size()) {
return false;
}
for (int i = 0; i < this.vector.size(); i++) {
if (!this.vector.elementAt(i).equals(otherObj.vector.elementAt(i))) {
return false;
}
}
return true;
} else {
return false;
}
}
public int hashCode()
{
int value = 0;
for (int i = 0; i < this.vector.size(); i++) {
value += this.vector.elementAt(i).hashCode();
}
return value;
}
/**
* Merge a copy of the given ByteRange into this list.
* The resulting range list will represent
* all the integers represented so far, plus the integers represented
* by the new range.
* The resulting list will be stored in this object,
* while the parameter object will remain intact.
* For instance:
* <ul>
* <li>merge("10-15 30-35", "20-25") -> "10-15 20-25 30-35"
* <li>merge("10-15 30-35", "12-15") -> "10-15 20-25"
* <li>merge("10-15 30-35", "16-40") -> "10-40"
* </ul>
**/
public void merge(final ByteRange range)
{
// always use copies of objects
ByteRange newRange = new ByteRange(range);
logger.debug(this.toFtpCmdArgument() + " + " + newRange.toString());
int oldSize = vector.size();
int index = 0;
final int NOT_YET = -1;
int merged = NOT_YET;
if (oldSize == 0) {
vector.add(newRange);
return;
}
for (int i = 0; i < oldSize; i++) {
int result = newRange.merge((ByteRange) vector.elementAt(index));
switch (result) {
case ByteRange.THIS_ABOVE:
//last_below = index;
index++;
break;
case ByteRange.ADJACENT:
case ByteRange.THIS_SUBSET:
case ByteRange.THIS_SUPERSET:
if (merged == NOT_YET) {
vector.remove(index);
vector.add(index, newRange);
merged = index;
index++;
} else {
vector.remove(index);
//do not augment index
}
break;
case ByteRange.THIS_BELOW:
if (merged == NOT_YET) {
vector.add(index, newRange);
}
return;
}
}
if (merged == NOT_YET) {
vector.add(newRange);
}
}
/**
* Merge into this list all the ranges contained
* in the given vector using merge(ByteRange).
*
* @param other the Vector of ByteRange objects
**/
public void merge(final Vector other)
{
for (int i = 0; i < other.size(); i++) {
this.merge((ByteRange) other.elementAt(i));
}
}
/**
* Merge into this list all the ranges contained
* in the given ByteRangeList using merge(ByteRange).
* The parameter object remains intact.
*
* @param other the ByteRangeList to be merged into this
**/
public void merge(final ByteRangeList other)
{
merge(other.vector);
}
/**
* convert this object to a vector of ByteRanges.
* The resulting vector will preserve the features
* of ByteRangeList: (1) order and (2) separation.
* Subsequent calls of this method will return
* the same Vector object.
**/
public Vector toVector()
{
return vector;
}
/**
* convert this object to a String, in the format
* of argument of REST GridFTP command, for instance:
* "0-29,32-89"
* The resulting String will preserve the features
* of ByteRangeList: (1) order and (2) separation
**/
@Override
public String toFtpCmdArgument()
{
char comma = ',';
boolean first = true;
StringBuilder result = new StringBuilder();
for (int i = 0; i < vector.size(); i++) {
if (first) {
first = false;
} else {
result.append(comma);
}
result.append(vector.elementAt(i).toString());
}
return result.toString();
}
}