/*
* 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.apache.el.stream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.el.ELException;
import javax.el.LambdaExpression;
import org.apache.el.lang.ELArithmetic;
import org.apache.el.lang.ELSupport;
import org.apache.el.util.MessageFactory;
public class Stream {
private final Iterator<Object> iterator;
public Stream(Iterator<Object> iterator) {
this.iterator = iterator;
}
public Stream filter(final LambdaExpression le) {
Iterator<Object> downStream = new OpIterator() {
@Override
protected void findNext() {
while (iterator.hasNext()) {
Object obj = iterator.next();
if (ELSupport.coerceToBoolean(null, le.invoke(obj),
true).booleanValue()) {
next = obj;
foundNext = true;
break;
}
}
}
};
return new Stream(downStream);
}
public Stream map(final LambdaExpression le) {
Iterator<Object> downStream = new OpIterator() {
@Override
protected void findNext() {
if (iterator.hasNext()) {
Object obj = iterator.next();
next = le.invoke(obj);
foundNext = true;
}
}
};
return new Stream(downStream);
}
public Stream flatMap(final LambdaExpression le) {
Iterator<Object> downStream = new OpIterator() {
private Iterator<?> inner;
@Override
protected void findNext() {
while (iterator.hasNext() ||
(inner != null && inner.hasNext())) {
if (inner == null || !inner.hasNext()) {
inner = ((Stream) le.invoke(iterator.next())).iterator;
}
if (inner.hasNext()) {
next = inner.next();
foundNext = true;
break;
}
}
}
};
return new Stream(downStream);
}
public Stream distinct() {
Iterator<Object> downStream = new OpIterator() {
private Set<Object> values = new HashSet<>();
@Override
protected void findNext() {
while (iterator.hasNext()) {
Object obj = iterator.next();
if (values.add(obj)) {
next = obj;
foundNext = true;
break;
}
}
}
};
return new Stream(downStream);
}
public Stream sorted() {
Iterator<Object> downStream = new OpIterator() {
private Iterator<Object> sorted = null;
@Override
protected void findNext() {
if (sorted == null) {
sort();
}
if (sorted.hasNext()) {
next = sorted.next();
foundNext = true;
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private final void sort() {
List list = new ArrayList<>();
while (iterator.hasNext()) {
list.add(iterator.next());
}
Collections.sort(list);
sorted = list.iterator();
}
};
return new Stream(downStream);
}
public Stream sorted(final LambdaExpression le) {
Iterator<Object> downStream = new OpIterator() {
private Iterator<Object> sorted = null;
@Override
protected void findNext() {
if (sorted == null) {
sort(le);
}
if (sorted.hasNext()) {
next = sorted.next();
foundNext = true;
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private final void sort(LambdaExpression le) {
List list = new ArrayList<>();
Comparator<Object> c = new LambdaExpressionComparator(le);
while (iterator.hasNext()) {
list.add(iterator.next());
}
Collections.sort(list, c);
sorted = list.iterator();
}
};
return new Stream(downStream);
}
public Object forEach(final LambdaExpression le) {
while (iterator.hasNext()) {
le.invoke(iterator.next());
}
return null;
}
public Stream peek(final LambdaExpression le) {
Iterator<Object> downStream = new OpIterator() {
@Override
protected void findNext() {
if (iterator.hasNext()) {
Object obj = iterator.next();
le.invoke(obj);
next = obj;
foundNext = true;
}
}
};
return new Stream(downStream);
}
public Iterator<?> iterator() {
return iterator;
}
public Stream limit(final Number count) {
return substream(Integer.valueOf(0), count);
}
public Stream substream(final Number start) {
return substream(start, Integer.valueOf(Integer.MAX_VALUE));
}
public Stream substream(final Number start, final Number end) {
Iterator<Object> downStream = new OpIterator() {
private final int startPos = start.intValue();
private final int endPos = end.intValue();
private int itemCount = 0;
@Override
protected void findNext() {
while (itemCount < startPos && iterator.hasNext()) {
iterator.next();
itemCount++;
}
if (itemCount < endPos && iterator.hasNext()) {
itemCount++;
next = iterator.next();
foundNext = true;
}
}
};
return new Stream(downStream);
}
public List<Object> toList() {
List<Object> result = new ArrayList<>();
while (iterator.hasNext()) {
result.add(iterator.next());
}
return result;
}
public Object[] toArray() {
List<Object> result = new ArrayList<>();
while (iterator.hasNext()) {
result.add(iterator.next());
}
return result.toArray(new Object[result.size()]);
}
public Optional reduce(LambdaExpression le) {
Object seed = null;
if (iterator.hasNext()) {
seed = iterator.next();
}
if (seed == null) {
return Optional.EMPTY;
} else {
return new Optional(reduce(seed, le));
}
}
public Object reduce(Object seed, LambdaExpression le) {
Object result = seed;
while (iterator.hasNext()) {
result = le.invoke(result, iterator.next());
}
return result;
}
public Optional max() {
return compare(true);
}
public Optional max(LambdaExpression le) {
return compare(true, le);
}
public Optional min() {
return compare(false);
}
public Optional min(LambdaExpression le) {
return compare(false, le);
}
public Optional average() {
long count = 0;
Number sum = Long.valueOf(0);
while (iterator.hasNext()) {
count++;
sum = ELArithmetic.add(sum, iterator.next());
}
if (count == 0) {
return Optional.EMPTY;
} else {
return new Optional(ELArithmetic.divide(sum, Long.valueOf(count)));
}
}
public Number sum() {
Number sum = Long.valueOf(0);
while (iterator.hasNext()) {
sum = ELArithmetic.add(sum, iterator.next());
}
return sum;
}
public Long count() {
long count = 0;
while (iterator.hasNext()) {
iterator.next();
count ++;
}
return Long.valueOf(count);
}
public Optional anyMatch(LambdaExpression le) {
if (!iterator.hasNext()) {
return Optional.EMPTY;
}
Boolean match = Boolean.FALSE;
while (!match.booleanValue() && iterator.hasNext()) {
match = (Boolean) le.invoke(iterator.next());
}
return new Optional(match);
}
public Optional allMatch(LambdaExpression le) {
if (!iterator.hasNext()) {
return Optional.EMPTY;
}
Boolean match = Boolean.TRUE;
while (match.booleanValue() && iterator.hasNext()) {
match = (Boolean) le.invoke(iterator.next());
}
return new Optional(match);
}
public Optional noneMatch(LambdaExpression le) {
if (!iterator.hasNext()) {
return Optional.EMPTY;
}
Boolean match = Boolean.FALSE;
while (!match.booleanValue() && iterator.hasNext()) {
match = (Boolean) le.invoke(iterator.next());
}
return new Optional(Boolean.valueOf(!match.booleanValue()));
}
public Optional findFirst() {
if (iterator.hasNext()) {
return new Optional(iterator.next());
} else {
return Optional.EMPTY;
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private Optional compare(boolean isMax) {
Comparable result = null;
if (iterator.hasNext()) {
Object obj = iterator.next();
if ((obj instanceof Comparable)) {
result = (Comparable) obj;
} else {
throw new ELException(
MessageFactory.get("stream.compare.notComparable"));
}
}
while (iterator.hasNext()) {
Object obj = iterator.next();
if ((obj instanceof Comparable)) {
if (isMax && ((Comparable) obj).compareTo(result) > 0) {
result = (Comparable) obj;
} else if (!isMax && ((Comparable) obj).compareTo(result) < 0) {
result = (Comparable) obj;
}
} else {
throw new ELException(
MessageFactory.get("stream.compare.notComparable"));
}
}
if (result == null) {
return Optional.EMPTY;
} else {
return new Optional(result);
}
}
private Optional compare(boolean isMax, LambdaExpression le) {
Object result = null;
if (iterator.hasNext()) {
Object obj = iterator.next();
result = obj;
}
while (iterator.hasNext()) {
Object obj = iterator.next();
if (isMax && ELSupport.coerceToNumber(null, le.invoke(obj, result),
Integer.class).intValue() > 0) {
result = obj;
} else if (!isMax && ELSupport.coerceToNumber(null, le.invoke(obj, result),
Integer.class).intValue() < 0) {
result = obj;
}
}
if (result == null) {
return Optional.EMPTY;
} else {
return new Optional(result);
}
}
private static class LambdaExpressionComparator
implements Comparator<Object> {
private final LambdaExpression le;
public LambdaExpressionComparator(LambdaExpression le) {
this.le = le;
}
@Override
public int compare(Object o1, Object o2) {
return ELSupport.coerceToNumber(
null, le.invoke(o1, o2), Integer.class).intValue();
}
}
private abstract static class OpIterator implements Iterator<Object> {
protected boolean foundNext = false;
protected Object next;
@Override
public boolean hasNext() {
if (foundNext) {
return true;
}
findNext();
return foundNext;
}
@Override
public Object next() {
if (foundNext) {
foundNext = false;
return next;
}
findNext();
if (foundNext) {
foundNext = false;
return next;
} else {
throw new NoSuchElementException();
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
protected abstract void findNext();
}
}