package freenet.support; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.NavigableSet; import java.util.TreeSet; public class SparseBitmap implements Iterable<int[]> { // Ranges ordered by start time. Invariant: ranges do not overlap and do not touch. private final TreeSet<Range> ranges; public SparseBitmap() { ranges = new TreeSet<Range>(new RangeComparator()); } public SparseBitmap(SparseBitmap original) { ranges = new TreeSet<Range>(new RangeComparator()); for(int[] range : original) { add(range[0], range[1]); } } /** * Marks the slots between start and end (inclusive) as present. */ public void add(int start, int end) { if(start > end) { throw new IllegalArgumentException("Tried adding bad range. Start: " + start + ", end: " + end); } NavigableSet<Range> toReplace = overlaps(start, end, true); if (!toReplace.isEmpty()) { Range first = toReplace.first(); if (first.start < start) { start = first.start; } Range last = toReplace.last(); if (last.end > end) { end = last.end; } toReplace.clear(); } ranges.add(new Range(start, end)); } public void clear() { ranges.clear(); } /** * Checks whether all slots between start and end (inclusive) are present. */ public boolean contains(int start, int end) { if(start > end) { throw new IllegalArgumentException("Tried checking bad range. Start: " + start + ", end: " + end); } // Find the latest range starting before (or at) start, if any exists. Range floor = ranges.floor(new Range(start, end)); // By definition of floor(), lower.start <= start. return floor != null && floor.end >= end; } /** * Marks all slots between start and end (inclusive) as not present. */ public void remove(int start, int end) { if(start > end) { throw new IllegalArgumentException("Removing bad range. Start: " + start + ", end: " + end); } List<Range> toAdd = new ArrayList<Range>(); Iterator<Range> it = overlaps(start, end, false).iterator(); while (it.hasNext()) { Range range = it.next(); if (range.start < start) { if (range.end <= end) { //Overlaps beginning toAdd.add(new Range(range.start, start - 1)); } else if (range.end > end) { //Overlaps entire range toAdd.add(new Range(range.start, start - 1)); toAdd.add(new Range(end + 1, range.end)); } } else { if (range.end > end) { // Overlaps end toAdd.add(new Range(end + 1, range.end)); } //Else it is equal or inside } it.remove(); } ranges.addAll(toAdd); } @Override public Iterator<int[]> iterator() { return new SparseBitmapIterator(this); } public boolean isEmpty() { return ranges.isEmpty(); } @Override public String toString() { StringBuffer s = new StringBuffer(); for(int[] range : this) { if(s.length() != 0) s.append(", "); s.append(range[0] + "->" + range[1]); } return s.toString(); } /** Finds all ranges that overlap or touch the given range. */ private NavigableSet<Range> overlaps(int start, int end, boolean includeTouching) { // Establish bounds on start times to select ranges that would overlap or touch Range startRange = new Range(start, 0); Range lower = ranges.lower(startRange); if (lower != null && lower.end >= (includeTouching ? start - 1 : start)) { // This range would overlap (or touch, if those are to be included) startRange = new Range(lower.start, 0); } Range endRange = new Range(end + 1, 0); // Any range with start time within startRange and endRange would touch or overlap return ranges.subSet(startRange, true, endRange, includeTouching); } private static class SparseBitmapIterator implements Iterator<int[]> { Iterator<Range> it; public SparseBitmapIterator(SparseBitmap map) { it = map.ranges.iterator(); } @Override public boolean hasNext() { return it.hasNext(); } @Override public int[] next() { Range r = it.next(); return new int[] {r.start, r.end}; } @Override public void remove() { it.remove(); } } private static class Range { final int start; // inclusive final int end; // inclusive public Range(int start, int end) { this.start = start; this.end = end; } @Override public String toString() { return "Range:"+start+"->"+end; } } private static class RangeComparator implements Comparator<Range> { @Override public int compare(Range r1, Range r2) { return r1.start - r2.start; } } /** @return The number of slots between start and end that are not marked as present */ public int notOverlapping(int start, int end) { int count = end - start + 1; for (Range range : overlaps(start, end, false)) { if (range.end < start || range.start > end) { throw new IllegalStateException(); } int overlap = range.end - range.start + 1; if (range.start < start) { overlap -= start - range.start; } if (range.end > end) { overlap -= range.end - end; } count -= overlap; } return count; } }