edu.umd.cfar.lamp.viper.util.Range.java Source code

Java tutorial

Introduction

Here is the source code for edu.umd.cfar.lamp.viper.util.Range.java

Source

/***************************************
 *            ViPER                    *
 *  The Video Processing               *
 *         Evaluation Resource         *
 *                                     *
 *  Distributed under the GPL license  *
 *        Terms available at gnu.org.  *
 *                                     *
 *  Copyright University of Maryland,  *
 *                      College Park.  *
 ***************************************/

package edu.umd.cfar.lamp.viper.util;

import java.util.*;

import org.apache.commons.collections.*;

/**
 * This class represents a set of Intervals. Note that 
 * all indexes must all be Comparable.
 */
public class Range implements Cloneable, Set, IntervalIndexList {
    /**
     * This is stored as a java.util.TreeMap (red/black tree, I think), 
     * with the keys being the first frame, and the values containing
     * the Span. The code is careful to make sure that no two Spans
     * within the spans Map are contiguous.
     */
    private SortedMap spans;

    private Object positiveValue = Boolean.TRUE;
    private Object negativeValue = null;

    /**
     * Creates a new, empty Range.
     */
    public Range() {
        spans = new TreeMap();
    }

    /**
     * Creates a new, empty Range.
     * @param c The comparison to use while adding items to the range.
     */
    public Range(Comparator c) {
        spans = new TreeMap(c);
    }

    /**
     * Creates a new Range from a Collection of Span objects.
     * @param c the intervals to add
     */
    public Range(Collection c) {
        spans = new TreeMap();
        for (Iterator iter = c.iterator(); iter.hasNext();) {
            this.add((Interval) iter.next());
        }
    }

    /**
     * Gets the number of disconnected ranges of 
     * Instants this Range contains. Note this is 
     * not the number of total Instants included 
     * in the Range.
     * @return number of discrete sections of time
     *    covered by this Range.
     */
    public int size() {
        return spans.size();
    }

    /**
     * Checks that this is 
     * 
     * @param s
     * @return
     */
    public boolean withinRange(Interval s) {
        return withinRange(s.getStart(), s.getEnd());
    }

    /**
     * Checks to see if the interval defined as [s,e)
     * is entirely contained within this Range object.
     * 
     * @param s
     * @param e
     * @return boolean
     */
    public boolean withinRange(Comparable s, Comparable e) {
        SortedMap m = spans.headMap(e);
        if (!m.isEmpty()) {
            // thankfully Range keeps contiguous spans merged,
            // so this is easy
            Comparable start = (Comparable) m.lastKey();
            Comparable end = (Comparable) m.get(start);
            if (end.compareTo(s) >= 0) {
                return start.compareTo(s) <= 0 && end.compareTo(e) >= 0;
            }
        }
        m = spans.tailMap(s);
        if (!m.isEmpty()) {
            Comparable sPrime = (Comparable) m.firstKey();
            if (sPrime.compareTo(s) == 0) {
                return e.compareTo((Comparable) m.get(sPrime)) <= 0;
            }
        }
        return false;
    }

    /**
     * Returns null if the moment is in the span, otherwise returns 
     * Boolean.TRUE. This is pretty hackish. 
     * @param index the index to see if is in range
     * @return <code>null</code> or <code>Boolean.TRUE</code>
     */
    public Object get(Comparable index) {
        if (this.contains(index)) {
            return positiveValue;
        } else {
            return negativeValue;
        }
    }

    /**
     * Since this is just a range, this ignores the value object except
     * to check if it is "null". Setting to Null, or Boolean.FALSE, is the
     * same as removing an Interval.
     * @param start the start point, inclusive
     * @param stop the end point, exclusive
     * @param value the value to set over the given range
     */
    public void set(Comparable start, Comparable stop, Object value) {
        if (value == null || ((value instanceof Boolean) && value.equals(Boolean.FALSE))) {
            this.remove(start, stop);
        } else {
            this.add(start, stop);
        }
    }

