package com.googlecode.totallylazy.collections;
import com.googlecode.totallylazy.functions.Binary;
import com.googlecode.totallylazy.functions.Function1;
import com.googlecode.totallylazy.functions.Monoid;
import com.googlecode.totallylazy.functions.Reducer;
import com.googlecode.totallylazy.Sequence;
import com.googlecode.totallylazy.Strings;
import com.googlecode.totallylazy.functions.Unary;
import com.googlecode.totallylazy.numbers.Integers;
import org.junit.Test;
import java.util.Map;
import static com.googlecode.totallylazy.Assert.assertThat;
import static com.googlecode.totallylazy.collections.SelectionTest.Concat.concat;
import static com.googlecode.totallylazy.predicates.Predicates.is;
import static com.googlecode.totallylazy.predicates.Predicates.where;
import static com.googlecode.totallylazy.Sequences.one;
import static com.googlecode.totallylazy.Sequences.sequence;
import static com.googlecode.totallylazy.Unchecked.cast;
import static com.googlecode.totallylazy.collections.PersistentMap.constructors.map;
public class SelectionTest {
Keyword<String> name = Keyword.keyword();
Keyword<Integer> age = Keyword.keyword();
PersistentMap<String, Object> dan = map(name.key(), "Dan", age.key(), 21);
PersistentMap<String, Object> matt = map(name.key(), "Matt", age.key(), 22);
PersistentMap<String, Object> bob = map(name.key(), "Bob", age.key(), 22);
Sequence<PersistentMap<String, Object>> data = sequence(dan, matt, bob);
@Test
public void canFilter() throws Exception {
assertThat(data.filter(where(name, is("Dan"))), is(one(dan)));
}
@Test
public void canSelect() throws Exception {
assertThat(data.map(name), is(sequence("Dan", "Matt", "Bob")));
assertThat(data.map(select(name)), is(sequence(map("name", "Dan"), map("name", "Matt"), map("name", "Bob"))));
}
@Test
public void canReduce() throws Exception {
Aggregate<String, String> minimumName = Aggregate.aggregate(name, Strings.minimum);
Aggregate<Integer, Integer> minimumAge = Aggregate.aggregate(age, Integers.minimum());
assertThat(data.reduce(minimumAge), is(21));
assertThat(data.reduce(minimumName), is("Bob"));
assertThat(data.reduce(select(minimumName, minimumAge)), is(map("name", "Bob", "age", 21)));
}
@Test
public void supportsConcatination() throws Exception {
Keyword<String> composite = composite(concat, name, age);
assertThat(data.filter(where(name, is("Dan"))).map(composite), is(sequence("Dan21")));
assertThat(data.filter(where(name, is("Dan"))).map(select(composite)), is(one(map(composite.key(), "Dan21"))));
}
@Test
public void supportsGroupByAndConcatication() throws Exception {
Aggregate<String, String> join = Aggregate.aggregate(name, concat);
assertThat(data.groupBy(age).map(group -> group.reduce(join)), is(sequence("Dan", "MattBob")));
}
@Test
public void supportsUppercase() throws Exception {
Keyword<String> upperCase = compose(name, String::toUpperCase);
assertThat(data.filter(where(name, is("Dan"))).map(upperCase), is(sequence("DAN")));
assertThat(data.filter(where(name, is("Dan"))).map(select(upperCase)), is(one(map(upperCase.key(), "DAN"))));
}
private <T, R> Keyword<R> compose(Keyword<T> keyword, Function1<T, R> function) {
return new Keyword<R>() {
@Override
public String key() {
return function.toString() + "(" + keyword.key() + ")";
}
@Override
public Class<R> forClass() {
return null;
}
@Override
public R call(Map<String, Object> map) throws Exception {
return function.call(keyword.call(map));
}
};
}
@SafeVarargs
public final <T, R> Keyword<R> composite(Reducer<? super T, R> reducer, Keyword<? extends T>... functions) {
return new Keyword<R>() {
Sequence<Keyword<? extends T>> keywords = sequence(functions);
@Override
public Class<R> forClass() {
return null;
}
@Override
public String key() {
return keywords.map(Keyword::key).toString(reducer.toString() + "(", ",", ")");
}
@Override
public R call(Map<String, Object> map) throws Exception {
return keywords.fold(reducer.identity(), (a, k) -> reducer.call(a, k.apply(map)));
}
};
}
private Projection<PersistentMap<String, Object>> select(Selection... selections) {
return Projection.projection(map(), (seed, row) ->
sequence(selections).fold(seed, (accumulator, selection) -> selection.select(row, accumulator)));
}
interface Projection<T> extends Monoid<T>, Unary<T> {
@Override
default T call(T t) throws Exception {
return call(identity(), t);
}
static <T> Projection<T> projection(T identity, Binary<T> binary) {
return new Projection<T>() {
@Override
public T call(T t, T t2) throws Exception {
return binary.call(t, t2);
}
@Override
public T identity() {
return identity;
}
};
}
}
enum Concat implements Reducer<Object, String> {
concat;
@Override
public String call(String s, Object o) throws Exception {
return s + o;
}
@Override
public String identity() {
return "";
}
}
interface Aggregate<T, R> extends Reducer<PersistentMap<String, Object>, R>, Selection {
Keyword<? extends T> keyword();
Reducer<? super T, R> reducer();
static <T, R> Aggregate<T, R> aggregate(Keyword<? extends T> keyword, Reducer<? super T, R> reducer) {
return new Aggregate<T, R>() {
@Override
public Keyword<? extends T> keyword() {
return keyword;
}
@Override
public Reducer<? super T, R> reducer() {
return reducer;
}
};
}
@Override
default R call(R seed, PersistentMap<String, Object> data) throws Exception {
return reducer().call(seed, keyword().call(data));
}
@Override
default R identity() {
return reducer().identity();
}
@Override
default PersistentMap<String, Object> select(PersistentMap<String, Object> source, PersistentMap<String, Object> destination) {
T sourceValue = keyword().apply(source);
R value = sourceValue == null ? identity() : cast(sourceValue);
R reduced = apply(value, destination);
return destination.insert(keyword().key(), reduced);
}
}
}