com.google.ical.iter.CompoundIteratorImpl Maven / Gradle / Ivy
// Copyright (C) 2006 Google Inc.
//
// Licensed 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 com.google.ical.iter;
import com.google.ical.values.DateValue;
import java.util.Collection;
import java.util.Comparator;
import java.util.NoSuchElementException;
import java.util.PriorityQueue;
/**
* a recurrence iterator that combines others. Some may be inclusions, and
* some may be exclusions.
*
* @author [email protected] (Mike Samuel)
*/
final class CompoundIteratorImpl implements RecurrenceIterator {
/** a queue that keeps the earliest dates at the head */
private PriorityQueue queue;
private HeapElement pending;
/**
* the number of inclusions on queue. We keep track of this so that we
* don't have to drain the exclusions to conclude that the series is
* exhausted.
*/
private int nInclusionsRemaining;
/**
* A generator that will generate only dates that are generated by inclusions
* and will not generate any dates that are generated by exclusions -- i.e.
* exclusions trump inclusions.
* @param inclusions iterators whose elements should be included unless
* explicitly excluded. non null without null elements.
* @param exclusions iterators whose elements should not be included.
* non null without null elements.
*/
CompoundIteratorImpl(
Collection inclusions,
Collection exclusions) {
queue = new PriorityQueue(
inclusions.size() + exclusions.size(), HeapElement.CMP);
for (RecurrenceIterator it : inclusions) {
HeapElement el = new HeapElement(true, it);
if (el.shift()) {
queue.add(el);
++nInclusionsRemaining;
}
}
for (RecurrenceIterator it : exclusions) {
HeapElement el = new HeapElement(false, it);
if (el.shift()) { queue.add(el); }
}
}
public boolean hasNext() {
requirePending();
return null != pending;
}
public DateValue next() {
requirePending();
if (null == pending) { throw new NoSuchElementException(); }
DateValue head = pending.head();
reattach(pending);
pending = null;
return head;
}
public void remove() { throw new UnsupportedOperationException(); }
public void advanceTo(DateValue newStart) {
long newStartCmp = DateValueComparison.comparable(newStart);
if (null != pending) {
if (pending.comparable() >= newStartCmp) { return; }
pending.advanceTo(newStart);
reattach(pending);
pending = null;
}
// Pull each element off the stack in turn, and advance it.
// Once we reach one we don't need to advance, we're done
while (0 != nInclusionsRemaining && !queue.isEmpty()
&& queue.peek().comparable() < newStartCmp) {
HeapElement el = queue.poll();
el.advanceTo(newStart);
reattach(el);
}
}
/**
* if the given element's iterator has more data, then push back onto the
* heap.
*/
private void reattach(HeapElement el) {
if (el.shift()) {
queue.add(el);
} else if (el.inclusion) {
// if we have no live inclusions, then the rest are exclusions which we
// can safely discard.
if (0 == --nInclusionsRemaining) {
queue.clear();
}
}
}
/**
* make sure that pending contains the next inclusive HeapElement that doesn't
* match any exclusion, and remove any dupes of it.
*/
private void requirePending() {
if (null != pending) { return; }
long exclusionComparable = Long.MIN_VALUE;
while (0 != nInclusionsRemaining && !queue.isEmpty()) {
// find a candidate that is not excluded
HeapElement inclusion = null;
do {
HeapElement candidate = queue.poll();
if (candidate.inclusion) {
if (exclusionComparable != candidate.comparable()) {
inclusion = candidate;
break;
}
} else {
exclusionComparable = candidate.comparable();
}
reattach(candidate);
if (0 == nInclusionsRemaining) { return; }
} while (!queue.isEmpty());
if (inclusion == null) { return; }
long inclusionComparable = inclusion.comparable();
// Check for any following exclusions and for duplicates.
// We could change the sort order so that exclusions always preceded
// inclusions, but that would be less efficient and would make the
// ordering different than the comparable value.
boolean excluded = exclusionComparable == inclusionComparable;
while (!queue.isEmpty()
&& queue.peek().comparable() == inclusionComparable) {
HeapElement match = queue.poll();
excluded |= !match.inclusion;
reattach(match);
if (0 == nInclusionsRemaining) { return; }
}
if (!excluded) {
pending = inclusion;
return;
} else {
reattach(inclusion);
}
}
}
}
final class HeapElement {
/**
* should iterators items be included in the series or should they
* nullify any matched items included by other series.
*/
final boolean inclusion;
/** the {@link DateValueComparison#comparable} for {@link #head}. */
private long comparable;
/** the last value removed from it. In utc. */
private DateValue head;
private RecurrenceIterator it;
HeapElement(boolean inclusion, RecurrenceIterator it) {
this.inclusion = inclusion;
this.it = it;
}
/** the last value removed from the iterator. */
DateValue head() { return head; }
/**
* A given HeapElement may be compared to many others as it bubbles towards
* the heap's root, so we cache this for each HeapElement.
*/
long comparable() { return comparable; }
/**
* discard the current, and return true iff there is another head to
* replace it.
*/
boolean shift() {
if (!it.hasNext()) { return false; }
head = it.next();
comparable = DateValueComparison.comparable(head);
return true;
}
/**
* advance the underlying iterator to the given date value a la
* {@link RecurrenceIterator#advanceTo}.
*/
void advanceTo(DateValue dvUtc) {
it.advanceTo(dvUtc);
}
@Override
public String toString() {
return
"[" + head.toString() + (inclusion ? ", inclusion]" : ", exclusion]");
}
/** compares to heap elements by comparing their heads. */
static Comparator CMP = new Comparator() {
public int compare(HeapElement a, HeapElement b) {
long ac = a.comparable(),
bc = b.comparable();
return ac < bc ? -1 : ac == bc ? 0 : 1;
}
};
}