// Edited for the Learning branch package com.limegroup.gnutella.util; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Collections; import java.util.ArrayList; import com.limegroup.gnutella.Assert; import com.limegroup.gnutella.ByteOrder; import com.limegroup.gnutella.downloader.Interval; /** * An IntervalSet object keeps a list of Interval objects. * Each Interval clips out a range of data in a file. * An IntervalSet clips out several ranges, describing which stripes we have and which we need. * * An IntSet is a list of numbers like {1, 2, 50, 51, 52}. * An Interval object has low and high to clip out a range within a file. * So, IntervalSet is a list of Interval objects. * * * A "range" version of IntSet. This is a first cut of the class and does * not support all the operations IntSet does, just the ones we need for now. * <p> * Important Note: This class uses Interval from the download package. Ideally, * classes in the util package should be stand alone, but we need to have * Interval stay in downloads for reasons of backward compatibility. */ public class IntervalSet implements Serializable { /** * The sorted set of intervals this contains. */ private final List /*of Interval*/ intervals; //constructor. public IntervalSet() { intervals = new ArrayList(); } /** * Creates an interval set representing a single Interval. * * @param lowBound the lower bound of the represented Interval * @param highBound the upper bound of the represented Interval * @return an IntervalSet representing the range lowBound to highBound, inclusive. */ public static IntervalSet createSingletonSet(long lowBound, long highBound) { IntervalSet ret = new IntervalSet(); ret.add(new Interval(lowBound, highBound)); return ret; } public void add(Interval addInterval) { final int low = addInterval.low; final int high = addInterval.high; Interval lower=null; Interval higher=null; for (Iterator iter=intervals.iterator(); iter.hasNext(); ) { Interval interval=(Interval)iter.next(); if (low<=interval.low && interval.high<=high) {// <low-------high> iter.remove(); // interval continue; } if (low >= interval.low && interval.high >= high) // <low, high> return; // ....interval.... if (low<=interval.high + 1 && interval.low < low) // <low, high> lower=interval; // interval........ if (interval.low - 1 <=high && interval.high > high) // <low, high> higher=interval; // .........interval // if high < interval.low we must have found all overlaps since // intervals is sorted. if (higher != null || interval.low > high) break; } //Add block. Note that remove(..) is linear time. That's not an issue //because there are typically few blocks. if (lower==null && higher==null) { //a) Doesn't overlap addImpl(new Interval(low, high)); } else if (lower!=null && higher!=null) { //b) Join two blocks removeImpl(higher); removeImpl(lower); addImpl(new Interval(lower.low, higher.high)); } else if (higher!=null) { //c) Join with higher removeImpl(higher); addImpl(new Interval(low, higher.high)); } else /*if (lower!=null)*/ { //d) Join with lower removeImpl(lower); addImpl(new Interval(lower.low, high)); } } /** * Adds a whole IntervalSet into this IntervalSet. * @param set */ public void add(IntervalSet set) { for (Iterator iter = set.getAllIntervals(); iter.hasNext(); ) add((Interval)iter.next()); } /** * Deletes any overlap of existing intervals with the Interval to delete. * @param deleteMe the Interval that should be deleted. */ public void delete(Interval deleteMe) { int low = deleteMe.low; int high = deleteMe.high; Interval lower = null; Interval higher = null; for (Iterator iter = intervals.iterator(); iter.hasNext();) { Interval interval = (Interval) iter.next(); if (interval.high >= low && interval.low <= high) { //found iter.remove(); // overlap if (interval.high <= high) { if (interval.low < low) // interval.low < low <= interval.high <= high lower = new Interval(interval.low, low - 1); // else // low <= interval.low <= interval.high <= high // do nothing, the interval has already been removed } else if (interval.low >= low) { // low <= interval.low <= high < interval.high higher = new Interval(high + 1, interval.high); // safe to break here because intervals is sorted. break; } else { // interval.low < low <= high < interval.high lower = new Interval(interval.low, low - 1); higher = new Interval(high + 1, interval.high); // we can break here because no other intervals will // overlap with deleteMe break; } } // stop here because intervals is sorted and all following // intervals will be out of range: // low <= high < interval.low <= interval.high else if (interval.low >= high) break; } if (lower != null) add(lower); if (higher != null) add(higher); } /** * Deletes all intervals in the specified set * from this set. */ public void delete(IntervalSet set) { for (Iterator iter = set.getAllIntervals(); iter.hasNext(); ) delete((Interval)iter.next()); } /** * Returns the first element without modifying this IntervalSet. * @throws NoSuchElementException if no intervals exist. */ public Interval getFirst() throws NoSuchElementException { if(intervals.isEmpty()) throw new NoSuchElementException(); return (Interval)intervals.get(0); } /** * Returns the last element without modifying this IntervalSet. * @throws NoSuchElementException if no intervals exist. */ public Interval getLast() throws NoSuchElementException { if(intervals.isEmpty()) throw new NoSuchElementException(); Interval ret = (Interval)intervals.get(intervals.size()-1); return ret; } /** @return the number of Intervals in this IntervalSet */ public int getNumberOfIntervals() { return intervals.size(); } /** * @return whether this interval set contains fully the given interval */ public boolean contains(Interval i) { for (Iterator iter = getAllIntervals(); iter.hasNext();) { Interval ours = (Interval)iter.next(); if (ours.low <= i.low && ours.high >= i.high) return true; } return false; } /** * @return whether this interval set contains any part of the given interval */ public boolean containsAny(Interval i) { int low = i.low; int high = i.high; for (Iterator iter = getAllIntervals(); iter.hasNext(); ) { Interval interval = (Interval)iter.next(); if (low<=interval.low && interval.high<=high) // <low-------high> return true; // interval if (low >= interval.low && interval.high >= high) // <low, high> return true; // ....interval.... if (low<=interval.high + 1 && interval.low < low) // <low, high> return true; // interval........ if (interval.low - 1 <=high && interval.high > high) // <low, high> return true; // .........interval } return false; } /** *@return a List of intervals that overlap checkInterval. For example * if Intervals contains{[1-4],[6-10]} and checkInterval is [3-8], * this method should return a list of 2 intervals {[3-4],[6-8]} * If there are no overlaps, this method returns an empty List. */ public List getOverlapIntervals(Interval checkInterval) { List overlapBlocks = new ArrayList(); //initialize for this write long high =checkInterval.high; long low = checkInterval.low; if (low > high) return overlapBlocks; //TODO2:For now we iterate over each of the inervals we have, //but there should be a faster way of finding which intrevals we //can overlap, Actually there is a max of two intervals we can overlap //one on the top end and one on the bottom end. We need to make this //more efficient for(Iterator iter = intervals.iterator(); iter.hasNext(); ) { Interval interval = (Interval)iter.next(); //case a: if(low <= interval.low && interval.high <= high) { //Need to check the whole iterval, starting point=interval.low overlapBlocks.add(interval); continue; } //case b: if(low<=interval.high && interval.low < low) { overlapBlocks.add(new Interval(low, Math.min(high,interval.high))); } //case c: if(interval.low <= high && interval.high > high) { overlapBlocks.add(new Interval(Math.max(interval.low,low), high)); } //Note: There is one condition under which case b and c are both //true. In this case the same interval will be added twice. The //effect of this is that we will check the same overlap interval //2 times. We are still doing it this way, beacuse this conditon //will not happen in practice, and the code looks better this way, //and finally, it cannot do any harm - the worst that can happen is //that we check the exact same interval twice. } return overlapBlocks; } public Iterator getAllIntervals() { return intervals.iterator(); } public List getAllIntervalsAsList() { return new ArrayList(intervals); } public int getSize() { int sum=0; for (Iterator iter=intervals.iterator(); iter.hasNext(); ) { Interval block=(Interval)iter.next(); sum+=block.high-block.low+1; } return sum; } public boolean isEmpty() { return intervals.isEmpty(); } public void clear() { intervals.clear(); } /** * This method creates an IntervalSet that is the negative to this * IntervalSet * @return IntervalSet containing all ranges not contained in this */ public IntervalSet invert(int maxSize) { IntervalSet ret = new IntervalSet(); if(maxSize < 1) return ret; //return an empty IntervalSet if (intervals.size()==0) {//Nothing recorded? Interval block=new Interval(0, maxSize-1); ret.add(block); return ret; } //Now step through list one element at a time, putting gaps into buf. //We take advantage of the fact that intervals are disjoint. Treat //beginning specially. //LOOP INVARIANT: interval!=null ==> low==interval.high int low=-1; Interval interval=null; boolean fixed = false; for (Iterator iter=intervals.iterator(); iter.hasNext(); ) { interval=(Interval)iter.next(); if (interval.low!=0 && low<interval.low) {//needed for first interval if (low+1 > interval.low-1) { if(!fixed) { fixed = true; fix(); iter = intervals.iterator(); low = -1; interval = null; continue; } else { throw new IllegalArgumentException("constructing invalid interval "+ " while trying to invert \n"+toString()+ " \n with size "+maxSize+ " low:"+low+" interval.low:"+interval.low); } } ret.add(new Interval(low+1, interval.low-1)); } low=interval.high; } //Special case space between last block and end of file. Assert.that(interval!=null, "Null interval in getFreeBlocks"); if (interval.high < maxSize-1) ret.add(new Interval(interval.high+1, maxSize-1)); return ret; } /** * @return an iterator or intervals needed to fill in the holes in this * IntervalSet. Note that the IntervalSet does not know the maximum value of * all the intervals. */ public Iterator getNeededIntervals(int maxSize) { return this.invert(maxSize).getAllIntervals(); } /** * Clones the IntervalSet. The underlying intervals are the same * (so they should never be modified), but the TreeSet this is * backed off of is new. */ public Object clone() { IntervalSet ret = new IntervalSet(); for (Iterator iter = getAllIntervals(); iter.hasNext(); ) // access the internal TreeSet directly, - it's faster that way. ret.intervals.add(iter.next()); return ret; } /** * Adds into the list, in order. */ private void addImpl(Interval i) { int point = Collections.binarySearch(intervals, i, IntervalComparator.INSTANCE); if(point >= 0) throw new IllegalStateException("interval (" + i + ") already in list: " + intervals); point = -(point + 1); intervals.add(point, i); } /** * Removes from the list, quickly. */ private void removeImpl(Interval i) { int point = Collections.binarySearch(intervals, i, IntervalComparator.INSTANCE); if(point < 0) throw new IllegalStateException("interval (" + i + ") doesn't exist in list: " + intervals); intervals.remove(point); } /** * Comparator for intervals. */ private static class IntervalComparator implements Comparator { private static final IntervalComparator INSTANCE = new IntervalComparator(); public int compare(Object a, Object b) { Interval ia=(Interval)a; Interval ib=(Interval)b; if ( ia.low > ib.low ) return 1; else if (ia.low < ib.low ) return -1; else return 0; // return ia.low-ib.low; } } /** * Lists the contained intervals. */ public String toString() { return intervals.toString(); } /** * * @return packed representation of the intervals. */ public byte [] toBytes() { byte [] ret = new byte[intervals.size()*8]; int pos = 0; for (Iterator iter = intervals.iterator();iter.hasNext();) { Interval current = (Interval) iter.next(); current.toBytes(ret,pos); pos+=8; } return ret; } /** * parses an IntervalSet from a byte array. */ public static IntervalSet parseBytes(byte [] data) throws IOException { if (data.length % 8 != 0) throw new IOException(); IntervalSet ret = new IntervalSet(); for (int i =0; i< data.length/8;i++) { int low = (int)ByteOrder.uint2long(ByteOrder.beb2int(data,i*8)); int high = (int)ByteOrder.uint2long(ByteOrder.beb2int(data,i*8+4)); if (high < low || high < 0 || low < 0) throw new IOException(); ret.add(new Interval(low,high)); } return ret; } /** * Recomposes intervals to ensure that invariants are met. */ private void fix() { String preIntervals = intervals.toString(); List oldIntervals = new ArrayList(intervals); intervals.clear(); for(Iterator i = oldIntervals.iterator(); i.hasNext(); ) add((Interval)i.next()); String postIntervals = intervals.toString(); Assert.silent(false, "IntervalSet invariants broken.\n" + "Pre Fixing: " + preIntervals + "\n" + "Post Fixing: " + postIntervals); } }