/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.fusesource.hawtdb.internal.util; import java.io.*; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Map.Entry; import org.fusesource.hawtbuf.AbstractVarIntSupport; import org.fusesource.hawtdb.util.TreeMap; import org.fusesource.hawtdb.util.TreeMap.TreeEntry; /** * Tracks numeric ranges. Handy for keeping track of things like allocation or free lists. * * @author <a href="http://hiramchirino.com">Hiram Chirino</a> */ final public class Ranges implements Externalizable, Iterable<Ranges.Range> { private static final long serialVersionUID = 8340484139329633582L; final public static class Range implements Serializable { private static final long serialVersionUID = -4904483630105365841L; public volatile int start; public volatile int end; public Range(int start, int end) { this.start = start; this.end = end; } public int size() { return end - start; } @Override public String toString() { if( start == end-1 ) { return Integer.toString(start); } return start+"-"+(end-1); } @Override public boolean equals(Object obj) { if( obj == this ) { return true; } if( obj == null || obj.getClass()!=Range.class ) { return false; } Range r = (Range)obj; return start == r.start && end==r.end; } @Override public int hashCode() { return start*77+end; } public boolean contains(int value) { return start <= value && value < end; } } private final TreeMap<Integer, Range> ranges = new TreeMap<Integer, Range>(); public Ranges copy() { Ranges rc = new Ranges(); for (Range r : this) { rc.ranges.put(r.start, range(r.start, r.end)); } return rc; } public void add(int start) { add(start, 1); } public void add(int start, int length) { int end = start+length; // look for entries starting from the end of the add range. TreeEntry<Integer, Range> entry = ranges.floorEntry(end); if( entry!=null ) { while( entry!=null) { Range range = entry.getValue(); TreeEntry<Integer, Range> curr = entry; entry = entry.previous(); // If tail of the range is not in the add range. if( range.end < start ) { // we are done.. break; } // if the end of the range is not in the add range. if( end < range.end ) { // extend the length out.. end = range.end; } if( start < range.start ) { // if the front of the range is in the add range. // just remove it.. ranges.removeEntry(curr); } else { // The front is not in the add range... // Then resize.. and we are done range.end = end; return; } } } // put the new range in. ranges.put(start, range(start, end)); } public void remove(int start) { remove(start, 1); } public void remove(int start, int length) { int end = start+length; // look for entries starting from the end of the remove range. TreeEntry<Integer, Range> entry = ranges.lowerEntry(end); while( entry!=null) { Range range = entry.getValue(); TreeEntry<Integer, Range> curr = entry; entry = entry.previous(); // If tail of the range is not in the remove range. if( range.end <= start ) { // we are done.. break; } // if the end if the range is not in the remove range. if( end < range.end ) { // Then we need to add back the tail part. ranges.put(end, range(end, range.end)); } if( start <= range.start ) { // if the front of the range is in the remove range. // just remove it.. ranges.removeEntry(curr); } else { // The front is not in the remove range... // Then resize.. and we are done range.end = start; break; } } } public boolean contains(int value) { TreeEntry<Integer, Range> entry = ranges.floorEntry(value); if( entry == null ) { return false; } return entry.getValue().contains(value); } public void clear() { ranges.clear(); } public void copy(Ranges source) { ranges.clear(); for (Entry<Integer, Range> entry : source.ranges.entrySet()) { Range value = entry.getValue(); ranges.put(entry.getKey(), range(value.start, value.end)); } } public int size() { int rc=0; TreeEntry<Integer, Range> entry = ranges.firstEntry(); while(entry!=null) { rc += entry.getValue().size(); entry = entry.next(); } return rc; } static public Range range(int start, int end) { return new Range(start, end); } public ArrayList<Range> toArrayList() { return new ArrayList<Range>(ranges.values()); } @Override public String toString() { StringBuilder sb = new StringBuilder(20+(10*ranges.size())); sb.append("[ "); boolean first=true; for (Range r : this) { if( !first ) { sb.append(", "); } first=false; sb.append(r); } sb.append(" ]"); return sb.toString(); } public Iterator<Range> iterator() { return ranges.values().iterator(); } public Iterator<Range> iteratorNotInRange(final Range mask) { return new Iterator<Range>() { Iterator<Range> iter = ranges.values().iterator(); Range last = new Range(mask.start, mask.start); Range next = null; public boolean hasNext() { if( next==null ) { while( last.end < mask.end && iter.hasNext() ) { Range r = iter.next(); // skip over the initial ranges not within the mask if( r.end < last.end ) { continue; } // Handle the case where a range straddles the mask start position if( r.start < last.end ) { // extend the last range out so that the next one starts at // the end of the range. last = new Range(last.start, r.end); continue; } if( r.start < mask.end ) { next = new Range(last.end, r.start); } else { next = new Range(last.end, mask.end); } break; } } return next!=null; } public Range next() { if( !hasNext() ) { throw new NoSuchElementException(); } last = next; next=null; return last; } public void remove() { throw new UnsupportedOperationException(); } }; } static private final class ValueIterator implements Iterator<Integer>, Iterable<Integer> { Iterator<Range> ranges; Range range; Integer next; int last; private ValueIterator(Iterator<Range> t) { ranges = t; } public boolean hasNext() { if( next==null ) { if( range == null ) { if( ranges.hasNext() ) { range = ranges.next(); next = range.start; } else { return false; } } else { next = last+1; } if( next == (range.end-1) ) { range=null; } } return next!=null; } public Integer next() { if( !hasNext() ) { throw new NoSuchElementException(); } last = next; next=null; return last; } public void remove() { throw new UnsupportedOperationException(); } public Iterator<Integer> iterator() { return this; } } public List<Integer> values() { ArrayList<Integer> rc = new ArrayList<Integer>(); for (Integer i : new ValueIterator(iterator())) { rc.add(i); } return rc; } public Iterator<Integer> valueIterator() { return new ValueIterator(iterator()); } public Iterator<Integer> valuesIteratorNotInRange(Range r) { return new ValueIterator(iteratorNotInRange(r)); } public boolean isEmpty() { return ranges.isEmpty(); } public void writeExternal(ObjectOutput out) throws IOException { writeExternal((DataOutput)out); } public void readExternal(ObjectInput in) throws IOException { readExternal((DataInput)in); } public void writeExternal(final DataOutput out) throws IOException { ArrayList<Range> values = new ArrayList<Range>(ranges.values()); out.writeInt(values.size()); AbstractVarIntSupport helper = new AbstractVarIntSupport() { @Override protected byte readByte() throws IOException { throw new UnsupportedOperationException(); } @Override protected void writeByte(int value) throws IOException { out.writeByte(value); } }; // We should get good compression since ranges should be // close to each other and we are just recording a var int // of the difference between the points. int base = 0; for( Range range: values) { helper.writeVarInt(range.start-base); base = range.start; helper.writeVarInt(range.end-base); base = range.end; } } public void readExternal(final DataInput in) throws IOException { ranges.clear(); int size = in.readInt(); AbstractVarIntSupport helper = new AbstractVarIntSupport() { @Override protected byte readByte() throws IOException { return in.readByte(); } @Override protected void writeByte(int value) throws IOException { throw new UnsupportedOperationException(); } }; int base = 0; for(int i=0; i < size; i++) { base += helper.readVarInt(); int start = base; base += helper.readVarInt(); int end = base; ranges.put(start, range(start, end)); } } }