/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* 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.asakusafw.utils.io;
import java.io.Closeable;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
/**
* Utilities about {@link Source}.
* @since 0.6.0
* @version 0.7.1
*/
public final class Sources {
private Sources() {
return;
}
/**
* Returns an empty {@link Source}.
* @param <T> the element type
* @return an empty source
*/
public static <T> Source<T> empty() {
return new Source<T>() {
@Override
public boolean next() throws IOException, InterruptedException {
return false;
}
@Override
public T get() throws IOException, InterruptedException {
throw new NoSuchElementException();
}
@Override
public void close() {
return;
}
};
}
/**
* Returns a {@link Source} for the array.
* @param <T> the array element type
* @param values the target array
* @return the wrapped source
*/
@SafeVarargs
public static <T> Source<T> wrap(T... values) {
return new Source<T>() {
private int position = -1;
@Override
public boolean next() throws IOException, InterruptedException {
if (position + 1 < values.length) {
position++;
return true;
} else {
return false;
}
}
@Override
public T get() throws IOException, InterruptedException {
if (0 <= position && position < values.length) {
return values[position];
}
throw new NoSuchElementException();
}
@Override
public void close() {
return;
}
};
}
/**
* Wraps {@link Iterator} object.
* @param iterator the iterator object
* @param <T> the element type
* @return the {@link Source} which provides elements in the iterator
*/
public static <T> Source<T> wrap(Iterator<? extends T> iterator) {
return new Source<T>() {
private T next;
@Override
public boolean next() throws IOException, InterruptedException {
next = null;
if (iterator.hasNext()) {
next = iterator.next();
return true;
} else {
return false;
}
}
@Override
public T get() throws IOException, InterruptedException {
if (next == null) {
throw new NoSuchElementException();
}
return next;
}
@Override
public void close() throws IOException {
next = null;
}
};
}
/**
* Concatenates a list of sources.
* @param sources the list of sources
* @param <T> the element type
* @return the concatenated source
*/
public static <T> Source<T> concat(List<? extends Source<? extends T>> sources) {
Iterator<? extends Source<? extends T>> iter = sources.iterator();
return new Source<T>() {
Source<? extends T> current;
@Override
public boolean next() throws IOException, InterruptedException {
while (true) {
if (current != null) {
if (current.next()) {
return true;
} else {
closeCurrent();
}
}
assert current == null;
if (iter.hasNext()) {
current = iter.next();
} else {
return false;
}
}
}
@Override
public T get() throws IOException, InterruptedException {
if (current == null) {
throw new NoSuchElementException();
}
return current.get();
}
@Override
public void close() throws IOException {
closeCurrent();
assert current == null;
while (iter.hasNext()) {
current = iter.next();
closeCurrent();
assert current == null;
}
}
private void closeCurrent() throws IOException {
if (current != null) {
current.close();
current = null;
}
}
};
}
/**
* Merges the sorted sources.
* @param <T> the element type
* @param sortedSources sources which are sorted by the {@code comparator}
* @param comparator the comparator
* @return the merged source
* @since 0.7.1
*/
public static <T> Source<T> merge(
List<? extends Source<? extends T>> sortedSources,
Comparator<? super T> comparator) {
if (sortedSources.isEmpty()) {
return empty();
} else if (sortedSources.size() == 1) {
@SuppressWarnings("unchecked")
Source<T> source = (Source<T>) sortedSources.get(0);
return source;
}
return new HeapSource<>(sortedSources, comparator);
}
private static final class HeapSource<T> implements Source<T> {
private final HeapElement<T>[] heap;
private final Comparator<? super T> comparator;
private boolean firstTime = true;
@SuppressWarnings("unchecked")
HeapSource(
List<? extends Source<? extends T>> sortedSources,
Comparator<? super T> comparator) {
assert sortedSources.isEmpty() == false;
this.heap = new HeapElement[sortedSources.size()];
this.comparator = comparator;
for (int i = 0; i < heap.length; i++) {
this.heap[i] = new HeapElement<>(sortedSources.get(i));
}
}
@Override
public boolean next() throws IOException, InterruptedException {
HeapElement<T>[] h = heap;
if (firstTime) {
firstTime = false;
for (int i = 0; i < h.length; i++) {
h[i].fill();
}
for (int i = h.length / 2; i >= 0; i--) {
shiftDown(i);
}
} else {
h[0].fill();
shiftDown(0);
}
return h[0].value != null;
}
private void shiftDown(int i) {
HeapElement<T>[] h = heap;
int length = h.length;
int current = i;
while (true) {
int left = (current << 1) + 1;
int right = left + 1;
if (left < length && isViolate(h[current], h[left])) {
if (right < length && isViolate(h[left], h[right])) {
swap(current, right);
current = right;
} else {
swap(current, left);
current = left;
}
} else {
if (right < length && isViolate(h[current], h[right])) {
swap(current, right);
current = right;
} else {
break;
}
}
}
}
private boolean isViolate(HeapElement<T> parent, HeapElement<T> node) {
T v1 = parent.value;
T v2 = node.value;
if (v1 == null) {
if (v2 == null) {
return false;
}
return true;
} else if (v2 == null) {
return false;
}
return comparator.compare(v1, v2) > 0;
}
private void swap(int i, int j) {
HeapElement<T>[] h = heap;
HeapElement<T> t = h[i];
h[i] = h[j];
h[j] = t;
}
@Override
public T get() {
HeapElement<T> top = heap[0];
if (firstTime || top.value == null) {
throw new NoSuchElementException();
}
return top.value;
}
@Override
public void close() throws IOException {
IOException firstException = null;
for (HeapElement<T> element : heap) {
try {
element.close();
} catch (IOException e) {
if (firstException == null) {
firstException = e;
}
}
}
if (firstException != null) {
throw firstException;
}
}
@Override
public String toString() {
return Arrays.toString(heap);
}
}
private static final class HeapElement<T> implements Closeable {
private final Source<? extends T> source;
T value;
HeapElement(Source<? extends T> source) {
this.source = source;
}
void fill() throws IOException, InterruptedException {
if (source.next()) {
value = source.get();
} else {
value = null;
}
}
@Override
public void close() throws IOException {
source.close();
}
@Override
public String toString() {
return String.valueOf(value);
}
}
}