    /**
     * Subsumes the Instants in the Span into this Range. 
     * @param start the first instant to add
     * @param stop the stop instant, exclusive
     * @return <code>true</code> iff the operation modified this Range
     */
    public boolean add(Comparable start, Comparable stop) {
        Comparable old = (Comparable) spans.get(start);
        if (old != null && old.compareTo(stop) >= 0) {
            return false;
        }
        SortedMap head = spans.headMap(start);
        if (!head.isEmpty()) {
            Comparable oldStart = (Comparable) head.lastKey();
            Comparable oldEnd = (Comparable) head.get(oldStart);
            if (oldEnd.compareTo(stop) >= 0) {
                return false;
            } else {
                if (oldEnd.compareTo(start) >= 0) {
                    start = oldStart;
                    spans.remove(oldStart);
                }
            }
        }
        SortedMap sub = spans.subMap(start, stop);
        if (!sub.isEmpty()) {
            Comparable oldStart = (Comparable) sub.lastKey();
            Comparable oldEnd = (Comparable) sub.get(oldStart);
            if (oldStart.compareTo(start) == 0 && oldEnd.compareTo(stop) >= 0) {
                return false;
            } else if (oldEnd.compareTo(stop) > 0) {
                stop = oldEnd;
            }
            sub.clear();
        }
        if (spans.containsKey(stop)) {
            stop = (Comparable) spans.remove(stop);
        }
        spans.put(start, stop);
        return true;
    }

    /**
     * Gets the Span that starts at the beginning
     * of the first Span and ends at the end of the 
     * last Span.
     * @return Span a Span that has all the others
     *    as a subset. Null if empty.
     */
    public Interval getExtrema() {
        if (spans.isEmpty()) {
            return null;
        } else if (spans.size() == 1) {
            Comparable start = (Comparable) spans.firstKey();
            Comparable end = (Comparable) spans.get(start);
            return createInterval(start, end);
        } else {
            Comparable start = (Comparable) spans.firstKey();
            Comparable end = (Comparable) spans.lastKey();
            end = (Comparable) spans.get(end);
            return createInterval(start, end);
        }
    }

    /**
     * @see edu.umd.cfar.lamp.viper.util.IntervalIndexList#remove(java.lang.Comparable, java.lang.Comparable)
     */
    public boolean remove(Comparable start, Comparable end) {
        boolean someFound = false;
        SortedMap head = spans.headMap(start);
        if (!head.isEmpty()) {
            Comparable oldStart = (Comparable) head.lastKey();
            Comparable oldEnd = (Comparable) head.get(oldStart);
            if (oldEnd.compareTo(start) > 0) {
                // if there is a span that goes into the span to
                // be removed, replace it.
                head.put(oldStart, start);
                someFound = true;
                double toCheck = oldEnd.compareTo(end);
                if (toCheck > 0) {
                    // if the span to be removed is a strict subset 
                    // of some existing span, you also have
                    // to add back the end.
                    spans.put(end, oldEnd);
                    return true;
                } else if (toCheck == 0) {
                    return true;
                }
            }
        }
        SortedMap sub = spans.subMap(start, end);
        if (!sub.isEmpty()) {
            someFound = true;
            Comparable oldStart = (Comparable) sub.lastKey();
            Comparable oldEnd = (Comparable) sub.get(oldStart);
            if (oldEnd.compareTo(end) > 0) {
                // if there is a span that starts during the
                // span to removed that goes past the end,
                // have to add back the difference.
                spans.put(end, oldEnd);
            }
            sub.clear();
        }
        return someFound;
    }

    /**
     * Adds all of the Span objects in the Collection s 
     * to this one. It won't remove any of the current
     * Instants.
     * @param s a set of homogenous type Span objects
     * @return <code>true</code> if any new Instants are now
     *    part of this set.
     */
    public boolean setSpans(Collection s) {
        boolean changed = false;
        for (Iterator iter = s.iterator(); iter.hasNext();) {
            changed = changed || add(iter.next());
        }
        return changed;
    }

    protected void cloneInto(Range r) {
        r.clear();
        r.spans.putAll(spans);
    }

    /**
     * @see java.lang.Object#clone()
     */
    public Object clone() {
        Range r = new Range();
        cloneInto(r);
        return r;
    }

