/* * 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(); } }