/**
* 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.waveprotocol.wave.model.document.util;
import org.waveprotocol.wave.model.document.AnnotationInterval;
import org.waveprotocol.wave.model.document.ReadableAnnotationSet;
import org.waveprotocol.wave.model.util.CollectionUtils;
import org.waveprotocol.wave.model.util.Preconditions;
import org.waveprotocol.wave.model.util.ReadableStringSet;
import org.waveprotocol.wave.model.util.ReadableStringSet.Proc;
import org.waveprotocol.wave.model.util.StringMap;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.PriorityQueue;
import java.util.Queue;
/**
* An iterable over AnnotationIntervals for a ReadableAnnotationSet
* restricted to a given set of keys and a given range.
*
* The iterator will iterate over all intervals that
* intersect the specified range; the start of the first interval and the end
* of the last interval returned may lie outside of the range being iterated
* over.
*
* @author ohler@google.com (Christian Ohler)
*
* @param <V> the type of values in the annotation set
*/
public class GenericAnnotationIntervalIterable<V> implements Iterable<AnnotationInterval<V>> {
private static class GenericAnnotationIntervalIterator<V>
implements Iterator<AnnotationInterval<V>> {
private static class KeyEntry implements Comparable<KeyEntry> {
final String key;
int nextChange;
private KeyEntry(String key, int nextChange) {
this.key = key;
this.nextChange = nextChange;
}
@Override
public int compareTo(KeyEntry other) {
// PriorityQueue sorts the smallest item to the top.
return nextChange - other.nextChange;
}
}
private final ReadableAnnotationSet<V> set;
private final int end;
private final Queue<KeyEntry> entries = new PriorityQueue<KeyEntry>();
private AnnotationIntervalImpl<V> interval = null;
private int intervalStart;
private final StringMap<V> annotationsHere;
private final StringMap<V> diffFromLeft;
private boolean valuesEqual(V a, V b) {
if (a == null) {
return b == null;
} else {
return a.equals(b);
}
}
boolean first = true;
// TODO(ohler): simplify.
public GenericAnnotationIntervalIterator(final ReadableAnnotationSet<V> set,
final int start, final int end, ReadableStringSet keys) {
Preconditions.checkPositionIndexes(start, end, set.size());
Preconditions.checkNotNull(keys,
"GenericAnnotationIntervalIterator: Key set must not be null");
this.set = set;
this.end = end;
this.annotationsHere = CollectionUtils.createStringMap();
this.diffFromLeft = CollectionUtils.createStringMap();
this.intervalStart = start;
if (start >= end) {
first = false;
// entries remains empty.
return;
}
// Make annotations and diffFromLeft reflect the first interval
// make every keyEntry point to the next change for that key
keys.each(new Proc() {
@Override
public void apply(String key) {
V startValue = set.getAnnotation(start, key);
V valueLeft;
if (start == 0) {
valueLeft = null;
} else {
valueLeft = set.getAnnotation(start - 1, key);
}
if (valuesEqual(valueLeft, startValue)) {
// no entry in diffFromLeft
} else {
diffFromLeft.put(key, startValue);
}
annotationsHere.put(key, startValue);
int nextChange = set.firstAnnotationChange(start, end, key, startValue);
if (nextChange == -1) {
// don't add
} else {
entries.add(new KeyEntry(key, nextChange));
}
}
});
}
@Override
public boolean hasNext() {
// Maybe check nextIntervalStart?
return first || !entries.isEmpty();
}
@Override
public AnnotationInterval<V> next() {
if (!hasNext()) {
throw new NoSuchElementException("No more intervals");
}
int thisIntervalStart = intervalStart;
int nextIntervalStart;
assert thisIntervalStart < end;
if (first == true) {
if (entries.isEmpty()) {
nextIntervalStart = end;
} else {
nextIntervalStart = entries.peek().nextChange;
}
first = false;
} else {
diffFromLeft.clear();
do {
KeyEntry entry = entries.remove();
assert thisIntervalStart == entry.nextChange;
String key = entry.key;
V value = set.getAnnotation(thisIntervalStart, key);
diffFromLeft.put(key, value);
annotationsHere.put(key, value);
entry.nextChange = set.firstAnnotationChange(thisIntervalStart + 1, end, key, value);
if (entry.nextChange != -1) {
entries.add(entry);
}
} while (!entries.isEmpty() && entries.peek().nextChange == thisIntervalStart);
if (!entries.isEmpty()) {
nextIntervalStart = entries.peek().nextChange;
} else {
nextIntervalStart = end;
}
}
if (interval == null) {
interval = new AnnotationIntervalImpl<V>(thisIntervalStart, nextIntervalStart,
annotationsHere, diffFromLeft);
} else {
interval.set(thisIntervalStart, nextIntervalStart, annotationsHere, diffFromLeft);
}
intervalStart = nextIntervalStart;
return interval;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Removing an annotation interval is not supported");
}
}
private final ReadableAnnotationSet<V> a;
private final int start;
private final int end;
private final ReadableStringSet keys;
public GenericAnnotationIntervalIterable(ReadableAnnotationSet<V> a, int start, int end,
ReadableStringSet keys) {
this.a = a;
this.start = start;
this.end = end;
this.keys = keys;
}
@Override
public Iterator<AnnotationInterval<V>> iterator() {
return new GenericAnnotationIntervalIterator<V>(a, start, end, keys);
}
}