/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.util; import java.util.Iterator; import java.util.NoSuchElementException; /** * Iterator which filters out another iterator. The filtering is done via an abstract method, which lets us * avoid unnecessary allocations (of a seperate predicate object). This method comes in two flavors, immutable * and mutable; for obvious reasons, mutability is only supported if the delegate iterator is also mutable. * @param <T> the type to iterate */ public abstract class FilteringIterator<T> implements Iterator<T> { private static enum State { /** * This iterator is freshly created. It hasn't asked its delegate anything. */ FRESH, /** * An item has been requested of the delegate, and it's pending to be delivered to this iterator's user. */ NEXT_PENDING, /** * An item has been requested of the delegate, but it's already been delivered to this iterator's user. */ NEXT_RETRIEVED, /** * There are no items left to deliver. */ DONE } private final Iterator<T> delegate; private final boolean isMutable; private T next; private State state; public FilteringIterator(Iterator<T> delegate, boolean isMutable) { this.delegate = delegate; this.isMutable = isMutable; this.state = State.FRESH; } protected abstract boolean allow(T item); @Override public boolean hasNext() { switch (state) { case NEXT_PENDING: return true; case FRESH: case NEXT_RETRIEVED: advance(); assert (state == State.NEXT_PENDING) || (state == State.DONE) : state; return state == State.NEXT_PENDING; case DONE: return false; default: throw new AssertionError("FilteringIterator has unknown internal state: " + state); } } @Override public T next() { if (state == State.FRESH || state == State.NEXT_RETRIEVED) { advance(); } if (state == State.DONE) { throw new NoSuchElementException(); } assert state == State.NEXT_PENDING : state; state = State.NEXT_RETRIEVED; return next; } @Override public void remove() { if (!isMutable) { throw new UnsupportedOperationException(); } if (state != State.NEXT_RETRIEVED) { throw new IllegalStateException(state.name()); } delegate.remove(); } /** * Advances the delegate until we get an element which passes the filter, * * Coming into this method, this iterator's state must be FRESH or NEXT_RETRIEVED. When this method completes * successfully, the state will be DONE or NEXT_PENDING. * * At the end of this method, this FilteringIterator's state will be either DONE or NEXT_KNOWN. This method * should only be invoked when the state is NEEDS_NEXT. If the state of this iterator is NEXT_KNOWN on return, * the "next" field will point to the correct item. */ private void advance() { assert (state == State.FRESH) || (state == State.NEXT_RETRIEVED) : state; while (state != State.NEXT_PENDING) { if (!delegate.hasNext()) { state = State.DONE; return; } else { T item = delegate.next(); if (allow(item)) { next = item; state = State.NEXT_PENDING; } } } } }