/*
* Copyright 2014 Christopher Exell
*
* 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 org.mongojack;
import com.mongodb.DBObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.mongojack.DBProjection.ProjectionBuilder;
import org.mongojack.DBQuery.Query;
import org.mongojack.DBSort.SortBuilder;
import org.mongojack.internal.query.QueryCondition;
/**
* A Generic Aggregation object that allows the aggregation operations,
* and the return type of the AggregationResult to be specified.
*
* @param <T> The type of results to be produced by the aggregation results.
*
* @author Christopher Exell
* @since 2.1.0
*/
public class Aggregation<T> {
private Class<T> resultType;
private DBObject initialOp;
private DBObject[] additionalOps;
public Aggregation(Class<T> resultType, DBObject initialOp, DBObject... additionalOps)
{
this.resultType = resultType;
this.initialOp = initialOp;
this.additionalOps = additionalOps;
}
public Class<T> getResultType() {
return resultType;
}
public DBObject getInitialOp() {
return initialOp;
}
public DBObject[] getAdditionalOps() {
return additionalOps;
}
public List<DBObject> getAllOps() {
List<DBObject> allOps = new ArrayList<DBObject>();
allOps.add(initialOp);
allOps.addAll(Arrays.asList(additionalOps));
return allOps;
}
public static Pipeline<Group.Accumulator> group(Expression<?> key, Map<String, Group.Accumulator> calculatedFields) {
return new Pipeline<Group.Accumulator>(Group.by(key).set(calculatedFields));
}
public static Pipeline<Group.Accumulator> group(Expression<?> key) {
return new Pipeline<Group.Accumulator>(Group.by(key));
}
public static Pipeline<Group.Accumulator> group(String key) {
return new Pipeline<Group.Accumulator>(Group.by(Expression.path(key)));
}
public static Pipeline<Void> limit(int n) {
return new Pipeline<Void>(new Limit(n));
}
public static Pipeline<Void> match(Query query) {
return new Pipeline<Void>(new Match(query));
}
public static Pipeline<Expression<?>> project(ProjectionBuilder projection) {
return new Pipeline<Expression<?>>(new Project(projection));
}
public static Pipeline<Expression<?>> project(String field) {
return new Pipeline<Expression<?>>(Project.fields(field));
}
public static Pipeline<Expression<?>> project(String field, Expression<?> value) {
return new Pipeline<Expression<?>>(Project.field(field, value));
}
public static Pipeline<Void> skip(int n) {
return new Pipeline<Void>(new Skip(n));
}
public static Pipeline<Void> sort(SortBuilder builder) {
return new Pipeline<Void>(new Sort(builder));
}
public static Pipeline<Void> unwind(String path) {
return new Pipeline<Void>(new Unwind(path));
}
private static abstract class SimpleStage implements Pipeline.Stage<Void> {
@Override
public Pipeline.Stage<Void> set(String field, Void value) {
throw new UnsupportedOperationException();
}
@Override
public Pipeline.Stage<Void> set(Map<String, Void> fields) {
throw new UnsupportedOperationException();
}
}
public static class Group implements Pipeline.Stage<Group.Accumulator> {
public enum Op { $addToSet, $avg, $first, $last, $max, $min, $push, $sum };
private static final Accumulator COUNT = sum(Expression.literal(1));
private final Expression<?> key;
private final Map<String, Accumulator> calculatedFields = new LinkedHashMap<String, Accumulator>();
private Group(Expression<?> key) {
this.key = key;
}
public static Group by(Expression<?> key) {
return new Group(key);
}
public static Group by(String key) {
return new Group(Expression.path(key));
}
public static Accumulator distinct(Expression<?> expression) {
return new Accumulator(Op.$addToSet, expression);
}
public static Accumulator distinct(String... path) {
return new Accumulator(Op.$addToSet, Expression.path(path));
}
public static Accumulator average(Expression<?> expression) {
return new Accumulator(Op.$avg, expression);
}
public static Accumulator average(String... path) {
return new Accumulator(Op.$avg, Expression.path(path));
}
public static Accumulator first(Expression<?> expression) {
return new Accumulator(Op.$first, expression);
}
public static Accumulator first(String... path) {
return new Accumulator(Op.$first, Expression.path(path));
}
public static Accumulator last(Expression<?> expression) {
return new Accumulator(Op.$last, expression);
}
public static Accumulator last(String... path) {
return new Accumulator(Op.$last, Expression.path(path));
}
public static Accumulator max(Expression<?> expression) {
return new Accumulator(Op.$max, expression);
}
public static Accumulator max(String... path) {
return new Accumulator(Op.$max, Expression.path(path));
}
public static Accumulator min(Expression<?> expression) {
return new Accumulator(Op.$min, expression);
}
public static Accumulator min(String... path) {
return new Accumulator(Op.$min, Expression.path(path));
}
public static Accumulator list(Expression<?> expression) {
return new Accumulator(Op.$push, expression);
}
public static Accumulator list(String... path) {
return new Accumulator(Op.$push, Expression.path(path));
}
public static Accumulator sum(Expression<?> expression) {
return new Accumulator(Op.$sum, expression);
}
public static Accumulator sum(String... path) {
return new Accumulator(Op.$sum, Expression.path(path));
}
public static Accumulator count() {
return COUNT;
}
/** Immutable pair of accumulator operation and expression. */
public static class Accumulator {
public final Op operator;
public final Expression<?> expression;
private Accumulator(Op operator, Expression<?> expression) {
this.operator = operator;
this.expression = expression;
}
}
@Override
public Group set(String field, Accumulator value) {
calculatedFields.put(field, value);
return this;
}
@Override
public Group set(Map<String, Accumulator> calculatedFields) {
this.calculatedFields.putAll(calculatedFields);
return this;
}
public Expression<?> key() {
return key;
}
public Set<Entry<String, Accumulator>> calculatedFields() {
return calculatedFields.entrySet();
}
}
public static class Limit extends SimpleStage implements Pipeline.Stage<Void> {
private final int n;
private Limit(int n) {
this.n = n;
}
public int limit() {
return n;
}
}
public static class Match extends DBQuery.AbstractBuilder<Match> implements Pipeline.Stage<Void> {
private final Query query;
private Match() {
this(DBQuery.empty());
}
private Match(Query query) {
this.query = query;
}
@Override
protected Match put(String op, QueryCondition value) {
query.put(op, value);
return this;
}
@Override
protected Match put(String field, String op, QueryCondition value) {
query.put(field, op, value);
return this;
}
@Override
protected Match putGroup(String op, Query... expressions) {
query.putGroup(op, expressions);
return this;
}
@Override
public Pipeline.Stage<Void> set(String field, Void value) {
throw new UnsupportedOperationException();
}
@Override
public Pipeline.Stage<Void> set(Map<String, Void> fields) {
throw new UnsupportedOperationException();
}
public Query query() {
return query;
}
}
public static class Project implements Pipeline.Stage<Expression<?>> {
private final ProjectionBuilder builder;
private Project(ProjectionBuilder builder) {
this.builder = builder;
}
private Project(String field, Expression<?> value) {
this.builder = DBProjection.include();
set(field, value);
}
public static Project fields(String... fields) {
return new Project(DBProjection.include(fields));
}
public static Project fields(Collection<String> fields) {
return new Project(DBProjection.include(fields.toArray(new String[fields.size()])));
}
public static Project field(String field, Expression<?> value) {
return new Project(field, value);
}
public static Project field(String field, String... path) {
return new Project(field, Expression.path(path));
}
public Project excludeId() {
builder.exclude("_id");
return this;
}
@Override
public Project set(String field, Expression<?> value) {
builder.append(field, value);
return this;
}
@Override
public Project set(Map<String, Expression<?>> fields) {
builder.putAll(fields);
return this;
}
public ProjectionBuilder builder() {
return builder;
}
}
public static class Skip extends SimpleStage implements Pipeline.Stage<Void> {
private final int n;
private Skip(int n) {
this.n = n;
}
public int skip() {
return n;
}
}
public static class Sort extends SimpleStage implements Pipeline.Stage<Void> {
private final SortBuilder builder;
private Sort(SortBuilder builder) {
this.builder = builder;
}
public Sort asc(String field) {
builder.asc(field);
return this;
}
public Sort desc(String field) {
builder.desc(field);
return this;
}
public SortBuilder builder() {
return builder;
}
}
public static class Unwind extends SimpleStage implements Pipeline.Stage<Void> {
private final String[] path;
private Unwind(String... path) {
this.path = path;
}
public FieldPath<Object> path() {
return new FieldPath<Object>(path);
}
}
public static class Out extends SimpleStage implements Pipeline.Stage<Void> {
private final String collectionName;
public Out(String collectionName) {
this.collectionName = collectionName;
}
public String collectionName() {
return collectionName;
}
}
/**
* A fluent Aggregation builder.
*
* Type parameter S is the type of value that can be passed to set(String, S), given current latest stage.
*/
public static class Pipeline<S> {
public static interface Stage<S> {
Stage<S> set(String field, S value);
Stage<S> set(Map<String, S> fields);
}
private Stage<S> latestStage;
private final List<Stage<?>> precedingStages;
private Pipeline(Stage<S> latestStage, List<Stage<?>> precedingStages) {
this.latestStage = latestStage;
this.precedingStages = precedingStages;
}
public Pipeline(Stage<S> stage) {
this(stage, new ArrayList<Stage<?>>());
}
public <X> Pipeline<X> then(Stage<X> stage) {
Pipeline<X> result = (Pipeline<X>) this;
result.precedingStages.add(latestStage);
result.latestStage = stage;
return result;
}
public Pipeline<Group.Accumulator> group(Expression<?> key, Map<String, Group.Accumulator> calculatedFields) {
return then(Group.by(key).set(calculatedFields));
}
public Pipeline<Group.Accumulator> group(Expression<?> key) {
return then(Group.by(key));
}
public Pipeline<Group.Accumulator> group(String... key) {
return then(Group.by(Expression.path(key)));
}
public Pipeline<S> set(String field, S value) {
latestStage.set(field, value);
return this;
}
public Pipeline<Void> limit(int n) {
return then(new Limit(n));
}
public Pipeline<Void> match(Query query) {
return then(new Match(query));
}
public Pipeline<Expression<?>> project(ProjectionBuilder projection) {
return then(new Project(projection));
}
public Pipeline<Expression<?>> project(String field) {
return then(Project.field(field));
}
public Pipeline<Expression<?>> project(String field, Expression<?> value) {
return then(new Project(field, value));
}
public Pipeline<Expression<?>> project(Collection<String> fields) {
return then(Project.fields(fields));
}
public Pipeline<Expression<?>> projectFields(String... fields) {
return then(Project.fields(fields));
}
public Pipeline<Expression<?>> projectField(String field, String... value) {
return then(Project.field(field, value));
}
public Pipeline<Void> skip(int n) {
return then(new Skip(n));
}
public Pipeline<Void> sort(SortBuilder builder) {
return then(new Sort(builder));
}
public Pipeline<Void> unwind(String... path) {
return then(new Unwind(path));
}
public Pipeline<Void> out(String collectionName) {
return then(new Out(collectionName));
}
public Iterable<Stage<?>> stages() {
ArrayList<Stage<?>> stages = new ArrayList<Stage<?>>(precedingStages.size() + 1);
stages.addAll(precedingStages);
stages.add(latestStage);
return stages;
}
}
/** Expression builder class. */
public static abstract class Expression<T> {
public static Expression<Object> path(String... path) {
return new FieldPath<Object>(path);
}
public static Expression<Boolean> bool(String... path) {
return new FieldPath<Boolean>(path);
}
public static Expression<Date> date(String... path) {
return new FieldPath<Date>(path);
}
public static Expression<Integer> integer(String... path) {
return new FieldPath<Integer>(path);
}
public static Expression<List<?>> list(String... path) {
return new FieldPath<List<?>>(path);
}
public static Expression<Number> number(String... path) {
return new FieldPath<Number>(path);
}
public static Expression<String> string(String... path) {
return new FieldPath<String>(path);
}
public static <T> Expression<T> literal(T value) {
return new Literal<T>(value);
}
public static Expression<Object> object(Map<String, Expression<?>> properties) {
return new ExpressionObject(properties);
}
// Boolean Operator Expressions
public static Expression<Boolean> and(Expression<?>... operands) {
return new OperatorExpression<Boolean>("$and", operands);
}
public static Expression<Boolean> not(Expression<?> operand) {
return new OperatorExpression<Boolean>("$not", operand);
}
public static Expression<Boolean> or(Expression<?>... operands) {
return new OperatorExpression<Boolean>("$or", operands);
}
// Set Operator Expressions
public static Expression<Boolean> allElementsTrue(Expression<List<?>> set) {
return new OperatorExpression<Boolean>("$allElementsTrue", set);
}
public static Expression<Boolean> anyElementTrue(Expression<List<?>> set) {
return new OperatorExpression<Boolean>("$anyElementTrue", set);
}
public static Expression<List<?>> setDifference(Expression<List<?>> set1, Expression<List<?>> set2) {
return new OperatorExpression<List<?>>("$setDifference", set1, set2);
}
public static Expression<Boolean> setEquals(Expression<List<?>>... sets) {
return new OperatorExpression<Boolean>("$setEquals", sets);
}
public static Expression<List<?>> setIntersection(Expression<List<?>>... sets) {
return new OperatorExpression<List<?>>("$setIntersection", sets);
}
public static Expression<Boolean> setIsSubset(Expression<List<?>> set1, Expression<List<?>> set2) {
return new OperatorExpression<Boolean>("$setIsSubset", set1, set2);
}
public static Expression<List<?>> setUnion(Expression<List<?>>... sets) {
return new OperatorExpression<List<?>>("$setUnion", sets);
}
// Comparison Operator Expressions
public static Expression<Integer> compareTo(Expression<?> value1, Expression<?> value2) {
return new OperatorExpression<Integer>("$cmp", value1, value2);
}
public static Expression<Boolean> equals(Expression<?> value1, Expression<?> value2) {
return new OperatorExpression<Boolean>("$eq", value1, value2);
}
public static Expression<Boolean> greaterThan(Expression<?> value1, Expression<?> value2) {
return new OperatorExpression<Boolean>("$gt", value1, value2);
}
public static Expression<Boolean> greaterThanOrEquals(Expression<?> value1, Expression<?> value2) {
return new OperatorExpression<Boolean>("$gte", value1, value2);
}
public static Expression<Boolean> lessThan(Expression<?> value1, Expression<?> value2) {
return new OperatorExpression<Boolean>("$lt", value1, value2);
}
public static Expression<Boolean> lessThanOrEquals(Expression<?> value1, Expression<?> value2) {
return new OperatorExpression<Boolean>("$lte", value1, value2);
}
public static Expression<Boolean> notEquals(Expression<?> value1, Expression<?> value2) {
return new OperatorExpression<Boolean>("$ne", value1, value2);
}
// Arithmetic Operator Expressions
public static Expression<Number> add(Expression<Number>... numbers) {
return new OperatorExpression<Number>("$add", numbers);
}
public static Expression<Number> divide(Expression<Number> number1, Expression<Number> number2) {
return new OperatorExpression<Number>("$divide", number1, number2);
}
public static Expression<Number> mod(Expression<Number> number1, Expression<Number> number2) {
return new OperatorExpression<Number>("$mod", number1, number2);
}
public static Expression<Number> multiply(Expression<Number>... numbers) {
return new OperatorExpression<Number>("$multiply", numbers);
}
public static Expression<Number> subtract(Expression<Number> number1, Expression<Number> number2) {
return new OperatorExpression<Number>("$subtract", number1, number2);
}
// String Operator Expressions
public static Expression<String> concat(Expression<String>... strings) {
return new OperatorExpression<String>("$concat", strings);
}
public static Expression<Integer> compareToIgnoreCase(Expression<String> string1, Expression<String> string2) {
return new OperatorExpression<Integer>("$strcasecmp", string1, string2);
}
public static Expression<String> substring(Expression<String> string, Expression<Integer> start, Expression<Integer> length) {
return new OperatorExpression<String>("$substr", string, start, length);
}
public static Expression<String> toLowerCase(Expression<String> string) {
return new OperatorExpression<String>("$toLower", string);
}
public static Expression<String> toUpperCase(Expression<String> string) {
return new OperatorExpression<String>("$toUpper", string);
}
// Array Operator Expressions
public static Expression<Integer> size(Expression<List<?>> array) {
return new OperatorExpression<Integer>("$size", array);
}
public static <T> Expression<T> arrayElemAt(Expression<List<?>> expression, Expression<Integer> index) {
return new OperatorExpression<T>("$arrayElemAt", expression, index);
}
// Date Operator Expressions
public static Expression<Integer> dayOfMonth(Expression<Date> date) {
return new OperatorExpression<Integer>("$dayOfMonth", date);
}
public static Expression<Integer> dayOfWeek(Expression<Date> date) {
return new OperatorExpression<Integer>("$dayOfWeek", date);
}
public static Expression<Integer> hour(Expression<Date> date) {
return new OperatorExpression<Integer>("$hour", date);
}
public static Expression<Integer> millisecond(Expression<Date> date) {
return new OperatorExpression<Integer>("$millisecond", date);
}
public static Expression<Integer> minute(Expression<Date> date) {
return new OperatorExpression<Integer>("$minute", date);
}
public static Expression<Integer> month(Expression<Date> date) {
return new OperatorExpression<Integer>("$month", date);
}
public static Expression<Integer> second(Expression<Date> date) {
return new OperatorExpression<Integer>("$second", date);
}
public static Expression<Integer> week(Expression<Date> date) {
return new OperatorExpression<Integer>("$week", date);
}
public static Expression<Integer> year(Expression<Date> date) {
return new OperatorExpression<Integer>("$year", date);
}
// Conditional Operator Expressions
public static <T> Expression<T> cond(Expression<Boolean> condition,
Expression<? extends T> consequent, Expression<? extends T> alternative) {
return new OperatorExpression<T>("$cond", condition, consequent, alternative);
}
public static <T> Expression<T> ifNull(Expression<? extends T> expression, Expression<? extends T> replacement) {
return new OperatorExpression<T>("$ifNull", expression, replacement);
}
}
public static final class FieldPath<T> extends Expression<T> {
private final String[] path;
private FieldPath(String... path) {
this.path = path;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("$").append(path[0]);
for (int i = 1; i < path.length; ++i) {
sb.append('.').append(path[i]);
}
return sb.toString();
}
}
public static class ExpressionObject extends Expression<Object> {
private final Map<String, Expression<?>> properties;
private ExpressionObject(Map<String, Expression<?>> properties) {
this.properties = properties;
}
public Set<Map.Entry<String, Expression<?>>> properties() {
return properties.entrySet();
}
}
public static class Literal<T> extends Expression<T> {
private final T value;
private Literal(T value) {
this.value = value;
}
public T value() {
return value;
}
}
public static class OperatorExpression<T> extends Expression<T> {
private final String operator;
private final Expression<?>[] operands;
private OperatorExpression(String operator, Expression<?>... operands) {
this.operator = operator;
this.operands = operands;
}
public String operator() {
return operator;
}
public Iterable<Expression<?>> operands() {
return Arrays.asList(operands);
}
}
}