/* * SonarQube * Copyright (C) 2009-2017 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.core.util; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import javax.annotation.CheckForNull; import org.apache.commons.lang.ArrayUtils; import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; public abstract class CloseableIterator<O> implements Iterator<O>, AutoCloseable { private O nextObject = null; boolean isClosed = false; private static final CloseableIterator<?> EMPTY_CLOSEABLE_ITERATOR = new CloseableIterator<Object>() { @Override public boolean hasNext() { return false; } @Override protected Object doNext() { // never called anyway throw new NoSuchElementException("Empty closeable Iterator has no element"); } @Override protected void doClose() throws Exception { // do nothing } }; @SuppressWarnings("unchecked") public static <T> CloseableIterator<T> emptyCloseableIterator() { return (CloseableIterator<T>) EMPTY_CLOSEABLE_ITERATOR; } /** * Creates a CloseableIterator from a regular {@link Iterator}. * * @throws IllegalArgumentException if the specified {@link Iterator} is a CloseableIterator */ public static <T> CloseableIterator<T> from(Iterator<T> iterator) { // early fail requireNonNull(iterator); checkArgument(!(iterator instanceof AutoCloseable), "This method does not support creating a CloseableIterator from an Iterator which is Closeable"); return new RegularIteratorWrapper<>(iterator); } /** * Wraps a {@code CloseableIterator} and optionally other instances of {@code AutoCloseable} that must be closed * at the same time. The wrapped iterator is closed first then the other {@code AutoCloseable} in the defined order. * * @throws IllegalArgumentException if the parameter {@code otherCloseables} contains the wrapped iterator */ public static <T> CloseableIterator<T> wrap(CloseableIterator<T> iterator, AutoCloseable... otherCloseables) { return new CloseablesIteratorWrapper<>(iterator, otherCloseables); } @Override public boolean hasNext() { // Optimization to not call bufferNext() when already closed if (isClosed) { return false; } boolean hasNext = nextObject != null || bufferNext() != null; if (!hasNext) { close(); } return hasNext; } private O bufferNext() { try { nextObject = doNext(); return nextObject; } catch (RuntimeException e) { close(); throw e; } } /** * Reads next item and returns {@code null} if no more items. */ @CheckForNull protected abstract O doNext(); @Override public O next() { if (!hasNext()) { throw new NoSuchElementException(); } O result = nextObject; nextObject = null; return result; } @Override public final void remove() { try { doRemove(); } catch (RuntimeException e) { close(); throw e; } } /** * By default it throws an UnsupportedOperationException. Override this method * to change behavior. */ protected void doRemove() { throw new UnsupportedOperationException("remove() is not supported by default. Override doRemove() if needed."); } /** * Do not declare "throws IOException" */ @Override public final void close() { try { doClose(); isClosed = true; } catch (Exception e) { Throwables.propagate(e); } } protected abstract void doClose() throws Exception; private static class RegularIteratorWrapper<T> extends CloseableIterator<T> { private final Iterator<T> iterator; public RegularIteratorWrapper(Iterator<T> iterator) { this.iterator = iterator; } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public T next() { return iterator.next(); } @Override protected T doNext() { throw new UnsupportedOperationException("hasNext has been override, doNext is never called"); } @Override protected void doClose() throws Exception { // do nothing } } private static class CloseablesIteratorWrapper<T> extends CloseableIterator<T> { private final CloseableIterator<T> iterator; private final List<AutoCloseable> otherCloseables; private CloseablesIteratorWrapper(CloseableIterator<T> iterator, AutoCloseable... otherCloseables) { requireNonNull(iterator); checkArgument(!ArrayUtils.contains(otherCloseables, iterator)); this.iterator = iterator; // the advantage of using ImmutableList is that it does not accept null elements, so it fails fast, during // construction of the wrapper, but not in close() this.otherCloseables = ImmutableList.copyOf(otherCloseables); } @Override protected T doNext() { return iterator.hasNext() ? iterator.next() : null; } @Override protected void doClose() throws Exception { // iterator can be already closed by doNext(), but closing here ensures // that iterator is closed when it is not fully traversed. iterator.close(); for (AutoCloseable otherCloseable : otherCloseables) { otherCloseable.close(); } } } }