    /**
     * @see java.util.Set#add(java.lang.Object)
     */
    public boolean add(Object o) {
        if (o instanceof Interval) {
            Interval t = (Interval) o;
            return add(t.getStart(), t.getEnd());
        } else if (o instanceof Incrementable) {
            Incrementable i = (Incrementable) o;
            return add(i, i.next());
        } else {
            throw new IllegalArgumentException("Cannot add " + o);
        }
    }

    /**
     * Adds all the elements described by the iterator.
     * @param iter the elements to add
     * @return  if <code>this</code> changed
     */
    public boolean addAll(Iterator iter) {
        boolean changed = false;
        while (iter.hasNext()) {
            changed = add(iter.next()) || changed;
        }
        return changed;
    }

    /**
     * @see java.util.Set#addAll(java.util.Collection)
     */
    public boolean addAll(Collection c) {
        if (this.isEmpty() && (c instanceof Range)) {
            if (!c.isEmpty()) {
                spans.putAll(((Range) c).spans);
                return true;
            } else {
                return false;
            }
        } else if (c instanceof Interval) {
            return add(c);
        } else {
            return addAll(c.iterator());
        }
    }

    /**
     * @see java.util.Set#clear()
     */
    public void clear() {
        spans.clear();
    }

    /**
     * @see java.util.Set#contains(java.lang.Object)
     */
    public boolean contains(Object o) {
        if (spans.size() == 0) {
            return false;
        } else if (o instanceof Comparable) {
            SortedMap m = spans.headMap(o);
            if (m.size() > 0) {
                Comparable e = (Comparable) m.get(m.lastKey());
                if (e.compareTo(o) > 0) {
                    return true;
                }
            }
            m = spans.tailMap(o);
            if (m.size() > 0) {
                Comparable s = (Comparable) m.firstKey();
                return s.compareTo(o) == 0;
            }
            return false;
        } else {
            return withinRange((Interval) o);
        }
    }

    /**
     * @see java.util.Set#containsAll(java.util.Collection)
     */
    public boolean containsAll(Collection c) {
        boolean foundAll = true;
        for (Iterator iter = c.iterator(); iter.hasNext();) {
            foundAll = contains(iter.next()) && foundAll;
        }
        return foundAll;
    }

    /**
     * @see java.util.Set#isEmpty()
     */
    public boolean isEmpty() {
        return spans.isEmpty();
    }

    class IntervalIterator implements Iterator {
        private Iterator internal;

        IntervalIterator() {
            internal = spans.entrySet().iterator();
        }

        /**
         * @see java.util.Iterator#hasNext()
         */
        public boolean hasNext() {
            return internal.hasNext();
        }

        /**
         * @see java.util.Iterator#next()
         */
        public Object next() {
            Map.Entry c = (Map.Entry) internal.next();
            Comparable start = (Comparable) c.getKey();
            Comparable end = (Comparable) c.getValue();
            return createInterval(start, end);
        }

        /**
         * @see java.util.Iterator#remove()
         */
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    /** @inheritDoc */
    public Iterator iterator() {
        return new IntervalIterator();
    }

    /** @inheritDoc */
    public Iterator iterator(Interval i) {
        return new CropIteratorUtility(i).getIterator(iterator());
    }

    /**
     * @see java.util.Set#remove(java.lang.Object)
     */
    public boolean remove(Object o) {
        if (o instanceof Interval) {
            Interval toRemove = (Interval) o;
            return remove(toRemove.getStart(), toRemove.getEnd());
        } else if (o instanceof Incrementable) {
            Incrementable toRemove = (Incrementable) o;
            return remove(toRemove, toRemove.next());
        } else {
            throw new IllegalArgumentException("Cannot remove: " + o);
        }
    }

    /**
     * @see java.util.Set#removeAll(java.util.Collection)
     */
    public boolean removeAll(Collection c) {
        boolean changed = false;
        for (Iterator iter = c.iterator(); iter.hasNext();) {
            changed = changed || remove(iter.next());
        }
        return changed;
    }

    /**
     * TODO: currently unsupported
     * @see java.util.Set#retainAll(java.util.Collection)
     */
    public boolean retainAll(Collection c) {
        // This is tough. Need a state machine of some sort.
        throw new UnsupportedOperationException();
    }

    /**
     * @see java.util.Set#toArray()
     */
    public Object[] toArray() {
        return toArray(new Interval[spans.size()]);
    }

    /**
     * @see java.util.Set#toArray(java.lang.Object[])
     */
    public Object[] toArray(Object[] A) {
        if (A.length < spans.size()) {
            A = new Interval[spans.size()];
        }
        int i = 0;
        for (Iterator iter = iterator(); iter.hasNext();) {
            A[i++] = iter.next();
        }
        return A;
    }

    /**
     * Returns the list as a space delimited of [s e) pairs.
     * @see java.lang.Object#toString()
     */
    public String toString() {
        StringBuffer sb = new StringBuffer();
        for (Iterator iter = spans.entrySet().iterator(); iter.hasNext();) {
            Map.Entry curr = (Map.Entry) iter.next();
            sb.append('[').append((curr.getKey())).append(',').append(curr.getValue()).append(')');
            if (iter.hasNext()) {
                sb.append(" ");
            }
        }
        return sb.toString();
    }

    /**
     * @see java.util.Set#equals(java.lang.Object)
     */
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        } else if (o instanceof Range) {
            SortedMap oSpans = ((Range) o).spans;
            if (oSpans.size() != spans.size()) {
                return false;
            } else {
                Iterator myIter = spans.entrySet().iterator();
                Iterator oIter = oSpans.entrySet().iterator();
                while (myIter.hasNext()) {
                    if (!myIter.next().equals(oIter.next())) {
                        return false;
                    }
                }
                return true;
            }
        } else {
            return false;
        }
    }

    /**
     * @see java.util.Set#hashCode()
     */
    public int hashCode() {
        return spans.hashCode();
    }

    /**
     * Tests to see if this range contains any element
     * or intersects any element of the specified set.
     * @param s the set to test against
     * @return if there is an intersection/shared item
     */
    public boolean intersects(Set s) {
        if (s instanceof Interval) {
            return intersects((Interval) s);
        } else if (s instanceof Range) {
            return intersects((Range) s);
        } else {
            for (Iterator iter = s.iterator(); iter.hasNext();) {
                Object curr = iter.next();
                if (curr instanceof Comparable) {
                    Comparable i = (Comparable) curr;
                    if (this.withinRange(i, i)) {
                        return true;
                    }
                } else if (this.intersects((Interval) curr)) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * Tests to see if the this intersects the other range.
     * @param r the range to test against
     * @return <code>true</code> if there is some shared interval
     */
    public boolean intersects(Range r) {
        if (!this.getExtrema().intersects(r.getExtrema())) {
            return false;
        } else {
            return this.intersect(r).size() > 0;
        }
    }

    /**
     * Checks to see if some interval of this
     * intersects the specified interval.
     * @param s the interval to test against
     * @return <code>true</code> if there is some overlap
     */
    public boolean intersects(Interval s) {
        if (this.isEmpty() || s.isEmpty()) {
            return false;
        }
        SortedMap m = spans.subMap(s.getStart(), s.getEnd());
        if (!m.isEmpty()) {
            return true;
        } else {
            m = spans.headMap(s.getStart());
            Interval last = (Interval) m.get(m.lastKey());
            return last.intersects(s);
        }
    }

    /**
     * Gets the range shared by this and the specified 
     * range.
     * @param list the list to intersect with
     * @return the shared range
     */
    public Range intersect(IntervalIndexList list) {
        // Note that each span in a range must
        // have a gap before it. this means it is 
        // ease enough to just follow the heuristic:
        // get span to add, advance to latest pointer,
        // get span to add.
        Range newRange = new Range();
        if (this.isEmpty() || list.isEmpty()) {
            return newRange;
        }
        SortedMap A = this.spans;
        Comparable aNextStart = (Comparable) A.firstKey();
        Comparable aNextEnd = (Comparable) A.get(aNextStart);
        Iterator B = list.iterator();
        if (!B.hasNext()) {
            return newRange;
        }
        Interval b = (Interval) B.next();
        if (b.isEmpty()) {
            throw new AssertionError();
        }
        Comparable bNextStart = b.getStart();
        Comparable bNextEnd = b.getEnd();

        while (aNextStart != null && bNextStart != null) {
            double diffStartStart = aNextStart.compareTo(bNextStart);
            double diffEndEnd = aNextEnd.compareTo(bNextEnd);
            if (diffStartStart == 0) {
                // both start at the same time
                if (diffEndEnd == 0) {
                    newRange.add(aNextStart, aNextEnd);
                    aNextStart = this.firstAfterOrAt(aNextEnd);
                    aNextEnd = this.endOf(aNextStart);
                    bNextStart = list.firstAfterOrAt(bNextEnd);
                    bNextEnd = list.endOf(bNextStart);
                } else if (diffEndEnd < 0) {
                    // a stops before b
                    newRange.add(aNextStart, aNextEnd);
                    aNextStart = this.firstAfterOrAt(aNextEnd);
                    aNextEnd = this.endOf(aNextStart);
                } else {
                    // b stops before a
                    newRange.add(bNextStart, bNextEnd);
                    bNextStart = list.firstAfterOrAt(bNextEnd);
                    bNextEnd = list.endOf(bNextStart);
                }
            } else if (diffStartStart < 0) {
                // a starts before b
                double diffEndStart = aNextEnd.compareTo(bNextStart);
                if (diffEndStart <= 0) {
                    // skip ahead, since there is no
                    // chance of overlap here
                    aNextStart = this.firstAfterOrAt(aNextEnd);
                    aNextEnd = this.endOf(aNextStart);
                } else if (diffEndEnd == 0) {
                    newRange.add(bNextStart, bNextEnd);
                    aNextStart = this.firstAfterOrAt(aNextEnd);
                    aNextEnd = this.endOf(aNextStart);
                    bNextStart = list.firstAfterOrAt(bNextEnd);
                    bNextEnd = list.endOf(bNextStart);
                } else if (diffEndEnd < 0) {
                    // a ends before b does, but after b starts
                    newRange.add(bNextStart, aNextEnd);
                    aNextStart = this.firstAfterOrAt(aNextEnd);
                    aNextEnd = this.endOf(aNextStart);
                } else {
                    // a ends after b does
                    newRange.add(bNextStart, bNextEnd);
                    bNextStart = list.firstAfterOrAt(bNextEnd);
                    bNextEnd = list.endOf(bNextStart);
                }
            } else {
                // a starts after b does
                double diffStartEnd = aNextStart.compareTo(bNextEnd);
                if (diffStartEnd >= 0) {
                    // skip ahead, since there is no
                    // chance of overlap here
                    bNextStart = list.firstAfterOrAt(bNextEnd);
                    bNextEnd = list.endOf(bNextStart);
                } else if (diffEndEnd == 0) {
                    // both end at the same moment
                    newRange.add(aNextStart, aNextEnd);
                    aNextStart = this.firstAfterOrAt(aNextEnd);
                    aNextEnd = this.endOf(aNextStart);
                    bNextStart = list.firstAfterOrAt(bNextEnd);
                    bNextEnd = list.endOf(bNextStart);
                } else if (diffEndEnd < 0) {
                    // a is a subset of b
                    newRange.add(aNextStart, aNextEnd);
                    aNextStart = this.firstAfterOrAt(aNextEnd);
                    aNextEnd = this.endOf(aNextStart);
                } else {
                    // a ends after b does
                    newRange.add(aNextStart, bNextEnd);
                    bNextStart = list.firstAfterOrAt(bNextEnd);
                    bNextEnd = list.endOf(bNextStart);
                }
            }
            if ((aNextStart != null && bNextStart != null) && (aNextEnd == null || bNextEnd == null)) {
                throw new AssertionError();
            }
        }
        return newRange;
    }

    /**
     * @see edu.umd.cfar.lamp.viper.util.IntervalIndexList#firstBefore(java.lang.Comparable)
     */
    public Comparable firstBefore(Comparable oldStart) {
        SortedMap head = spans.headMap(oldStart);
        if (head.size() > 0) {
            return (Comparable) head.lastKey();
        }
        return null;
    }

    public Comparable firstBeforeOrAt(Comparable oldStart) {
        if (spans.containsKey(oldStart)) {
            return oldStart;
        }
        SortedMap head = spans.headMap(oldStart);
        if (head.size() > 0) {
            return (Comparable) head.lastKey();
        }
        return null;
    }

    /**
     * @see edu.umd.cfar.lamp.viper.util.IntervalIndexList#firstAfter(java.lang.Comparable)
     */
    public Comparable firstAfter(Comparable oldStart) {
        Collection tail = spans.tailMap(oldStart).keySet();
        if (!tail.isEmpty()) {
            Iterator iter = tail.iterator();
            if (spans.containsKey(oldStart)) {
                iter.next();
                if (!iter.hasNext()) {
                    return null;
                }
            }
            return (Comparable) iter.next();
        }
        return null;
    }

    /**
     * @see edu.umd.cfar.lamp.viper.util.IntervalIndexList#firstAfterOrAt(java.lang.Comparable)
     */
    public Comparable firstAfterOrAt(Comparable oldStart) {
        if (spans.containsKey(oldStart)) {
            return oldStart;
        } else {
            return firstAfter(oldStart);
        }
    }

    /**
     * @see edu.umd.cfar.lamp.viper.util.IntervalIndexList#endOf(java.lang.Comparable)
     */
    public Comparable endOf(Comparable c) {
        if (c == null) {
            return null;
        }
        Object o = spans.get(c);
        if (o == null) {
            SortedMap head = spans.headMap(c);
            if (head != null && !head.isEmpty()) {
                Comparable last = (Comparable) spans.get(head.lastKey());
                if (last.compareTo(c) >= 0) {
                    return last;
                }
            }
            return null;
        } else {
            return (Comparable) o;
        }
    }

    /**
     * Gets the union of this and the specified range.
     * @param o the range to union with 
     * @return a new range containing all items in either this
     * or the specified range
     */
    public Range union(Range o) {
        Range u = new Range();
        u.addAll(this.iterator());
        u.addAll(o.iterator());
        return u;
    }

    /**
     * Gets all elements from this that are not elements
     * of the specified range.
     * @param o the range to complement
     * @return this - that
     */
    public Range intersectComplement(Range o) {
        Range r = new Range();
        r.addAll(this.iterator());
        r.removeAll(o);
        return r;
    }

    /**
     * @see edu.umd.cfar.lamp.viper.util.IntervalIndexList#addAll(edu.umd.cfar.lamp.viper.util.IntervalIndexList)
     */
    public boolean addAll(IntervalIndexList l) {
        return addAll(l.iterator());
    }

    /**
     * @see edu.umd.cfar.lamp.viper.util.IntervalIndexList#subList(java.lang.Comparable, java.lang.Comparable)
     */
    public IntervalIndexList subList(Comparable start, Comparable stop) {
        return intersect(Intervals.singleton(new SimpleInterval(start, stop)));
    }

    /**
     * @see edu.umd.cfar.lamp.viper.util.IntervalIndexList#map(edu.umd.cfar.lamp.viper.util.IntervalIndexList.Change)
     */
    public void map(Transformer c) {
        throw new UnsupportedOperationException();
    }

    /**
     * Creates a new interval. This is used instead of a constructor, so
     * subclasses of this range which wish to return subclasses of interval 
     * should override this method.
     * @param s
     * @param e
     * @return
     */
    public Interval createInterval(Comparable s, Comparable e) {
        return new SimpleInterval(s, e);
    }

    /**
     * @see edu.umd.cfar.lamp.viper.util.IntervalIndexList#getContiguousIntervalCount()
     */
    public int getContiguousIntervalCount() {
        return spans.size();
    }

    public Object getPositiveValue() {
        return positiveValue;
    }

    public void setPositiveValue(Object positiveValue) {
        this.positiveValue = positiveValue;
    }
}