From f5da4688bb6634552b452d666b93d388357ee2c8 Mon Sep 17 00:00:00 2001
From: Christoph Strobl <cstrobl@pivotal.io>
Date: Tue, 6 Dec 2016 14:32:48 +0100
Subject: [PATCH 1/3] DATAMONGO-1548 - Add support for MongoDB 3.4 aggregation
 operators.

Prepare issue branch.
---
 pom.xml                                  | 2 +-
 spring-data-mongodb-cross-store/pom.xml  | 4 ++--
 spring-data-mongodb-distribution/pom.xml | 2 +-
 spring-data-mongodb-log4j/pom.xml        | 2 +-
 spring-data-mongodb/pom.xml              | 2 +-
 5 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/pom.xml b/pom.xml
index ea80a3cb74..19b54876d4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
 
 	<groupId>org.springframework.data</groupId>
 	<artifactId>spring-data-mongodb-parent</artifactId>
-	<version>1.10.0.BUILD-SNAPSHOT</version>
+	<version>1.10.0.DATAMONGO-1548-SNAPSHOT</version>
 	<packaging>pom</packaging>
 
 	<name>Spring Data MongoDB</name>
diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml
index ae0a5d6c8f..08d1e76ce9 100644
--- a/spring-data-mongodb-cross-store/pom.xml
+++ b/spring-data-mongodb-cross-store/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.data</groupId>
 		<artifactId>spring-data-mongodb-parent</artifactId>
-		<version>1.10.0.BUILD-SNAPSHOT</version>
+		<version>1.10.0.DATAMONGO-1548-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
@@ -48,7 +48,7 @@
 		<dependency>
 			<groupId>org.springframework.data</groupId>
 			<artifactId>spring-data-mongodb</artifactId>
-			<version>1.10.0.BUILD-SNAPSHOT</version>
+			<version>1.10.0.DATAMONGO-1548-SNAPSHOT</version>
 		</dependency>
 
 		<dependency>
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index 2d02722262..5ab3647b24 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -13,7 +13,7 @@
 	<parent>
 		<groupId>org.springframework.data</groupId>
 		<artifactId>spring-data-mongodb-parent</artifactId>
-		<version>1.10.0.BUILD-SNAPSHOT</version>
+		<version>1.10.0.DATAMONGO-1548-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml
index ee5e3336db..0142c1b99a 100644
--- a/spring-data-mongodb-log4j/pom.xml
+++ b/spring-data-mongodb-log4j/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>org.springframework.data</groupId>
 		<artifactId>spring-data-mongodb-parent</artifactId>
-		<version>1.10.0.BUILD-SNAPSHOT</version>
+		<version>1.10.0.DATAMONGO-1548-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index 8072d3f665..8bdc1b020d 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -11,7 +11,7 @@
 	<parent>
 		<groupId>org.springframework.data</groupId>
 		<artifactId>spring-data-mongodb-parent</artifactId>
-		<version>1.10.0.BUILD-SNAPSHOT</version>
+		<version>1.10.0.DATAMONGO-1548-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 

From 80f5c54243a4824c85c3dcfd899c5c1c832ca8af Mon Sep 17 00:00:00 2001
From: Christoph Strobl <cstrobl@pivotal.io>
Date: Tue, 6 Dec 2016 20:15:45 +0100
Subject: [PATCH 2/3] DATAMONGO-1548 - Add support for MongoDB 3.4 aggregation
 operators.

We now support the following MongoDB 3.4 aggregation operators:

$indexOfBytes, $indexOfCP, $split, $strLenBytes, $strLenCP, $substrCP, $indexOfArray, $range, $reverseArray, $reduce, $zip, $in, $isoDayOfWeek, $isoWeek, $isoWeekYear, $switch and $type.
---
 .../aggregation/AggregationExpressions.java   | 1641 ++++++++++++++++-
 .../core/aggregation/ExposedFields.java       |    6 +
 .../data/mongodb/core/aggregation/Fields.java |   25 +-
 .../SpelExpressionTransformer.java            |    6 +-
 .../core/spel/MethodReferenceNode.java        |   18 +
 .../ProjectionOperationUnitTests.java         |  311 ++++
 .../SpelExpressionTransformerUnitTests.java   |  127 +-
 src/main/asciidoc/reference/mongodb.adoc      |   12 +-
 8 files changed, 2054 insertions(+), 92 deletions(-)

diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java
index 2b5e873747..76812cbb31 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java
@@ -23,11 +23,15 @@
 import java.util.List;
 
 import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.data.domain.Range;
 import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder;
 import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder;
 import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder;
 import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Reduce.PropertyExpression;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Switch.CaseOperator;
 import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
+import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
 import org.springframework.data.mongodb.core.query.CriteriaDefinition;
 import org.springframework.util.Assert;
 import org.springframework.util.ClassUtils;
@@ -262,6 +266,30 @@ public static IfNull.ThenBuilder ifNull(AggregationExpression expression) {
 			return IfNull.ifNull(expression);
 		}
 
+		/**
+		 * Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it
+		 * finds an expression which evaluates to true, {@code $switch} executes a specified expression and breaks out of
+		 * the control flow.
+		 *
+		 * @param conditions must not be {@literal null}.
+		 * @return
+		 */
+		public static Switch switchCases(CaseOperator... conditions) {
+			return Switch.switchCases(conditions);
+		}
+
+		/**
+		 * Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it
+		 * finds an expression which evaluates to true, {@code $switch} executes a specified expression and breaks out of
+		 * the control flow.
+		 *
+		 * @param conditions must not be {@literal null}.
+		 * @return
+		 */
+		public static Switch switchCases(List<CaseOperator> conditions) {
+			return Switch.switchCases(conditions);
+		}
+
 		public static class ConditionalOperatorFactory {
 
 			private final String fieldReference;
@@ -1564,6 +1592,184 @@ public StrCaseCmp strCaseCmpValueOf(AggregationExpression expression) {
 			private StrCaseCmp createStrCaseCmp() {
 				return fieldReference != null ? StrCaseCmp.valueOf(fieldReference) : StrCaseCmp.valueOf(expression);
 			}
+
+			/**
+			 * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
+			 * string for an occurence of a given {@literal substring} and returns the UTF-8 byte index (zero-based) of the
+			 * first occurence.
+			 *
+			 * @param substring must not be {@literal null}.
+			 * @return
+			 */
+			public IndexOfBytes indexOf(String substring) {
+
+				Assert.notNull(substring, "Substring must not be null!");
+				return createIndexOfBytesSubstringBuilder().indexOf(substring);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
+			 * string for an occurence of a substring contained in the given {@literal field reference} and returns the UTF-8
+			 * byte index (zero-based) of the first occurence.
+			 *
+			 * @param fieldReference must not be {@literal null}.
+			 * @return
+			 */
+			public IndexOfBytes indexOf(Field fieldReference) {
+
+				Assert.notNull(fieldReference, "FieldReference must not be null!");
+				return createIndexOfBytesSubstringBuilder().indexOf(fieldReference);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
+			 * string for an occurence of a substring resulting from the given {@link AggregationExpression} and returns the
+			 * UTF-8 byte index (zero-based) of the first occurence.
+			 *
+			 * @param expression must not be {@literal null}.
+			 * @return
+			 */
+			public IndexOfBytes indexOf(AggregationExpression expression) {
+
+				Assert.notNull(expression, "Expression must not be null!");
+				return createIndexOfBytesSubstringBuilder().indexOf(expression);
+			}
+
+			private IndexOfBytes.SubstringBuilder createIndexOfBytesSubstringBuilder() {
+				return fieldReference != null ? IndexOfBytes.valueOf(fieldReference) : IndexOfBytes.valueOf(expression);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
+			 * string for an occurence of a given {@literal substring} and returns the UTF-8 code point index (zero-based) of
+			 * the first occurence.
+			 *
+			 * @param substring must not be {@literal null}.
+			 * @return
+			 */
+			public IndexOfCP indexOfCP(String substring) {
+
+				Assert.notNull(substring, "Substring must not be null!");
+				return createIndexOfCPSubstringBuilder().indexOf(substring);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
+			 * string for an occurence of a substring contained in the given {@literal field reference} and returns the UTF-8
+			 * code point index (zero-based) of the first occurence.
+			 *
+			 * @param fieldReference must not be {@literal null}.
+			 * @return
+			 */
+			public IndexOfCP indexOfCP(Field fieldReference) {
+
+				Assert.notNull(fieldReference, "FieldReference must not be null!");
+				return createIndexOfCPSubstringBuilder().indexOf(fieldReference);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
+			 * string for an occurence of a substring resulting from the given {@link AggregationExpression} and returns the
+			 * UTF-8 code point index (zero-based) of the first occurence.
+			 *
+			 * @param expression must not be {@literal null}.
+			 * @return
+			 */
+			public IndexOfCP indexOfCP(AggregationExpression expression) {
+
+				Assert.notNull(expression, "Expression must not be null!");
+				return createIndexOfCPSubstringBuilder().indexOf(expression);
+			}
+
+			private IndexOfCP.SubstringBuilder createIndexOfCPSubstringBuilder() {
+				return fieldReference != null ? IndexOfCP.valueOf(fieldReference) : IndexOfCP.valueOf(expression);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpression} that divides the associated string representation into an array of
+			 * substrings based on the given delimiter.
+			 *
+			 * @param delimiter must not be {@literal null}.
+			 * @return
+			 */
+			public Split split(String delimiter) {
+				return createSplit().split(delimiter);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpression} that divides the associated string representation into an array of
+			 * substrings based on the delimiter resulting from the referenced field..
+			 *
+			 * @param fieldReference must not be {@literal null}.
+			 * @return
+			 */
+			public Split split(Field fieldReference) {
+				return createSplit().split(fieldReference);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpression} that divides the associated string representation into an array of
+			 * substrings based on a delimiter resulting from the given {@link AggregationExpression}.
+			 *
+			 * @param expression must not be {@literal null}.
+			 * @return
+			 */
+			public Split split(AggregationExpression expression) {
+				return createSplit().split(expression);
+			}
+
+			private Split createSplit() {
+				return fieldReference != null ? Split.valueOf(fieldReference) : Split.valueOf(expression);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpression} that returns the number of UTF-8 bytes in the associated string
+			 * representation.
+			 *
+			 * @return
+			 */
+			public StrLenBytes length() {
+				return fieldReference != null ? StrLenBytes.stringLengthOf(fieldReference)
+						: StrLenBytes.stringLengthOf(expression);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpression} that returns the number of UTF-8 code points in the associated string
+			 * representation.
+			 *
+			 * @return
+			 */
+			public StrLenCP lengthCP() {
+				return fieldReference != null ? StrLenCP.stringLengthOfCP(fieldReference)
+						: StrLenCP.stringLengthOfCP(expression);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a
+			 * substring starting at a specified code point index position.
+			 *
+			 * @param codePointStart
+			 * @return
+			 */
+			public SubstrCP substringCP(int codePointStart) {
+				return substringCP(codePointStart, -1);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a
+			 * substring starting at a specified code point index position including the specified number of code points.
+			 *
+			 * @param codePointStart
+			 * @param nrOfCodePoints
+			 * @return
+			 */
+			public SubstrCP substringCP(int codePointStart, int nrOfCodePoints) {
+				return createSubstrCP().substringCP(codePointStart, nrOfCodePoints);
+			}
+
+			private SubstrCP createSubstrCP() {
+				return fieldReference != null ? SubstrCP.valueOf(fieldReference) : SubstrCP.valueOf(expression);
+			}
 		}
 	}
 
@@ -1731,6 +1937,89 @@ public Slice slice() {
 				return usesFieldRef() ? Slice.sliceArrayOf(fieldReference) : Slice.sliceArrayOf(expression);
 			}
 
+			/**
+			 * Creates new {@link AggregationExpressions} that searches the associated array for an occurence of a specified
+			 * value and returns the array index (zero-based) of the first occurence.
+			 *
+			 * @param value must not be {@literal null}.
+			 * @return
+			 */
+			public IndexOfArray indexOf(Object value) {
+				return usesFieldRef() ? IndexOfArray.arrayOf(fieldReference).indexOf(value)
+						: IndexOfArray.arrayOf(expression).indexOf(value);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpressions} that returns an array with the elements in reverse order.
+			 *
+			 * @return
+			 */
+			public ReverseArray reverse() {
+				return usesFieldRef() ? ReverseArray.reverseArrayOf(fieldReference) : ReverseArray.reverseArrayOf(expression);
+			}
+
+			/**
+			 * Start creating new {@link AggregationExpressions} that applies an {@link AggregationExpression} to each element
+			 * in an array and combines them into a single value.
+			 *
+			 * @param expression must not be {@literal null}.
+			 * @return
+			 */
+			public ReduceInitialValueBuilder reduce(final AggregationExpression expression) {
+				return new ReduceInitialValueBuilder() {
+					@Override
+					public Reduce startingWith(Object initialValue) {
+						return (usesFieldRef() ? Reduce.arrayOf(fieldReference) : Reduce.arrayOf(expression))
+								.withInitialValue(initialValue).reduce(expression);
+					}
+				};
+			}
+
+			/**
+			 * Start creating new {@link AggregationExpressions} that applies an {@link AggregationExpression} to each element
+			 * in an array and combines them into a single value.
+			 *
+			 * @param expressions
+			 * @return
+			 */
+			public ReduceInitialValueBuilder reduce(final PropertyExpression... expressions) {
+
+				return new ReduceInitialValueBuilder() {
+					@Override
+					public Reduce startingWith(Object initialValue) {
+						return (usesFieldRef() ? Reduce.arrayOf(fieldReference) : Reduce.arrayOf(expression))
+								.withInitialValue(initialValue).reduce(expressions);
+					}
+				};
+			}
+
+			/**
+			 * Creates new {@link AggregationExpressions} that transposes an array of input arrays so that the first element
+			 * of the output array would be an array containing, the first element of the first input array, the first element
+			 * of the second input array, etc
+			 *
+			 * @param arrays must not be {@literal null}.
+			 * @return
+			 */
+			public Zip zipWith(Object... arrays) {
+				return (usesFieldRef() ? Zip.arrayOf(fieldReference) : Zip.arrayOf(expression)).zip(arrays);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpressions} that returns a boolean indicating whether a specified value is in
+			 * the associcated array.
+			 *
+			 * @param value must not be {@literal null}.
+			 * @return
+			 */
+			public In containsValue(Object value) {
+				return (usesFieldRef() ? In.arrayOf(fieldReference) : In.arrayOf(expression)).containsValue(value);
+			}
+
+			public interface ReduceInitialValueBuilder {
+				Reduce startingWith(Object initialValue);
+			}
+
 			private boolean usesFieldRef() {
 				return fieldReference != null;
 			}
@@ -1952,6 +2241,35 @@ public DateToString toString(String format) {
 						.toString(format);
 			}
 
+			/**
+			 * Creates new {@link AggregationExpressions} that returns the weekday number in ISO 8601 format, ranging from 1
+			 * (for Monday) to 7 (for Sunday).
+			 *
+			 * @return
+			 */
+			public IsoDayOfWeek isoDayOfWeek() {
+				return usesFieldRef() ? IsoDayOfWeek.isoDayOfWeek(fieldReference) : IsoDayOfWeek.isoDayOfWeek(expression);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpressions} that returns the week number in ISO 8601 format, ranging from 1 to
+			 * 53.
+			 *
+			 * @return
+			 */
+			public IsoWeek isoWeek() {
+				return usesFieldRef() ? IsoWeek.isoWeekOf(fieldReference) : IsoWeek.isoWeekOf(expression);
+			}
+
+			/**
+			 * Creates new {@link AggregationExpressions} that returns the year number in ISO 8601 format.
+			 *
+			 * @return
+			 */
+			public IsoWeekYear isoWeekYear() {
+				return usesFieldRef() ? IsoWeekYear.isoWeekYearOf(fieldReference) : IsoWeekYear.isoWeekYearOf(expression);
+			}
+
 			private boolean usesFieldRef() {
 				return fieldReference != null;
 			}
@@ -2072,6 +2390,17 @@ private Object unpack(Object value, AggregationOperationContext context) {
 				return context.getReference((Field) value).toString();
 			}
 
+			if (value instanceof List) {
+
+				List<Object> sourceList = (List<Object>) value;
+				List<Object> mappedList = new ArrayList<Object>(sourceList.size());
+
+				for (Object item : sourceList) {
+					mappedList.add(unpack(item, context));
+				}
+				return mappedList;
+			}
+
 			return value;
 		}
 
@@ -2094,17 +2423,29 @@ protected List<Object> append(Object value) {
 			return Arrays.asList(this.value, value);
 		}
 
-		protected Object append(String key, Object value) {
+		protected java.util.Map<String, Object> append(String key, Object value) {
 
-			if (!(value instanceof java.util.Map)) {
+			if (!(this.value instanceof java.util.Map)) {
 				throw new IllegalArgumentException("o_O");
 			}
-			java.util.Map<String, Object> clone = new LinkedHashMap<String, Object>((java.util.Map<String, Object>) value);
+			java.util.Map<String, Object> clone = new LinkedHashMap<String, Object>(
+					(java.util.Map<String, Object>) this.value);
 			clone.put(key, value);
 			return clone;
 
 		}
 
+		protected List<Object> values() {
+
+			if (value instanceof List) {
+				return new ArrayList<Object>((List) value);
+			}
+			if (value instanceof java.util.Map) {
+				return new ArrayList<Object>(((java.util.Map) value).values());
+			}
+			return new ArrayList<Object>(Arrays.asList(value));
+		}
+
 		protected abstract String getMongoMethod();
 	}
 
@@ -3442,6 +3783,10 @@ public static Trunc truncValueOf(Number value) {
 		}
 	}
 
+	// #########################################
+	// STRING OPERATORS
+	// #########################################
+
 	/**
 	 * {@link AggregationExpression} for {@code $concat}.
 	 *
@@ -3736,117 +4081,453 @@ public StrCaseCmp strcasecmpValueOf(AggregationExpression expression) {
 	}
 
 	/**
-	 * {@link AggregationExpression} for {@code $arrayElementAt}.
+	 * {@link AggregationExpression} for {@code $indexOfBytes}.
 	 *
 	 * @author Christoph Strobl
 	 */
-	class ArrayElemAt extends AbstractAggregationExpression {
+	class IndexOfBytes extends AbstractAggregationExpression {
 
-		private ArrayElemAt(List<?> value) {
+		private IndexOfBytes(List<?> value) {
 			super(value);
 		}
 
 		@Override
 		protected String getMongoMethod() {
-			return "$arrayElemAt";
+			return "$indexOfBytes";
 		}
 
 		/**
-		 * Creates new {@link ArrayElemAt}.
+		 * Start creating a new {@link IndexOfBytes}.
 		 *
 		 * @param fieldReference must not be {@literal null}.
 		 * @return
 		 */
-		public static ArrayElemAt arrayOf(String fieldReference) {
+		public static SubstringBuilder valueOf(String fieldReference) {
 
 			Assert.notNull(fieldReference, "FieldReference must not be null!");
-			return new ArrayElemAt(asFields(fieldReference));
+			return new SubstringBuilder(Fields.field(fieldReference));
 		}
 
 		/**
-		 * Creates new {@link ArrayElemAt}.
+		 * Start creating a new {@link IndexOfBytes}.
 		 *
 		 * @param expression must not be {@literal null}.
 		 * @return
 		 */
-		public static ArrayElemAt arrayOf(AggregationExpression expression) {
+		public static SubstringBuilder valueOf(AggregationExpression expression) {
 
 			Assert.notNull(expression, "Expression must not be null!");
-			return new ArrayElemAt(Collections.singletonList(expression));
+			return new SubstringBuilder(expression);
 		}
 
-		public ArrayElemAt elementAt(int index) {
-			return new ArrayElemAt(append(index));
-		}
+		/**
+		 * Optionally define the substring search start and end position.
+		 *
+		 * @param range must not be {@literal null}.
+		 * @return
+		 */
+		public IndexOfBytes within(Range<Long> range) {
 
-		public ArrayElemAt elementAt(AggregationExpression expression) {
+			Assert.notNull(range, "Range must not be null!");
 
-			Assert.notNull(expression, "Expression must not be null!");
-			return new ArrayElemAt(append(expression));
+			List<Long> rangeValues = new ArrayList<Long>(2);
+			rangeValues.add(range.getLowerBound());
+			if (range.getUpperBound() != null) {
+				rangeValues.add(range.getUpperBound());
+			}
+
+			return new IndexOfBytes(append(rangeValues));
 		}
 
-		public ArrayElemAt elementAt(String arrayFieldReference) {
+		public static class SubstringBuilder {
 
-			Assert.notNull(arrayFieldReference, "ArrayReference must not be null!");
-			return new ArrayElemAt(append(Fields.field(arrayFieldReference)));
+			private final Object stringExpression;
+
+			private SubstringBuilder(Object stringExpression) {
+				this.stringExpression = stringExpression;
+			}
+
+			public IndexOfBytes indexOf(String substring) {
+				return new IndexOfBytes(Arrays.asList(stringExpression, substring));
+			}
+
+			public IndexOfBytes indexOf(AggregationExpression expression) {
+				return new IndexOfBytes(Arrays.asList(stringExpression, expression));
+			}
+
+			public IndexOfBytes indexOf(Field fieldReference) {
+				return new IndexOfBytes(Arrays.asList(stringExpression, fieldReference));
+			}
 		}
 	}
 
 	/**
-	 * {@link AggregationExpression} for {@code $concatArrays}.
+	 * {@link AggregationExpression} for {@code $indexOfCP}.
 	 *
 	 * @author Christoph Strobl
 	 */
-	class ConcatArrays extends AbstractAggregationExpression {
+	class IndexOfCP extends AbstractAggregationExpression {
 
-		private ConcatArrays(List<?> value) {
+		private IndexOfCP(List<?> value) {
 			super(value);
 		}
 
 		@Override
 		protected String getMongoMethod() {
-			return "$concatArrays";
+			return "$indexOfCP";
 		}
 
 		/**
-		 * Creates new {@link ConcatArrays}.
+		 * Start creating a new {@link IndexOfCP}.
 		 *
 		 * @param fieldReference must not be {@literal null}.
 		 * @return
 		 */
-		public static ConcatArrays arrayOf(String fieldReference) {
+		public static SubstringBuilder valueOf(String fieldReference) {
 
 			Assert.notNull(fieldReference, "FieldReference must not be null!");
-			return new ConcatArrays(asFields(fieldReference));
+			return new SubstringBuilder(Fields.field(fieldReference));
 		}
 
 		/**
-		 * Creates new {@link ConcatArrays}.
+		 * Start creating a new {@link IndexOfCP}.
 		 *
 		 * @param expression must not be {@literal null}.
 		 * @return
 		 */
-		public static ConcatArrays arrayOf(AggregationExpression expression) {
+		public static SubstringBuilder valueOf(AggregationExpression expression) {
 
 			Assert.notNull(expression, "Expression must not be null!");
-			return new ConcatArrays(Collections.singletonList(expression));
+			return new SubstringBuilder(expression);
 		}
 
-		public ConcatArrays concat(String arrayFieldReference) {
+		/**
+		 * Optionally define the substring search start and end position.
+		 *
+		 * @param range must not be {@literal null}.
+		 * @return
+		 */
+		public IndexOfCP within(Range<Long> range) {
 
-			Assert.notNull(arrayFieldReference, "ArrayFieldReference must not be null!");
-			return new ConcatArrays(append(Fields.field(arrayFieldReference)));
+			Assert.notNull(range, "Range must not be null!");
+
+			List<Long> rangeValues = new ArrayList<Long>(2);
+			rangeValues.add(range.getLowerBound());
+			if (range.getUpperBound() != null) {
+				rangeValues.add(range.getUpperBound());
+			}
+
+			return new IndexOfCP(append(rangeValues));
 		}
 
-		public ConcatArrays concat(AggregationExpression expression) {
+		public static class SubstringBuilder {
 
-			Assert.notNull(expression, "Expression must not be null!");
-			return new ConcatArrays(append(expression));
+			private final Object stringExpression;
+
+			private SubstringBuilder(Object stringExpression) {
+				this.stringExpression = stringExpression;
+			}
+
+			public IndexOfCP indexOf(String substring) {
+				return new IndexOfCP(Arrays.asList(stringExpression, substring));
+			}
+
+			public IndexOfCP indexOf(AggregationExpression expression) {
+				return new IndexOfCP(Arrays.asList(stringExpression, expression));
+			}
+
+			public IndexOfCP indexOf(Field fieldReference) {
+				return new IndexOfCP(Arrays.asList(stringExpression, fieldReference));
+			}
 		}
 	}
 
 	/**
-	 * {@code $filter} {@link AggregationExpression} allows to select a subset of the array to return based on the
+	 * {@link AggregationExpression} for {@code $split}.
+	 */
+	class Split extends AbstractAggregationExpression {
+
+		private Split(List<?> values) {
+			super(values);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$split";
+		}
+
+		/**
+		 * Start creating a new {@link Split}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
+		public static Split valueOf(String fieldReference) {
+
+			Assert.notNull(fieldReference, "FieldReference must not be null!");
+			return new Split(asFields(fieldReference));
+		}
+
+		/**
+		 * Start creating a new {@link Split}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
+		public static Split valueOf(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
+			return new Split(Collections.singletonList(expression));
+		}
+
+		/**
+		 * Use given {@link String} as deliminator
+		 *
+		 * @param deliminator must not be {@literal null}.
+		 * @return
+		 */
+		public Split split(String deliminator) {
+
+			Assert.notNull(deliminator, "Deliminator must not be null!");
+			return new Split(append(deliminator));
+		}
+
+		/**
+		 * Usge value of referenced field as deliminator.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
+		public Split split(Field fieldReference) {
+
+			Assert.notNull(fieldReference, "FieldReference must not be null!");
+			return new Split(append(fieldReference));
+		}
+
+		/**
+		 * Use value resulting from {@link AggregationExpression} as deliminator.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
+		public Split split(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
+			return new Split(append(expression));
+		}
+	}
+
+	/**
+	 * {@link AggregationExpression} for {@code $strLenBytes}.
+	 */
+	class StrLenBytes extends AbstractAggregationExpression {
+
+		private StrLenBytes(Object value) {
+			super(value);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$strLenBytes";
+		}
+
+		public static StrLenBytes stringLengthOf(String fieldReference) {
+			return new StrLenBytes(Fields.field(fieldReference));
+		}
+
+		public static StrLenBytes stringLengthOf(AggregationExpression expression) {
+			return new StrLenBytes(expression);
+		}
+	}
+
+	/**
+	 * {@link AggregationExpression} for {@code $strLenCP}.
+	 */
+	class StrLenCP extends AbstractAggregationExpression {
+
+		private StrLenCP(Object value) {
+			super(value);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$strLenCP";
+		}
+
+		public static StrLenCP stringLengthOfCP(String fieldReference) {
+			return new StrLenCP(Fields.field(fieldReference));
+		}
+
+		public static StrLenCP stringLengthOfCP(AggregationExpression expression) {
+			return new StrLenCP(expression);
+		}
+	}
+
+	/**
+	 * {@link AggregationExpression} for {@code $substrCP}.
+	 *
+	 * @author Christoph Strobl
+	 */
+	class SubstrCP extends AbstractAggregationExpression {
+
+		private SubstrCP(List<?> value) {
+			super(value);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$substrCP";
+		}
+
+		/**
+		 * Creates new {@link SubstrCP}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
+		public static SubstrCP valueOf(String fieldReference) {
+
+			Assert.notNull(fieldReference, "FieldReference must not be null!");
+			return new SubstrCP(asFields(fieldReference));
+		}
+
+		/**
+		 * Creates new {@link SubstrCP}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
+		public static SubstrCP valueOf(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
+			return new SubstrCP(Collections.singletonList(expression));
+		}
+
+		public SubstrCP substringCP(int start) {
+			return substringCP(start, -1);
+		}
+
+		public SubstrCP substringCP(int start, int nrOfChars) {
+			return new SubstrCP(append(Arrays.asList(start, nrOfChars)));
+		}
+	}
+
+	// #########################################
+	// ARRAY OPERATORS
+	// #########################################
+
+	/**
+	 * {@link AggregationExpression} for {@code $arrayElementAt}.
+	 *
+	 * @author Christoph Strobl
+	 */
+	class ArrayElemAt extends AbstractAggregationExpression {
+
+		private ArrayElemAt(List<?> value) {
+			super(value);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$arrayElemAt";
+		}
+
+		/**
+		 * Creates new {@link ArrayElemAt}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
+		public static ArrayElemAt arrayOf(String fieldReference) {
+
+			Assert.notNull(fieldReference, "FieldReference must not be null!");
+			return new ArrayElemAt(asFields(fieldReference));
+		}
+
+		/**
+		 * Creates new {@link ArrayElemAt}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
+		public static ArrayElemAt arrayOf(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
+			return new ArrayElemAt(Collections.singletonList(expression));
+		}
+
+		public ArrayElemAt elementAt(int index) {
+			return new ArrayElemAt(append(index));
+		}
+
+		public ArrayElemAt elementAt(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
+			return new ArrayElemAt(append(expression));
+		}
+
+		public ArrayElemAt elementAt(String arrayFieldReference) {
+
+			Assert.notNull(arrayFieldReference, "ArrayReference must not be null!");
+			return new ArrayElemAt(append(Fields.field(arrayFieldReference)));
+		}
+	}
+
+	/**
+	 * {@link AggregationExpression} for {@code $concatArrays}.
+	 *
+	 * @author Christoph Strobl
+	 */
+	class ConcatArrays extends AbstractAggregationExpression {
+
+		private ConcatArrays(List<?> value) {
+			super(value);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$concatArrays";
+		}
+
+		/**
+		 * Creates new {@link ConcatArrays}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
+		public static ConcatArrays arrayOf(String fieldReference) {
+
+			Assert.notNull(fieldReference, "FieldReference must not be null!");
+			return new ConcatArrays(asFields(fieldReference));
+		}
+
+		/**
+		 * Creates new {@link ConcatArrays}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
+		public static ConcatArrays arrayOf(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
+			return new ConcatArrays(Collections.singletonList(expression));
+		}
+
+		public ConcatArrays concat(String arrayFieldReference) {
+
+			Assert.notNull(arrayFieldReference, "ArrayFieldReference must not be null!");
+			return new ConcatArrays(append(Fields.field(arrayFieldReference)));
+		}
+
+		public ConcatArrays concat(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
+			return new ConcatArrays(append(expression));
+		}
+	}
+
+	/**
+	 * {@code $filter} {@link AggregationExpression} allows to select a subset of the array to return based on the
 	 * specified condition.
 	 *
 	 * @author Christoph Strobl
@@ -4127,112 +4808,678 @@ public static IsArray isArray(String fieldReference) {
 		 */
 		public static IsArray isArray(AggregationExpression expression) {
 
-			Assert.notNull(expression, "Expression must not be null!");
-			return new IsArray(expression);
+			Assert.notNull(expression, "Expression must not be null!");
+			return new IsArray(expression);
+		}
+	}
+
+	/**
+	 * {@link AggregationExpression} for {@code $size}.
+	 *
+	 * @author Christoph Strobl
+	 */
+	class Size extends AbstractAggregationExpression {
+
+		private Size(Object value) {
+			super(value);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$size";
+		}
+
+		/**
+		 * Creates new {@link Size}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
+		public static Size lengthOfArray(String fieldReference) {
+
+			Assert.notNull(fieldReference, "FieldReference must not be null!");
+			return new Size(Fields.field(fieldReference));
+		}
+
+		/**
+		 * Creates new {@link Size}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
+		public static Size lengthOfArray(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
+			return new Size(expression);
+		}
+	}
+
+	/**
+	 * {@link AggregationExpression} for {@code $slice}.
+	 *
+	 * @author Christoph Strobl
+	 */
+	class Slice extends AbstractAggregationExpression {
+
+		private Slice(List<?> value) {
+			super(value);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$slice";
+		}
+
+		/**
+		 * Creates new {@link Slice}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
+		public static Slice sliceArrayOf(String fieldReference) {
+
+			Assert.notNull(fieldReference, "FieldReference must not be null!");
+			return new Slice(asFields(fieldReference));
+		}
+
+		/**
+		 * Creates new {@link Slice}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
+		public static Slice sliceArrayOf(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
+			return new Slice(Collections.singletonList(expression));
+		}
+
+		public Slice itemCount(int nrElements) {
+			return new Slice(append(nrElements));
+		}
+
+		public SliceElementsBuilder offset(final int position) {
+
+			return new SliceElementsBuilder() {
+
+				@Override
+				public Slice itemCount(int nrElements) {
+					return new Slice(append(position)).itemCount(nrElements);
+				}
+			};
+		}
+
+		public interface SliceElementsBuilder {
+			Slice itemCount(int nrElements);
+		}
+	}
+
+	/**
+	 * {@link AggregationExpression} for {@code $indexOfArray}.
+	 *
+	 * @author Christoph Strobl
+	 */
+	class IndexOfArray extends AbstractAggregationExpression {
+
+		private IndexOfArray(List<Object> value) {
+			super(value);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$indexOfArray";
+		}
+
+		/**
+		 * Start creating new {@link IndexOfArray}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
+		public static IndexOfArrayBuilder arrayOf(String fieldReference) {
+
+			Assert.notNull(fieldReference, "FieldReference must not be null!");
+			return new IndexOfArrayBuilder(Fields.field(fieldReference));
+		}
+
+		/**
+		 * Start creating new {@link IndexOfArray}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
+		public static IndexOfArrayBuilder arrayOf(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
+			return new IndexOfArrayBuilder(expression);
+		}
+
+		public IndexOfArray within(Range<Long> range) {
+
+			Assert.notNull(range, "Range must not be null!");
+
+			List<Long> rangeValues = new ArrayList<Long>(2);
+			rangeValues.add(range.getLowerBound());
+			if (range.getUpperBound() != null) {
+				rangeValues.add(range.getUpperBound());
+			}
+
+			return new IndexOfArray(append(rangeValues));
+		}
+
+		public static class IndexOfArrayBuilder {
+
+			private final Object targetArray;
+
+			private IndexOfArrayBuilder(Object targetArray) {
+				this.targetArray = targetArray;
+			}
+
+			public IndexOfArray indexOf(Object value) {
+
+				Assert.notNull(value, "Value must not be null!");
+				return new IndexOfArray(Arrays.asList(targetArray, value));
+			}
+		}
+	}
+
+	/**
+	 * {@link AggregationExpression} for {@code $range}.
+	 *
+	 * @author Christoph Strobl
+	 */
+	class RangeOperator extends AbstractAggregationExpression {
+
+		private RangeOperator(List<Object> values) {
+			super(values);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$range";
+		}
+
+		public static RangeOperatorBuilder rangeStartingAt(String fieldReference) {
+			return new RangeOperatorBuilder(Fields.field(fieldReference));
+		}
+
+		public static RangeOperatorBuilder rangeStartingAt(AggregationExpression expression) {
+			return new RangeOperatorBuilder(expression);
+		}
+
+		public static RangeOperatorBuilder rangeStartingAt(Long value) {
+			return new RangeOperatorBuilder(value);
+		}
+
+		public RangeOperator withStepSize(Long stepSize) {
+			return new RangeOperator(append(stepSize));
+		}
+
+		public static class RangeOperatorBuilder {
+
+			private final Object startPoint;
+
+			private RangeOperatorBuilder(Object startPoint) {
+				this.startPoint = startPoint;
+			}
+
+			public RangeOperator to(Long index) {
+				return new RangeOperator(Arrays.asList(startPoint, index));
+			}
+
+			public RangeOperator to(AggregationExpression expression) {
+				return new RangeOperator(Arrays.asList(startPoint, expression));
+			}
+
+			public RangeOperator to(String fieldReference) {
+				return new RangeOperator(Arrays.asList(startPoint, Fields.field(fieldReference)));
+			}
+		}
+
+	}
+
+	/**
+	 * {@link AggregationExpression} for {@code $reverseArray}.
+	 */
+	class ReverseArray extends AbstractAggregationExpression {
+
+		private ReverseArray(Object value) {
+			super(value);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$reverseArray";
+		}
+
+		public static ReverseArray reverseArrayOf(String fieldReference) {
+			return new ReverseArray(Fields.field(fieldReference));
+		}
+
+		public static ReverseArray reverseArrayOf(AggregationExpression expression) {
+			return new ReverseArray(expression);
+		}
+	}
+
+	/**
+	 * {@link AggregationExpression} for {@code $reduce}.
+	 */
+	class Reduce implements AggregationExpression {
+
+		private final Object input;
+		private final Object initialValue;
+		private final List<AggregationExpression> reduceExpressions;
+
+		private Reduce(Object input, Object initialValue, List<AggregationExpression> reduceExpressions) {
+			this.input = input;
+			this.initialValue = initialValue;
+			this.reduceExpressions = reduceExpressions;
+		}
+
+		@Override
+		public DBObject toDbObject(AggregationOperationContext context) {
+
+			DBObject dbo = new BasicDBObject();
+
+			dbo.put("input", getMappedValue(input, context));
+			dbo.put("initialValue", getMappedValue(initialValue, context));
+
+			if (reduceExpressions.iterator().next() instanceof PropertyExpression) {
+
+				DBObject properties = new BasicDBObject();
+				for (AggregationExpression e : reduceExpressions) {
+					properties.putAll(e.toDbObject(context));
+				}
+				dbo.put("in", properties);
+			} else {
+				dbo.put("in", (reduceExpressions.iterator().next()).toDbObject(context));
+			}
+
+			return new BasicDBObject("$reduce", dbo);
+		}
+
+		private Object getMappedValue(Object value, AggregationOperationContext context) {
+
+			if (value instanceof DBObject) {
+				return value;
+			}
+			if (value instanceof AggregationExpression) {
+				return ((AggregationExpression) value).toDbObject(context);
+			} else if (value instanceof Field) {
+				return context.getReference(((Field) value)).toString();
+			} else {
+				return context.getMappedObject(new BasicDBObject("###val###", value)).get("###val###");
+			}
+		}
+
+		public static InitialValueBuilder arrayOf(final String fieldReference) {
+			return new InitialValueBuilder() {
+
+				@Override
+				public ReduceBuilder withInitialValue(final Object initialValue) {
+					return new ReduceBuilder() {
+						@Override
+						public Reduce reduce(AggregationExpression expression) {
+							return new Reduce(Fields.field(fieldReference), initialValue, Collections.singletonList(expression));
+						}
+
+						@Override
+						public Reduce reduce(PropertyExpression... expressions) {
+							return new Reduce(Fields.field(fieldReference), initialValue,
+									Arrays.<AggregationExpression> asList(expressions));
+						}
+					};
+				}
+			};
+		}
+
+		public static InitialValueBuilder arrayOf(final AggregationExpression expression) {
+			return new InitialValueBuilder() {
+
+				@Override
+				public ReduceBuilder withInitialValue(final Object initialValue) {
+					return new ReduceBuilder() {
+						@Override
+						public Reduce reduce(AggregationExpression expression) {
+							return new Reduce(expression, initialValue, Collections.singletonList(expression));
+						}
+
+						@Override
+						public Reduce reduce(PropertyExpression... expressions) {
+							return new Reduce(expression, initialValue, Arrays.<AggregationExpression> asList(expressions));
+						}
+					};
+				}
+			};
+		}
+
+		public interface InitialValueBuilder {
+
+			/**
+			 * Define the initial cumulative value set before in is applied to the first element of the input array.
+			 *
+			 * @param intialValue must not be {@literal null}.
+			 * @return
+			 */
+			ReduceBuilder withInitialValue(Object intialValue);
+		}
+
+		public interface ReduceBuilder {
+
+			/**
+			 * Define the {@link AggregationExpression} to apply to each element in the input array in left-to-right order.
+			 * <br />
+			 * <b>NOTE:</b> During evaulation of the in expression the variable references {@link Variable#THIS} and
+			 * {@link Variable#VALUE} are availble.
+			 *
+			 * @param expression must not be {@literal null}.
+			 * @return
+			 */
+			Reduce reduce(AggregationExpression expression);
+
+			/**
+			 * Define the {@link PropertyExpression}s to apply to each element in the input array in left-to-right order.
+			 * <br />
+			 * <b>NOTE:</b> During evaulation of the in expression the variable references {@link Variable#THIS} and
+			 * {@link Variable#VALUE} are availble.
+			 *
+			 * @param expression must not be {@literal null}.
+			 * @return
+			 */
+			Reduce reduce(PropertyExpression... expressions);
+		}
+
+		/**
+		 * @author Christoph Strobl
+		 */
+		public static class PropertyExpression implements AggregationExpression {
+
+			private final String propertyName;
+			private final AggregationExpression aggregationExpression;
+
+			public PropertyExpression(String propertyName, AggregationExpression aggregationExpression) {
+				this.propertyName = propertyName;
+				this.aggregationExpression = aggregationExpression;
+			}
+
+			/**
+			 * Define a result property for an {@link AggregationExpression} used in {@link Reduce}.
+			 *
+			 * @param name must not be {@literal null}.
+			 * @return
+			 */
+			public static AsBuilder property(final String name) {
+				return new AsBuilder() {
+					@Override
+					public PropertyExpression definedAs(AggregationExpression expression) {
+						return new PropertyExpression(name, expression);
+					}
+				};
+			}
+
+			@Override
+			public DBObject toDbObject(AggregationOperationContext context) {
+				return new BasicDBObject(propertyName, aggregationExpression.toDbObject(context));
+			}
+
+			interface AsBuilder {
+
+				/**
+				 * Set the {@link AggregationExpression} resulting in the properties value.
+				 *
+				 * @param expression must not be {@literal null}.
+				 * @return
+				 */
+				PropertyExpression definedAs(AggregationExpression expression);
+			}
+		}
+
+		public enum Variable implements Field {
+			THIS {
+				@Override
+				public String getName() {
+					return "$$this";
+				}
+
+				@Override
+				public String getTarget() {
+					return "$$this";
+				}
+
+				@Override
+				public boolean isAliased() {
+					return false;
+				}
+
+				@Override
+				public String toString() {
+					return getName();
+				}
+			},
+			VALUE {
+				@Override
+				public String getName() {
+					return "$$value";
+				}
+
+				@Override
+				public String getTarget() {
+					return "$$value";
+				}
+
+				@Override
+				public boolean isAliased() {
+					return false;
+				}
+
+				@Override
+				public String toString() {
+					return getName();
+				}
+			};
+
+			/**
+			 * Create a {@link Field} reference to a given {@literal property} prefixed with the {@link Variable} identifier.
+			 * eg. {@code $$value.product}
+			 *
+			 * @param property must not be {@literal null}.
+			 * @return
+			 */
+			public Field referingTo(final String property) {
+
+				return new Field() {
+					@Override
+					public String getName() {
+						return Variable.this.getName() + "." + property;
+					}
+
+					@Override
+					public String getTarget() {
+						return Variable.this.getTarget() + "." + property;
+					}
+
+					@Override
+					public boolean isAliased() {
+						return false;
+					}
+
+					@Override
+					public String toString() {
+						return getName();
+					}
+				};
+			}
 		}
 	}
 
 	/**
-	 * {@link AggregationExpression} for {@code $size}.
-	 *
-	 * @author Christoph Strobl
+	 * {@link AggregationExpression} for {@code $zip}.
 	 */
-	class Size extends AbstractAggregationExpression {
+	class Zip extends AbstractAggregationExpression {
 
-		private Size(Object value) {
+		protected Zip(java.util.Map<String, Object> value) {
 			super(value);
 		}
 
 		@Override
 		protected String getMongoMethod() {
-			return "$size";
+			return "$zip";
 		}
 
 		/**
-		 * Creates new {@link Size}.
+		 * Start creating new {@link Zip}.
 		 *
 		 * @param fieldReference must not be {@literal null}.
 		 * @return
 		 */
-		public static Size lengthOfArray(String fieldReference) {
+		public static ZipBuilder arrayOf(String fieldReference) {
 
 			Assert.notNull(fieldReference, "FieldReference must not be null!");
-			return new Size(Fields.field(fieldReference));
+			return new ZipBuilder(Fields.field(fieldReference));
 		}
 
 		/**
-		 * Creates new {@link Size}.
+		 * Start creating new {@link Zip}.
 		 *
 		 * @param expression must not be {@literal null}.
 		 * @return
 		 */
-		public static Size lengthOfArray(AggregationExpression expression) {
+		public static ZipBuilder arrayOf(AggregationExpression expression) {
 
 			Assert.notNull(expression, "Expression must not be null!");
-			return new Size(expression);
-		}
-	}
-
-	/**
-	 * {@link AggregationExpression} for {@code $slice}.
-	 *
-	 * @author Christoph Strobl
-	 */
-	class Slice extends AbstractAggregationExpression {
-
-		private Slice(List<?> value) {
-			super(value);
+			return new ZipBuilder(expression);
 		}
 
-		@Override
-		protected String getMongoMethod() {
-			return "$slice";
+		/**
+		 * Create new {@link Zip} and set the {@code useLongestLength} property to {@literal true}.
+		 *
+		 * @return
+		 */
+		public Zip useLongestLength() {
+			return new Zip(append("useLongestLength", true));
 		}
 
 		/**
-		 * Creates new {@link Slice}.
+		 * Optionally provide a default value.
 		 *
 		 * @param fieldReference must not be {@literal null}.
 		 * @return
 		 */
-		public static Slice sliceArrayOf(String fieldReference) {
+		public Zip defaultTo(String fieldReference) {
 
 			Assert.notNull(fieldReference, "FieldReference must not be null!");
-			return new Slice(asFields(fieldReference));
+			return new Zip(append("defaults", Fields.field(fieldReference)));
 		}
 
 		/**
-		 * Creates new {@link Slice}.
+		 * Optionally provide a default value.
 		 *
 		 * @param expression must not be {@literal null}.
 		 * @return
 		 */
-		public static Slice sliceArrayOf(AggregationExpression expression) {
+		public Zip defaultTo(AggregationExpression expression) {
 
 			Assert.notNull(expression, "Expression must not be null!");
-			return new Slice(Collections.singletonList(expression));
+			return new Zip(append("defaults", expression));
 		}
 
-		public Slice itemCount(int nrElements) {
-			return new Slice(append(nrElements));
+		/**
+		 * Optionally provide a default value.
+		 *
+		 * @param array must not be {@literal null}.
+		 * @return
+		 */
+		public Zip defaultTo(Object[] array) {
+
+			Assert.notNull(array, "Array must not be null!");
+			return new Zip(append("defaults", array));
 		}
 
-		public SliceElementsBuilder offset(final int position) {
+		public static class ZipBuilder {
 
-			return new SliceElementsBuilder() {
+			private final List<Object> sourceArrays;
+
+			public ZipBuilder(Object sourceArray) {
+
+				this.sourceArrays = new ArrayList<Object>();
+				this.sourceArrays.add(sourceArray);
+			}
+
+			/**
+			 * Creates new {@link Zip} that transposes an array of input arrays so that the first element of the output array
+			 * would be an array containing, the first element of the first input array, the first element of the second input
+			 * array, etc
+			 *
+			 * @param arrays arrays to zip the referenced one with. must not be {@literal null}.
+			 * @return
+			 */
+			public Zip zip(Object... arrays) {
+
+				Assert.notNull(arrays, "Arrays must not be null!");
+				for (Object value : arrays) {
+
+					if (value instanceof String) {
+						sourceArrays.add(Fields.field((String) value));
+					} else {
+						sourceArrays.add(value);
+					}
+				}
+
+				return new Zip(Collections.<String, Object> singletonMap("inputs", sourceArrays));
+			}
+		}
+	}
+
+	/**
+	 * {@link AggregationExpression} for {@code $in}.
+	 */
+	class In extends AbstractAggregationExpression {
+
+		private In(List<Object> values) {
+			super(values);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$in";
+		}
 
+		public static InBuilder arrayOf(final String fieldReference) {
+
+			Assert.notNull(fieldReference, "FieldReference must not be null!");
+			return new InBuilder() {
 				@Override
-				public Slice itemCount(int nrElements) {
-					return new Slice(append(position)).itemCount(nrElements);
+				public In containsValue(Object value) {
+
+					Assert.notNull(value, "Value must not be null!");
+					return new In(Arrays.asList(value, Fields.field(fieldReference)));
 				}
 			};
 		}
 
-		public interface SliceElementsBuilder {
-			Slice itemCount(int nrElements);
+		public static InBuilder arrayOf(final AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
+			return new InBuilder() {
+				@Override
+				public In containsValue(Object value) {
+
+					Assert.notNull(value, "Value must not be null!");
+					return new In(Arrays.asList(value, expression));
+				}
+			};
+		}
+
+		public interface InBuilder {
+			In containsValue(Object value);
 		}
+
 	}
 
+	// ############
+	// LITERAL OPERATORS
+	// ############
+
 	/**
 	 * {@link AggregationExpression} for {@code $literal}.
 	 *
@@ -4747,6 +5994,129 @@ public interface FormatBuilder {
 		}
 	}
 
+	/**
+	 * {@link AggregationExpression} for {@code $isoDayOfWeek}.
+	 *
+	 * @author Christoph Strobl
+	 */
+	class IsoDayOfWeek extends AbstractAggregationExpression {
+
+		private IsoDayOfWeek(Object value) {
+			super(value);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$isoDayOfWeek";
+		}
+
+		/**
+		 * Creates new {@link IsoDayOfWeek}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
+		public static IsoDayOfWeek isoDayOfWeek(String fieldReference) {
+
+			Assert.notNull(fieldReference, "FieldReference must not be null!");
+			return new IsoDayOfWeek(Fields.field(fieldReference));
+		}
+
+		/**
+		 * Creates new {@link IsoDayOfWeek}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
+		public static IsoDayOfWeek isoDayOfWeek(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
+			return new IsoDayOfWeek(expression);
+		}
+	}
+
+	/**
+	 * {@link AggregationExpression} for {@code $isoWeek}.
+	 *
+	 * @author Christoph Strobl
+	 */
+	class IsoWeek extends AbstractAggregationExpression {
+
+		private IsoWeek(Object value) {
+			super(value);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$isoWeek";
+		}
+
+		/**
+		 * Creates new {@link IsoWeek}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
+		public static IsoWeek isoWeekOf(String fieldReference) {
+
+			Assert.notNull(fieldReference, "FieldReference must not be null!");
+			return new IsoWeek(Fields.field(fieldReference));
+		}
+
+		/**
+		 * Creates new {@link IsoWeek}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
+		public static IsoWeek isoWeekOf(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
+			return new IsoWeek(expression);
+		}
+	}
+
+	/**
+	 * {@link AggregationExpression} for {@code $isoWeekYear}.
+	 *
+	 * @author Christoph Strobl
+	 */
+	class IsoWeekYear extends AbstractAggregationExpression {
+
+		private IsoWeekYear(Object value) {
+			super(value);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$isoWeekYear";
+		}
+
+		/**
+		 * Creates new {@link IsoWeekYear}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
+		public static IsoWeekYear isoWeekYearOf(String fieldReference) {
+
+			Assert.notNull(fieldReference, "FieldReference must not be null!");
+			return new IsoWeekYear(Fields.field(fieldReference));
+		}
+
+		/**
+		 * Creates new {@link Millisecond}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
+		public static IsoWeekYear isoWeekYearOf(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
+			return new IsoWeekYear(expression);
+		}
+	}
+
 	/**
 	 * {@link AggregationExpression} for {@code $sum}.
 	 *
@@ -6864,4 +8234,111 @@ public ExpressionVariable forExpression(DBObject expressionObject) {
 			}
 		}
 	}
+
+
+	/**
+	 * {@link AggregationExpression} for {@code $switch}.
+	 *
+	 * @author Christoph Strobl
+	 */
+	class Switch extends AbstractAggregationExpression {
+
+		private Switch(java.util.Map<String, Object> values) {
+			super(values);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$switch";
+		}
+
+		public static Switch switchCases(CaseOperator... conditions) {
+
+			Assert.notNull(conditions, "Conditions must not be null!");
+			return switchCases(Arrays.asList(conditions));
+		}
+
+		public static Switch switchCases(List<CaseOperator> conditions) {
+
+			Assert.notNull(conditions, "Conditions must not be null!");
+			return new Switch(Collections.<String, Object> singletonMap("branches", new ArrayList<CaseOperator>(conditions)));
+		}
+
+		public Switch defaultTo(Object value) {
+			return new Switch(append("default", value));
+		}
+
+		public static class CaseOperator implements AggregationExpression {
+
+			private final AggregationExpression when;
+			private final Object then;
+
+			private CaseOperator(AggregationExpression when, Object then) {
+
+				this.when = when;
+				this.then = then;
+			}
+
+			public static ThenBuilder when(final AggregationExpression condition) {
+
+				Assert.notNull(condition, "Condition must not be null!");
+				return new ThenBuilder() {
+					@Override
+					public CaseOperator then(Object value) {
+
+						Assert.notNull(value, "Value must not be null!");
+						return new CaseOperator(condition, value);
+					}
+				};
+			}
+
+			@Override
+			public DBObject toDbObject(AggregationOperationContext context) {
+				DBObject dbo = new BasicDBObject("case", when.toDbObject(context));
+
+				if (then instanceof AggregationExpression) {
+					dbo.put("then", ((AggregationExpression) then).toDbObject(context));
+				} else if (then instanceof Field) {
+					dbo.put("then", context.getReference((Field) then).toString());
+				} else {
+					dbo.put("then", then);
+				}
+
+				return dbo;
+			}
+
+			public interface ThenBuilder {
+				CaseOperator then(Object value);
+			}
+		}
+	}
+
+	/**
+	 * {@link AggregationExpression} for {@code $type}.
+	 *
+	 * @author Christoph Strobl
+	 */
+	class Type extends AbstractAggregationExpression {
+
+		private Type(Object value) {
+			super(value);
+		}
+
+		@Override
+		protected String getMongoMethod() {
+			return "$type";
+		}
+
+		/**
+		 * Creates new {@link Type}.
+		 *
+		 * @param field must not be {@literal null}.
+		 * @return
+		 */
+		public static Type typeOf(String field) {
+
+			Assert.notNull(field, "Field must not be null!");
+			return new Type(Fields.field(field));
+		}
+	}
 }
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java
index f9033743db..ce51f40624 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java
@@ -22,6 +22,7 @@
 import java.util.List;
 
 import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
+import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField;
 import org.springframework.util.Assert;
 import org.springframework.util.CompositeIterator;
 import org.springframework.util.ObjectUtils;
@@ -406,6 +407,11 @@ public Object getReferenceValue() {
 		 */
 		@Override
 		public String toString() {
+
+			if(getRaw().startsWith("$")) {
+				return getRaw();
+			}
+
 			return String.format("$%s", getRaw());
 		}
 
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java
index 183d526520..2ba33412a4 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java
@@ -187,13 +187,13 @@ public Iterator<Field> iterator() {
 	}
 
 	/**
-	 *
 	 * @return
 	 * @since 1.10
 	 */
 	public List<Field> asList() {
 		return Collections.unmodifiableList(fields);
 	}
+
 	/**
 	 * Value object to encapsulate a field in an aggregation operation.
 	 * 
@@ -201,6 +201,7 @@ public List<Field> asList() {
 	 */
 	static class AggregationField implements Field {
 
+		private final String raw;
 		private final String name;
 		private final String target;
 
@@ -225,6 +226,7 @@ public AggregationField(String name) {
 		 */
 		public AggregationField(String name, String target) {
 
+			raw = name;
 			String nameToSet = cleanUp(name);
 			String targetToSet = cleanUp(target);
 
@@ -266,6 +268,11 @@ public String getName() {
 		 * @see org.springframework.data.mongodb.core.aggregation.Field#getAlias()
 		 */
 		public String getTarget() {
+
+			if (isLocalVar()) {
+				return this.getRaw();
+			}
+
 			return StringUtils.hasText(this.target) ? this.target : this.name;
 		}
 
@@ -278,6 +285,22 @@ public boolean isAliased() {
 			return !getName().equals(getTarget());
 		}
 
+		/**
+		 * @return {@literal true} in case the field name starts with {@code $$}.
+		 * @since 1.10
+		 */
+		public boolean isLocalVar() {
+			return raw.startsWith("$$") && !raw.startsWith("$$$");
+		}
+
+		/**
+		 * @return
+		 * @since 1.10
+		 */
+		public String getRaw() {
+			return raw;
+		}
+
 		/* 
 		 * (non-Javadoc)
 		 * @see java.lang.Object#toString()
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java
index d381020b5c..587f0b327e 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java
@@ -491,8 +491,10 @@ protected Object convert(AggregationExpressionTransformationContext<MethodRefere
 			} else if (ObjectUtils.nullSafeEquals(methodReference.getArgumentType(), ArgumentType.MAP)) {
 
 				DBObject dbo = new BasicDBObject();
-				for (int i = 0; i < methodReference.getArgumentMap().length; i++) {
-					dbo.put(methodReference.getArgumentMap()[i], transform(node.getChild(i), context));
+
+				int i = 0;
+				for(ExpressionNode child : node) {
+					dbo.put(methodReference.getArgumentMap()[i++], transform(child, context));
 				}
 				args = dbo;
 			} else {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java
index 18ffc5a441..fcdd5c15c8 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java
@@ -90,6 +90,12 @@ public class MethodReferenceNode extends ExpressionNode {
 		map.put("toLower", singleArgumentAggregationMethodReference().forOperator("$toLower"));
 		map.put("toUpper", singleArgumentAggregationMethodReference().forOperator("$toUpper"));
 		map.put("strcasecmp", arrayArgumentAggregationMethodReference().forOperator("$strcasecmp"));
+		map.put("indexOfBytes", arrayArgumentAggregationMethodReference().forOperator("$indexOfBytes"));
+		map.put("indexOfCP", arrayArgumentAggregationMethodReference().forOperator("$indexOfCP"));
+		map.put("split", arrayArgumentAggregationMethodReference().forOperator("$split"));
+		map.put("strLenBytes", singleArgumentAggregationMethodReference().forOperator("$strLenBytes"));
+		map.put("strLenCP", singleArgumentAggregationMethodReference().forOperator("$strLenCP"));
+		map.put("substrCP", arrayArgumentAggregationMethodReference().forOperator("$substrCP"));
 
 		// TEXT SEARCH OPERATORS
 		map.put("meta", singleArgumentAggregationMethodReference().forOperator("$meta"));
@@ -102,6 +108,12 @@ public class MethodReferenceNode extends ExpressionNode {
 		map.put("isArray", singleArgumentAggregationMethodReference().forOperator("$isArray"));
 		map.put("size", singleArgumentAggregationMethodReference().forOperator("$size"));
 		map.put("slice", arrayArgumentAggregationMethodReference().forOperator("$slice"));
+		map.put("reverseArray", singleArgumentAggregationMethodReference().forOperator("$reverseArray"));
+		map.put("reduce", mapArgumentAggregationMethodReference().forOperator("$reduce").mappingParametersTo("input",
+				"initialValue", "in"));
+		map.put("zip", mapArgumentAggregationMethodReference().forOperator("$zip").mappingParametersTo("inputs",
+				"useLongestLength", "defaults"));
+		map.put("in", arrayArgumentAggregationMethodReference().forOperator("$in"));
 
 		// VARIABLE OPERATORS
 		map.put("map", mapArgumentAggregationMethodReference().forOperator("$map") //
@@ -124,6 +136,9 @@ public class MethodReferenceNode extends ExpressionNode {
 		map.put("millisecond", singleArgumentAggregationMethodReference().forOperator("$millisecond"));
 		map.put("dateToString", mapArgumentAggregationMethodReference().forOperator("$dateToString") //
 				.mappingParametersTo("format", "date"));
+		map.put("isoDayOfWeek", singleArgumentAggregationMethodReference().forOperator("$isoDayOfWeek"));
+		map.put("isoWeek", singleArgumentAggregationMethodReference().forOperator("$isoWeek"));
+		map.put("isoWeekYear", singleArgumentAggregationMethodReference().forOperator("$isoWeekYear"));
 
 		// CONDITIONAL OPERATORS
 		map.put("cond", mapArgumentAggregationMethodReference().forOperator("$cond") //
@@ -142,6 +157,9 @@ public class MethodReferenceNode extends ExpressionNode {
 		map.put("stdDevPop", arrayArgumentAggregationMethodReference().forOperator("$stdDevPop"));
 		map.put("stdDevSamp", arrayArgumentAggregationMethodReference().forOperator("$stdDevSamp"));
 
+		// TYPE OPERATORS
+		map.put("type", singleArgumentAggregationMethodReference().forOperator("$type"));
+
 		FUNCTIONS = Collections.unmodifiableMap(map);
 	}
 
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java
index 96d45ef1e9..5fff2b2e59 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java
@@ -28,13 +28,35 @@
 import java.util.Arrays;
 import java.util.List;
 
+import org.hamcrest.Matchers;
 import org.junit.Test;
+import org.springframework.data.domain.Range;
 import org.springframework.data.mongodb.core.DBObjectTestUtils;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.And;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArithmeticOperators;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArrayOperators;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Avg;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.BooleanOperators;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ComparisonOperators;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.DateOperators;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Gte;
 import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.LiteralOperators;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Lt;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.RangeOperator;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Reduce.PropertyExpression;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Reduce.Variable;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.SetOperators;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.StringOperators;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Switch.CaseOperator;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Type;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.VariableOperators;
 import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder;
 import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.*;
 
 import com.mongodb.BasicDBObject;
+import com.mongodb.BasicDBObjectBuilder;
 import com.mongodb.DBObject;
 import com.mongodb.util.JSON;
 
@@ -1792,7 +1814,296 @@ public void shouldRenderLetExpressionCorrectlyWhenUsingLetOnProjectionBuilder()
 						"}}}}")));
 	}
 
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderIndexOfBytesCorrectly() {
+
+		DBObject agg = project().and(StringOperators.valueOf("item").indexOf("foo")).as("byteLocation")
+				.toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse("{ $project: { byteLocation: { $indexOfBytes: [ \"$item\", \"foo\" ] } } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderIndexOfBytesWithRangeCorrectly() {
+
+		DBObject agg = project().and(StringOperators.valueOf("item").indexOf("foo").within(new Range<Long>(5L, 9L)))
+				.as("byteLocation").toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, isBsonObject().containing("$project.byteLocation.$indexOfBytes.[2]", 5L)
+				.containing("$project.byteLocation.$indexOfBytes.[3]", 9L));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderIndexOfCPCorrectly() {
+
+		DBObject agg = project().and(StringOperators.valueOf("item").indexOfCP("foo")).as("cpLocation")
+				.toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse("{ $project: { cpLocation: { $indexOfCP: [ \"$item\", \"foo\" ] } } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderIndexOfCPWithRangeCorrectly() {
+
+		DBObject agg = project().and(StringOperators.valueOf("item").indexOfCP("foo").within(new Range<Long>(5L, 9L)))
+				.as("cpLocation").toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, isBsonObject().containing("$project.cpLocation.$indexOfCP.[2]", 5L)
+				.containing("$project.cpLocation.$indexOfCP.[3]", 9L));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderSplitCorrectly() {
+
+		DBObject agg = project().and(StringOperators.valueOf("city").split(", ")).as("city_state")
+				.toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse("{ $project : { city_state : { $split: [\"$city\", \", \"] }} }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderStrLenBytesCorrectly() {
+
+		DBObject agg = project().and(StringOperators.valueOf("name").length()).as("length")
+				.toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse("{ $project : { \"length\": { $strLenBytes: \"$name\" } } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderStrLenCPCorrectly() {
+
+		DBObject agg = project().and(StringOperators.valueOf("name").lengthCP()).as("length")
+				.toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse("{ $project : { \"length\": { $strLenCP: \"$name\" } } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderSubstrCPCorrectly() {
+
+		DBObject agg = project().and(StringOperators.valueOf("quarter").substringCP(0, 2)).as("yearSubstring")
+				.toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse("{ $project : { yearSubstring: { $substrCP: [ \"$quarter\", 0, 2 ] } } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderIndexOfArrayCorrectly() {
+
+		DBObject agg = project().and(ArrayOperators.arrayOf("items").indexOf(2)).as("index")
+				.toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse("{ $project : { index: { $indexOfArray: [ \"$items\", 2 ] } } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderRangeCorrectly() {
+
+		DBObject agg = project().and(RangeOperator.rangeStartingAt(0L).to("distance").withStepSize(25L)).as("rest_stops")
+				.toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, isBsonObject().containing("$project.rest_stops.$range.[0]", 0L)
+				.containing("$project.rest_stops.$range.[1]", "$distance").containing("$project.rest_stops.$range.[2]", 25L));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderReverseArrayCorrectly() {
+
+		DBObject agg = project().and(ArrayOperators.arrayOf("favorites").reverse()).as("reverseFavorites")
+				.toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse("{ $project : { reverseFavorites: { $reverseArray: \"$favorites\" } } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderReduceWithSimpleObjectCorrectly() {
+
+		DBObject agg = project()
+				.and(ArrayOperators.arrayOf("probabilityArr")
+						.reduce(ArithmeticOperators.valueOf("$$value").multiplyBy("$$this")).startingWith(1))
+				.as("results").toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse(
+				"{ $project : { \"results\": { $reduce: { input: \"$probabilityArr\", initialValue: 1, in: { $multiply: [ \"$$value\", \"$$this\" ] } } } } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderReduceWithComplexObjectCorrectly() {
+
+		PropertyExpression sum = PropertyExpression.property("sum").definedAs(
+				ArithmeticOperators.valueOf(Variable.VALUE.referingTo("sum").getName()).add(Variable.THIS.getName()));
+		PropertyExpression product = PropertyExpression.property("product").definedAs(ArithmeticOperators
+				.valueOf(Variable.VALUE.referingTo("product").getName()).multiplyBy(Variable.THIS.getName()));
+
+		DBObject agg = project()
+				.and(ArrayOperators.arrayOf("probabilityArr").reduce(sum, product)
+						.startingWith(new BasicDBObjectBuilder().add("sum", 5).add("product", 2).get()))
+				.as("results").toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse(
+				"{ $project : { \"results\": { $reduce: { input: \"$probabilityArr\", initialValue:  { \"sum\" : 5 , \"product\" : 2} , in: { \"sum\": { $add : [\"$$value.sum\", \"$$this\"] }, \"product\": { $multiply: [ \"$$value.product\", \"$$this\" ] } } } } } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderZipCorrectly() {
+
+		AggregationExpression elemAt0 = ArrayOperators.arrayOf("matrix").elementAt(0);
+		AggregationExpression elemAt1 = ArrayOperators.arrayOf("matrix").elementAt(1);
+		AggregationExpression elemAt2 = ArrayOperators.arrayOf("matrix").elementAt(2);
+
+		DBObject agg = project().and(
+				ArrayOperators.arrayOf(elemAt0).zipWith(elemAt1, elemAt2).useLongestLength().defaultTo(new Object[] { 1, 2 }))
+				.as("transposed").toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse(
+				"{ $project : {  transposed: { $zip: { inputs: [ { $arrayElemAt: [ \"$matrix\", 0 ] }, { $arrayElemAt: [ \"$matrix\", 1 ] }, { $arrayElemAt: [ \"$matrix\", 2 ] } ], useLongestLength : true, defaults: [1,2] } } } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderInCorrectly() {
+
+		DBObject agg = project().and(ArrayOperators.arrayOf("in_stock").containsValue("bananas")).as("has_bananas")
+				.toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse("{ $project : { has_bananas : { $in : [\"bananas\", \"$in_stock\" ] } } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderIsoDayOfWeekCorrectly() {
+
+		DBObject agg = project().and(DateOperators.dateOf("birthday").isoDayOfWeek()).as("dayOfWeek")
+				.toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse("{ $project : { dayOfWeek: { $isoDayOfWeek: \"$birthday\" } } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderIsoWeekCorrectly() {
+
+		DBObject agg = project().and(DateOperators.dateOf("date").isoWeek()).as("weekNumber")
+				.toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse("{ $project : { weekNumber: { $isoWeek: \"$date\" } } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderIsoWeekYearCorrectly() {
+
+		DBObject agg = project().and(DateOperators.dateOf("date").isoWeekYear()).as("yearNumber")
+				.toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse("{ $project : { yearNumber: { $isoWeekYear: \"$date\" } } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderSwitchCorrectly() {
+
+		String expected = "$switch:\n" + //
+				"{\n" + //
+				"     branches: [\n" + //
+				"       {\n" + //
+				"         case: { $gte : [ { $avg : \"$scores\" }, 90 ] },\n" + //
+				"         then: \"Doing great!\"\n" + //
+				"       },\n" + //
+				"       {\n" + //
+				"         case: { $and : [ { $gte : [ { $avg : \"$scores\" }, 80 ] },\n" + //
+				"                          { $lt : [ { $avg : \"$scores\" }, 90 ] } ] },\n" + //
+				"         then: \"Doing pretty well.\"\n" + //
+				"       },\n" + //
+				"       {\n" + //
+				"         case: { $lt : [ { $avg : \"$scores\" }, 80 ] },\n" + //
+				"         then: \"Needs improvement.\"\n" + //
+				"       }\n" + //
+				"     ],\n" + //
+				"     default: \"No scores found.\"\n" + //
+				"   }\n" + //
+				"}";
+
+		CaseOperator cond1 = CaseOperator.when(Gte.valueOf(Avg.avgOf("scores")).greaterThanEqualToValue(90))
+				.then("Doing great!");
+		CaseOperator cond2 = CaseOperator.when(And.and(Gte.valueOf(Avg.avgOf("scores")).greaterThanEqualToValue(80),
+				Lt.valueOf(Avg.avgOf("scores")).lessThanValue(90))).then("Doing pretty well.");
+		CaseOperator cond3 = CaseOperator.when(Lt.valueOf(Avg.avgOf("scores")).lessThanValue(80))
+				.then("Needs improvement.");
+
+		DBObject agg = project().and(ConditionalOperators.switchCases(cond1, cond2, cond3).defaultTo("No scores found."))
+				.as("summary").toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse("{ $project : { summary: {" + expected + "} } }")));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldTypeCorrectly() {
+
+		DBObject agg = project().and(Type.typeOf("a")).as("a")
+				.toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+		assertThat(agg, Matchers.is(JSON.parse("{ $project : { a: { $type: \"$a\" } } }")));
+	}
+
 	private static DBObject exctractOperation(String field, DBObject fromProjectClause) {
 		return (DBObject) fromProjectClause.get(field);
 	}
+
 }
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java
index 770145a80a..013d6189e7 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java
@@ -745,13 +745,13 @@ public void shouldRenderMethodReferenceNodeMin() {
 		assertThat(transform("min(a, b)"), is("{ \"$min\" : [ \"$a\" , \"$b\"]}"));
 	}
 
-
 	/**
 	 * @see DATAMONGO-1530
 	 */
 	@Test
 	public void shouldRenderMethodReferenceNodePush() {
-		assertThat(transform("push({'item':'$item', 'quantity':'$qty'})"), is("{ \"$push\" : { \"item\" : \"$item\" , \"quantity\" : \"$qty\"}}"));
+		assertThat(transform("push({'item':'$item', 'quantity':'$qty'})"),
+				is("{ \"$push\" : { \"item\" : \"$item\" , \"quantity\" : \"$qty\"}}"));
 	}
 
 	/**
@@ -884,6 +884,129 @@ public void shouldRenderComplexNotCorrectly() {
 		assertThat(transform("!(foo > 10)"), is("{ \"$not\" : [ { \"$gt\" : [ \"$foo\" , 10]}]}"));
 	}
 
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodReferenceIndexOfBytes() {
+		assertThat(transform("indexOfBytes(item, 'foo')"), is("{ \"$indexOfBytes\" : [ \"$item\" , \"foo\"]}"));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodReferenceIndexOfCP() {
+		assertThat(transform("indexOfCP(item, 'foo')"), is("{ \"$indexOfCP\" : [ \"$item\" , \"foo\"]}"));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodReferenceSplit() {
+		assertThat(transform("split(item, ',')"), is("{ \"$split\" : [ \"$item\" , \",\"]}"));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodReferenceStrLenBytes() {
+		assertThat(transform("strLenBytes(item)"), is("{ \"$strLenBytes\" : \"$item\"}"));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodReferenceStrLenCP() {
+		assertThat(transform("strLenCP(item)"), is("{ \"$strLenCP\" : \"$item\"}"));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodSubstrCP() {
+		assertThat(transform("substrCP(item, 0, 5)"), is("{ \"$substrCP\" : [ \"$item\" , 0 , 5]}"));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodReferenceReverseArray() {
+		assertThat(transform("reverseArray(array)"), is("{ \"$reverseArray\" : \"$array\"}"));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodReferenceReduce() {
+		assertThat(transform("reduce(field, '', {'$concat':new String[]{'$$value','$$this'}})"), is(
+				"{ \"$reduce\" : { \"input\" : \"$field\" , \"initialValue\" : \"\" , \"in\" : { \"$concat\" : [ \"$$value\" , \"$$this\"]}}}"));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodReferenceZip() {
+		assertThat(transform("zip(new String[]{'$array1', '$array2'})"),
+				is("{ \"$zip\" : { \"inputs\" : [ \"$array1\" , \"$array2\"]}}"));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodReferenceZipWithOptionalArgs() {
+		assertThat(transform("zip(new String[]{'$array1', '$array2'}, true, new int[]{1,2})"), is(
+				"{ \"$zip\" : { \"inputs\" : [ \"$array1\" , \"$array2\"] , \"useLongestLength\" : true , \"defaults\" : [ 1 , 2]}}"));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodIn() {
+		assertThat(transform("in('item', array)"), is("{ \"$in\" : [ \"item\" , \"$array\"]}"));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodRefereneIsoDayOfWeek() {
+		assertThat(transform("isoDayOfWeek(date)"), is("{ \"$isoDayOfWeek\" : \"$date\"}"));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodRefereneIsoWeek() {
+		assertThat(transform("isoWeek(date)"), is("{ \"$isoWeek\" : \"$date\"}"));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodRefereneIsoWeekYear() {
+		assertThat(transform("isoWeekYear(date)"), is("{ \"$isoWeekYear\" : \"$date\"}"));
+	}
+
+	/**
+	 * @see DATAMONGO-1548
+	 */
+	@Test
+	public void shouldRenderMethodRefereneType() {
+		assertThat(transform("type(a)"), is("{ \"$type\" : \"$a\"}"));
+	}
+
 	private String transform(String expression, Object... params) {
 		Object result = transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params);
 		return result == null ? null : result.toString();
diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc
index 0b9b03fc0c..d6ff11f630 100644
--- a/src/main/asciidoc/reference/mongodb.adoc
+++ b/src/main/asciidoc/reference/mongodb.adoc
@@ -1686,26 +1686,28 @@ At the time of this writing we provide support for the following Aggregation Ope
 | abs, add (*via plus), ceil, divide, exp, floor, ln, log, log10, mod, multiply, pow, sqrt, subtract (*via minus), trunc
 
 | String Aggregation Operators
-| concat, substr, toLower, toUpper, stcasecmp
+| concat, substr, toLower, toUpper, stcasecmp, indexOfBytes, indexOfCP, split, strLenBytes, strLenCP, substrCP,
 
 | Comparison Aggregation Operators
 | eq (*via: is), gt, gte, lt, lte, ne
 
 | Array Aggregation Operators
-| arrayElementAt, concatArrays, filter, isArray, size, slice
+| arrayElementAt, concatArrays, filter, in, indexOfArray, isArray, range, reverseArray, reduce, size, slice, zip
 
 | Literal Operators
 | literal
 
 | Date Aggregation Operators
-| dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateToString
+| dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateToString, isoDayOfWeek, isoWeek, isoWeekYear
 
 | Variable Operators
 | map
 
-
 | Conditional Aggregation Operators
-| cond, ifNull
+| cond, ifNull, switch
+
+| Type Aggregation Operators
+| type
 
 |===
 

From b74f1d92ed0424a8271cf53f54e5f1808317655c Mon Sep 17 00:00:00 2001
From: Mark Paluch <mpaluch@pivotal.io>
Date: Mon, 12 Dec 2016 11:55:51 +0100
Subject: [PATCH 3/3] DATAMONGO-1548 - Polishing.

Enhance JavaDoc. Minor formatting. Fix typos.
---
 .../aggregation/AggregationExpressions.java   | 406 ++++++++++++++++--
 .../ProjectionOperationUnitTests.java         |   4 +-
 2 files changed, 361 insertions(+), 49 deletions(-)

diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java
index 76812cbb31..0c86caf1e7 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java
@@ -24,6 +24,7 @@
 
 import org.springframework.dao.InvalidDataAccessApiUsageException;
 import org.springframework.data.domain.Range;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArithmeticOperators.ArithmeticOperatorFactory;
 import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder;
 import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder;
 import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder;
@@ -31,7 +32,6 @@
 import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Reduce.PropertyExpression;
 import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Switch.CaseOperator;
 import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
-import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
 import org.springframework.data.mongodb.core.query.CriteriaDefinition;
 import org.springframework.util.Assert;
 import org.springframework.util.ClassUtils;
@@ -268,8 +268,8 @@ public static IfNull.ThenBuilder ifNull(AggregationExpression expression) {
 
 		/**
 		 * Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it
-		 * finds an expression which evaluates to true, {@code $switch} executes a specified expression and breaks out of
-		 * the control flow.
+		 * finds an expression which evaluates to {@literal true}, {@code $switch} executes a specified expression and
+		 * breaks out of the control flow.
 		 *
 		 * @param conditions must not be {@literal null}.
 		 * @return
@@ -280,8 +280,8 @@ public static Switch switchCases(CaseOperator... conditions) {
 
 		/**
 		 * Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it
-		 * finds an expression which evaluates to true, {@code $switch} executes a specified expression and breaks out of
-		 * the control flow.
+		 * finds an expression which evaluates to {@literal true}, {@code $switch} executes a specified expression and
+		 * breaks out of the control flow.
 		 *
 		 * @param conditions must not be {@literal null}.
 		 * @return
@@ -1595,8 +1595,8 @@ private StrCaseCmp createStrCaseCmp() {
 
 			/**
 			 * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
-			 * string for an occurence of a given {@literal substring} and returns the UTF-8 byte index (zero-based) of the
-			 * first occurence.
+			 * string for an occurrence of a given {@literal substring} and returns the UTF-8 byte index (zero-based) of the
+			 * first occurrence.
 			 *
 			 * @param substring must not be {@literal null}.
 			 * @return
@@ -1609,8 +1609,8 @@ public IndexOfBytes indexOf(String substring) {
 
 			/**
 			 * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
-			 * string for an occurence of a substring contained in the given {@literal field reference} and returns the UTF-8
-			 * byte index (zero-based) of the first occurence.
+			 * string for an occurrence of a substring contained in the given {@literal field reference} and returns the UTF-8
+			 * byte index (zero-based) of the first occurrence.
 			 *
 			 * @param fieldReference must not be {@literal null}.
 			 * @return
@@ -1623,8 +1623,8 @@ public IndexOfBytes indexOf(Field fieldReference) {
 
 			/**
 			 * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
-			 * string for an occurence of a substring resulting from the given {@link AggregationExpression} and returns the
-			 * UTF-8 byte index (zero-based) of the first occurence.
+			 * string for an occurrence of a substring resulting from the given {@link AggregationExpression} and returns the
+			 * UTF-8 byte index (zero-based) of the first occurrence.
 			 *
 			 * @param expression must not be {@literal null}.
 			 * @return
@@ -1641,8 +1641,8 @@ private IndexOfBytes.SubstringBuilder createIndexOfBytesSubstringBuilder() {
 
 			/**
 			 * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
-			 * string for an occurence of a given {@literal substring} and returns the UTF-8 code point index (zero-based) of
-			 * the first occurence.
+			 * string for an occurrence of a given {@literal substring} and returns the UTF-8 code point index (zero-based) of
+			 * the first occurrence.
 			 *
 			 * @param substring must not be {@literal null}.
 			 * @return
@@ -1655,8 +1655,8 @@ public IndexOfCP indexOfCP(String substring) {
 
 			/**
 			 * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
-			 * string for an occurence of a substring contained in the given {@literal field reference} and returns the UTF-8
-			 * code point index (zero-based) of the first occurence.
+			 * string for an occurrence of a substring contained in the given {@literal field reference} and returns the UTF-8
+			 * code point index (zero-based) of the first occurrence.
 			 *
 			 * @param fieldReference must not be {@literal null}.
 			 * @return
@@ -1669,8 +1669,8 @@ public IndexOfCP indexOfCP(Field fieldReference) {
 
 			/**
 			 * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
-			 * string for an occurence of a substring resulting from the given {@link AggregationExpression} and returns the
-			 * UTF-8 code point index (zero-based) of the first occurence.
+			 * string for an occurrence of a substring resulting from the given {@link AggregationExpression} and returns the
+			 * UTF-8 code point index (zero-based) of the first occurrence.
 			 *
 			 * @param expression must not be {@literal null}.
 			 * @return
@@ -1938,8 +1938,8 @@ public Slice slice() {
 			}
 
 			/**
-			 * Creates new {@link AggregationExpressions} that searches the associated array for an occurence of a specified
-			 * value and returns the array index (zero-based) of the first occurence.
+			 * Creates new {@link AggregationExpressions} that searches the associated array for an occurrence of a specified
+			 * value and returns the array index (zero-based) of the first occurrence.
 			 *
 			 * @param value must not be {@literal null}.
 			 * @return
@@ -1967,6 +1967,7 @@ public ReverseArray reverse() {
 			 */
 			public ReduceInitialValueBuilder reduce(final AggregationExpression expression) {
 				return new ReduceInitialValueBuilder() {
+
 					@Override
 					public Reduce startingWith(Object initialValue) {
 						return (usesFieldRef() ? Reduce.arrayOf(fieldReference) : Reduce.arrayOf(expression))
@@ -1985,6 +1986,7 @@ public Reduce startingWith(Object initialValue) {
 			public ReduceInitialValueBuilder reduce(final PropertyExpression... expressions) {
 
 				return new ReduceInitialValueBuilder() {
+
 					@Override
 					public Reduce startingWith(Object initialValue) {
 						return (usesFieldRef() ? Reduce.arrayOf(fieldReference) : Reduce.arrayOf(expression))
@@ -1996,7 +1998,7 @@ public Reduce startingWith(Object initialValue) {
 			/**
 			 * Creates new {@link AggregationExpressions} that transposes an array of input arrays so that the first element
 			 * of the output array would be an array containing, the first element of the first input array, the first element
-			 * of the second input array, etc
+			 * of the second input array, etc.
 			 *
 			 * @param arrays must not be {@literal null}.
 			 * @return
@@ -2007,7 +2009,7 @@ public Zip zipWith(Object... arrays) {
 
 			/**
 			 * Creates new {@link AggregationExpressions} that returns a boolean indicating whether a specified value is in
-			 * the associcated array.
+			 * the associated array.
 			 *
 			 * @param value must not be {@literal null}.
 			 * @return
@@ -2016,7 +2018,17 @@ public In containsValue(Object value) {
 				return (usesFieldRef() ? In.arrayOf(fieldReference) : In.arrayOf(expression)).containsValue(value);
 			}
 
+			/**
+			 * @author Christoph Strobl
+			 */
 			public interface ReduceInitialValueBuilder {
+
+				/**
+				 * Define the initial cumulative value set before in is applied to the first element of the input array.
+				 *
+				 * @param initialValue must not be {@literal null}.
+				 * @return
+				 */
 				Reduce startingWith(Object initialValue);
 			}
 
@@ -2340,6 +2352,9 @@ protected AbstractAggregationExpression(Object value) {
 			this.value = value;
 		}
 
+		/* (non-Javadoc)
+		 * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+		 */
 		@Override
 		public DBObject toDbObject(AggregationOperationContext context) {
 			return toDbObject(this.value, context);
@@ -4147,14 +4162,32 @@ private SubstringBuilder(Object stringExpression) {
 				this.stringExpression = stringExpression;
 			}
 
+			/**
+			 * Creates a new {@link IndexOfBytes} given {@literal substring}.
+			 *
+			 * @param substring must not be {@literal null}.
+			 * @return
+			 */
 			public IndexOfBytes indexOf(String substring) {
 				return new IndexOfBytes(Arrays.asList(stringExpression, substring));
 			}
 
+			/**
+			 * Creates a new {@link IndexOfBytes} given {@link AggregationExpression} that resolves to the substring.
+			 *
+			 * @param expression must not be {@literal null}.
+			 * @return
+			 */
 			public IndexOfBytes indexOf(AggregationExpression expression) {
 				return new IndexOfBytes(Arrays.asList(stringExpression, expression));
 			}
 
+			/**
+			 * Creates a new {@link IndexOfBytes} given {@link Field} that resolves to the substring.
+			 *
+			 * @param fieldReference must not be {@literal null}.
+			 * @return
+			 */
 			public IndexOfBytes indexOf(Field fieldReference) {
 				return new IndexOfBytes(Arrays.asList(stringExpression, fieldReference));
 			}
@@ -4228,14 +4261,32 @@ private SubstringBuilder(Object stringExpression) {
 				this.stringExpression = stringExpression;
 			}
 
+			/**
+			 * Creates a new {@link IndexOfCP} given {@literal substring}.
+			 *
+			 * @param substring must not be {@literal null}.
+			 * @return
+			 */
 			public IndexOfCP indexOf(String substring) {
 				return new IndexOfCP(Arrays.asList(stringExpression, substring));
 			}
 
+			/**
+			 * Creates a new {@link IndexOfCP} given {@link AggregationExpression} that resolves to the substring.
+			 *
+			 * @param expression must not be {@literal null}.
+			 * @return
+			 */
 			public IndexOfCP indexOf(AggregationExpression expression) {
 				return new IndexOfCP(Arrays.asList(stringExpression, expression));
 			}
 
+			/**
+			 * Creates a new {@link IndexOfCP} given {@link Field} that resolves to the substring.
+			 *
+			 * @param fieldReference must not be {@literal null}.
+			 * @return
+			 */
 			public IndexOfCP indexOf(Field fieldReference) {
 				return new IndexOfCP(Arrays.asList(stringExpression, fieldReference));
 			}
@@ -4244,6 +4295,8 @@ public IndexOfCP indexOf(Field fieldReference) {
 
 	/**
 	 * {@link AggregationExpression} for {@code $split}.
+	 *
+	 * @author Christoph Strobl
 	 */
 	class Split extends AbstractAggregationExpression {
 
@@ -4281,19 +4334,19 @@ public static Split valueOf(AggregationExpression expression) {
 		}
 
 		/**
-		 * Use given {@link String} as deliminator
+		 * Use given {@link String} as delimiter.
 		 *
-		 * @param deliminator must not be {@literal null}.
+		 * @param delimiter must not be {@literal null}.
 		 * @return
 		 */
-		public Split split(String deliminator) {
+		public Split split(String delimiter) {
 
-			Assert.notNull(deliminator, "Deliminator must not be null!");
-			return new Split(append(deliminator));
+			Assert.notNull(delimiter, "Delimiter must not be null!");
+			return new Split(append(delimiter));
 		}
 
 		/**
-		 * Usge value of referenced field as deliminator.
+		 * Use value of referenced field as delimiter.
 		 *
 		 * @param fieldReference must not be {@literal null}.
 		 * @return
@@ -4305,7 +4358,7 @@ public Split split(Field fieldReference) {
 		}
 
 		/**
-		 * Use value resulting from {@link AggregationExpression} as deliminator.
+		 * Use value resulting from {@link AggregationExpression} as delimiter.
 		 *
 		 * @param expression must not be {@literal null}.
 		 * @return
@@ -4319,6 +4372,8 @@ public Split split(AggregationExpression expression) {
 
 	/**
 	 * {@link AggregationExpression} for {@code $strLenBytes}.
+	 *
+	 * @author Christoph Strobl
 	 */
 	class StrLenBytes extends AbstractAggregationExpression {
 
@@ -4331,17 +4386,33 @@ protected String getMongoMethod() {
 			return "$strLenBytes";
 		}
 
+		/**
+		 * Creates new {@link StrLenBytes}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
 		public static StrLenBytes stringLengthOf(String fieldReference) {
 			return new StrLenBytes(Fields.field(fieldReference));
 		}
 
+		/**
+		 * Creates new {@link StrLenBytes}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
 		public static StrLenBytes stringLengthOf(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
 			return new StrLenBytes(expression);
 		}
 	}
 
 	/**
 	 * {@link AggregationExpression} for {@code $strLenCP}.
+	 *
+	 * @author Christoph Strobl
 	 */
 	class StrLenCP extends AbstractAggregationExpression {
 
@@ -4354,11 +4425,25 @@ protected String getMongoMethod() {
 			return "$strLenCP";
 		}
 
+		/**
+		 * Creates new {@link StrLenCP}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
 		public static StrLenCP stringLengthOfCP(String fieldReference) {
 			return new StrLenCP(Fields.field(fieldReference));
 		}
 
+		/**
+		 * Creates new {@link StrLenCP}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
 		public static StrLenCP stringLengthOfCP(AggregationExpression expression) {
+
+			Assert.notNull(expression, "Expression must not be null!");
 			return new StrLenCP(expression);
 		}
 	}
@@ -4909,7 +4994,17 @@ public Slice itemCount(int nrElements) {
 			};
 		}
 
+		/**
+		 * @author Christoph Strobl
+		 */
 		public interface SliceElementsBuilder {
+
+			/**
+			 * Set the number of elements given {@literal nrElements}.
+			 *
+			 * @param nrElements
+			 * @return
+			 */
 			Slice itemCount(int nrElements);
 		}
 	}
@@ -4967,6 +5062,9 @@ public IndexOfArray within(Range<Long> range) {
 			return new IndexOfArray(append(rangeValues));
 		}
 
+		/**
+		 * @author Christoph Strobl
+		 */
 		public static class IndexOfArrayBuilder {
 
 			private final Object targetArray;
@@ -4975,6 +5073,12 @@ private IndexOfArrayBuilder(Object targetArray) {
 				this.targetArray = targetArray;
 			}
 
+			/**
+			 * Set the {@literal value} to check for its index in the array.
+			 *
+			 * @param value must not be {@literal null}.
+			 * @return
+			 */
 			public IndexOfArray indexOf(Object value) {
 
 				Assert.notNull(value, "Value must not be null!");
@@ -4999,19 +5103,37 @@ protected String getMongoMethod() {
 			return "$range";
 		}
 
+		/**
+		 * Start creating new {@link RangeOperator}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
 		public static RangeOperatorBuilder rangeStartingAt(String fieldReference) {
 			return new RangeOperatorBuilder(Fields.field(fieldReference));
 		}
 
+		/**
+		 * Start creating new {@link RangeOperator}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
 		public static RangeOperatorBuilder rangeStartingAt(AggregationExpression expression) {
 			return new RangeOperatorBuilder(expression);
 		}
 
-		public static RangeOperatorBuilder rangeStartingAt(Long value) {
+		/**
+		 * Start creating new {@link RangeOperator}.
+		 *
+		 * @param value
+		 * @return
+		 */
+		public static RangeOperatorBuilder rangeStartingAt(long value) {
 			return new RangeOperatorBuilder(value);
 		}
 
-		public RangeOperator withStepSize(Long stepSize) {
+		public RangeOperator withStepSize(long stepSize) {
 			return new RangeOperator(append(stepSize));
 		}
 
@@ -5023,23 +5145,42 @@ private RangeOperatorBuilder(Object startPoint) {
 				this.startPoint = startPoint;
 			}
 
-			public RangeOperator to(Long index) {
+			/**
+			 * Creates new {@link RangeOperator}.
+			 *
+			 * @param index
+			 * @return
+			 */
+			public RangeOperator to(long index) {
 				return new RangeOperator(Arrays.asList(startPoint, index));
 			}
 
+			/**
+			 * Creates new {@link RangeOperator}.
+			 *
+			 * @param expression must not be {@literal null}.
+			 * @return
+			 */
 			public RangeOperator to(AggregationExpression expression) {
 				return new RangeOperator(Arrays.asList(startPoint, expression));
 			}
 
+			/**
+			 * Creates new {@link RangeOperator}.
+			 *
+			 * @param fieldReference must not be {@literal null}.
+			 * @return
+			 */
 			public RangeOperator to(String fieldReference) {
 				return new RangeOperator(Arrays.asList(startPoint, Fields.field(fieldReference)));
 			}
 		}
-
 	}
 
 	/**
 	 * {@link AggregationExpression} for {@code $reverseArray}.
+	 *
+	 * @author Christoph Strobl
 	 */
 	class ReverseArray extends AbstractAggregationExpression {
 
@@ -5052,10 +5193,22 @@ protected String getMongoMethod() {
 			return "$reverseArray";
 		}
 
+		/**
+		 * Creates new {@link ReverseArray} given {@literal fieldReference}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
 		public static ReverseArray reverseArrayOf(String fieldReference) {
 			return new ReverseArray(Fields.field(fieldReference));
 		}
 
+		/**
+		 * Creates new {@link ReverseArray} given {@link AggregationExpression}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
 		public static ReverseArray reverseArrayOf(AggregationExpression expression) {
 			return new ReverseArray(expression);
 		}
@@ -5063,6 +5216,8 @@ public static ReverseArray reverseArrayOf(AggregationExpression expression) {
 
 	/**
 	 * {@link AggregationExpression} for {@code $reduce}.
+	 *
+	 * @author Christoph Strobl
 	 */
 	class Reduce implements AggregationExpression {
 
@@ -5071,11 +5226,15 @@ class Reduce implements AggregationExpression {
 		private final List<AggregationExpression> reduceExpressions;
 
 		private Reduce(Object input, Object initialValue, List<AggregationExpression> reduceExpressions) {
+
 			this.input = input;
 			this.initialValue = initialValue;
 			this.reduceExpressions = reduceExpressions;
 		}
 
+		/* (non-Javadoc)
+		 * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+		 */
 		@Override
 		public DBObject toDbObject(AggregationOperationContext context) {
 
@@ -5112,19 +5271,37 @@ private Object getMappedValue(Object value, AggregationOperationContext context)
 			}
 		}
 
+		/**
+		 * Start creating new {@link Reduce}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
 		public static InitialValueBuilder arrayOf(final String fieldReference) {
+
+			Assert.notNull(fieldReference, "FieldReference must not be null");
+
 			return new InitialValueBuilder() {
 
 				@Override
 				public ReduceBuilder withInitialValue(final Object initialValue) {
+
+					Assert.notNull(initialValue, "Initial value must not be null");
+
 					return new ReduceBuilder() {
+
 						@Override
 						public Reduce reduce(AggregationExpression expression) {
+
+							Assert.notNull(expression, "AggregationExpression must not be null");
 							return new Reduce(Fields.field(fieldReference), initialValue, Collections.singletonList(expression));
 						}
 
 						@Override
 						public Reduce reduce(PropertyExpression... expressions) {
+
+							Assert.notNull(expressions, "PropertyExpressions must not be null");
+
 							return new Reduce(Fields.field(fieldReference), initialValue,
 									Arrays.<AggregationExpression> asList(expressions));
 						}
@@ -5133,19 +5310,36 @@ public Reduce reduce(PropertyExpression... expressions) {
 			};
 		}
 
+		/**
+		 * Start creating new {@link Reduce}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
 		public static InitialValueBuilder arrayOf(final AggregationExpression expression) {
+
+			Assert.notNull(expression, "AggregationExpression must not be null");
+
 			return new InitialValueBuilder() {
 
 				@Override
 				public ReduceBuilder withInitialValue(final Object initialValue) {
+
+					Assert.notNull(initialValue, "Initial value must not be null");
+
 					return new ReduceBuilder() {
+
 						@Override
 						public Reduce reduce(AggregationExpression expression) {
+
+							Assert.notNull(expression, "AggregationExpression must not be null");
 							return new Reduce(expression, initialValue, Collections.singletonList(expression));
 						}
 
 						@Override
 						public Reduce reduce(PropertyExpression... expressions) {
+
+							Assert.notNull(expressions, "PropertyExpressions must not be null");
 							return new Reduce(expression, initialValue, Arrays.<AggregationExpression> asList(expressions));
 						}
 					};
@@ -5153,24 +5347,30 @@ public Reduce reduce(PropertyExpression... expressions) {
 			};
 		}
 
+		/**
+		 * @author Christoph Strobl
+		 */
 		public interface InitialValueBuilder {
 
 			/**
 			 * Define the initial cumulative value set before in is applied to the first element of the input array.
 			 *
-			 * @param intialValue must not be {@literal null}.
+			 * @param initialValue must not be {@literal null}.
 			 * @return
 			 */
-			ReduceBuilder withInitialValue(Object intialValue);
+			ReduceBuilder withInitialValue(Object initialValue);
 		}
 
+		/**
+		 * @author Christoph Strobl
+		 */
 		public interface ReduceBuilder {
 
 			/**
 			 * Define the {@link AggregationExpression} to apply to each element in the input array in left-to-right order.
 			 * <br />
-			 * <b>NOTE:</b> During evaulation of the in expression the variable references {@link Variable#THIS} and
-			 * {@link Variable#VALUE} are availble.
+			 * <b>NOTE:</b> During evaluation of the in expression the variable references {@link Variable#THIS} and
+			 * {@link Variable#VALUE} are available.
 			 *
 			 * @param expression must not be {@literal null}.
 			 * @return
@@ -5180,8 +5380,8 @@ public interface ReduceBuilder {
 			/**
 			 * Define the {@link PropertyExpression}s to apply to each element in the input array in left-to-right order.
 			 * <br />
-			 * <b>NOTE:</b> During evaulation of the in expression the variable references {@link Variable#THIS} and
-			 * {@link Variable#VALUE} are availble.
+			 * <b>NOTE:</b> During evaluation of the in expression the variable references {@link Variable#THIS} and
+			 * {@link Variable#VALUE} are available.
 			 *
 			 * @param expression must not be {@literal null}.
 			 * @return
@@ -5197,7 +5397,11 @@ public static class PropertyExpression implements AggregationExpression {
 			private final String propertyName;
 			private final AggregationExpression aggregationExpression;
 
-			public PropertyExpression(String propertyName, AggregationExpression aggregationExpression) {
+			protected PropertyExpression(String propertyName, AggregationExpression aggregationExpression) {
+
+				Assert.notNull(propertyName, "Property name must not be null!");
+				Assert.notNull(aggregationExpression, "AggregationExpression must not be null!");
+
 				this.propertyName = propertyName;
 				this.aggregationExpression = aggregationExpression;
 			}
@@ -5209,7 +5413,9 @@ public PropertyExpression(String propertyName, AggregationExpression aggregation
 			 * @return
 			 */
 			public static AsBuilder property(final String name) {
+
 				return new AsBuilder() {
+
 					@Override
 					public PropertyExpression definedAs(AggregationExpression expression) {
 						return new PropertyExpression(name, expression);
@@ -5217,11 +5423,17 @@ public PropertyExpression definedAs(AggregationExpression expression) {
 				};
 			}
 
+			/* (non-Javadoc)
+			 * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+			 */
 			@Override
 			public DBObject toDbObject(AggregationOperationContext context) {
 				return new BasicDBObject(propertyName, aggregationExpression.toDbObject(context));
 			}
 
+			/**
+			 * @author Christoph Strobl
+			 */
 			interface AsBuilder {
 
 				/**
@@ -5235,6 +5447,7 @@ interface AsBuilder {
 		}
 
 		public enum Variable implements Field {
+
 			THIS {
 				@Override
 				public String getName() {
@@ -5256,6 +5469,7 @@ public String toString() {
 					return getName();
 				}
 			},
+
 			VALUE {
 				@Override
 				public String getName() {
@@ -5285,7 +5499,7 @@ public String toString() {
 			 * @param property must not be {@literal null}.
 			 * @return
 			 */
-			public Field referingTo(final String property) {
+			public Field referringTo(final String property) {
 
 				return new Field() {
 					@Override
@@ -5314,6 +5528,8 @@ public String toString() {
 
 	/**
 	 * {@link AggregationExpression} for {@code $zip}.
+	 *
+	 * @author Christoph Strobl
 	 */
 	class Zip extends AbstractAggregationExpression {
 
@@ -5399,7 +5615,7 @@ public static class ZipBuilder {
 
 			private final List<Object> sourceArrays;
 
-			public ZipBuilder(Object sourceArray) {
+			private ZipBuilder(Object sourceArray) {
 
 				this.sourceArrays = new ArrayList<Object>();
 				this.sourceArrays.add(sourceArray);
@@ -5408,7 +5624,7 @@ public ZipBuilder(Object sourceArray) {
 			/**
 			 * Creates new {@link Zip} that transposes an array of input arrays so that the first element of the output array
 			 * would be an array containing, the first element of the first input array, the first element of the second input
-			 * array, etc
+			 * array, etc.
 			 *
 			 * @param arrays arrays to zip the referenced one with. must not be {@literal null}.
 			 * @return
@@ -5432,6 +5648,8 @@ public Zip zip(Object... arrays) {
 
 	/**
 	 * {@link AggregationExpression} for {@code $in}.
+	 *
+	 * @author Christoph Strobl
 	 */
 	class In extends AbstractAggregationExpression {
 
@@ -5444,10 +5662,18 @@ protected String getMongoMethod() {
 			return "$in";
 		}
 
+		/**
+		 * Start creating {@link In}.
+		 *
+		 * @param fieldReference must not be {@literal null}.
+		 * @return
+		 */
 		public static InBuilder arrayOf(final String fieldReference) {
 
 			Assert.notNull(fieldReference, "FieldReference must not be null!");
+
 			return new InBuilder() {
+
 				@Override
 				public In containsValue(Object value) {
 
@@ -5457,10 +5683,18 @@ public In containsValue(Object value) {
 			};
 		}
 
+		/**
+		 * Start creating {@link In}.
+		 *
+		 * @param expression must not be {@literal null}.
+		 * @return
+		 */
 		public static InBuilder arrayOf(final AggregationExpression expression) {
 
 			Assert.notNull(expression, "Expression must not be null!");
+
 			return new InBuilder() {
+
 				@Override
 				public In containsValue(Object value) {
 
@@ -5470,10 +5704,19 @@ public In containsValue(Object value) {
 			};
 		}
 
+		/**
+		 * @author Christoph Strobl
+		 */
 		public interface InBuilder {
+
+			/**
+			 * Set the {@literal value} to check for existence in the array.
+			 *
+			 * @param value must not be {@literal value}.
+			 * @return
+			 */
 			In containsValue(Object value);
 		}
-
 	}
 
 	// ############
@@ -5944,7 +6187,9 @@ protected String getMongoMethod() {
 		public static FormatBuilder dateOf(final String fieldReference) {
 
 			Assert.notNull(fieldReference, "FieldReference must not be null!");
+
 			return new FormatBuilder() {
+
 				@Override
 				public DateToString toString(String format) {
 
@@ -5963,12 +6208,13 @@ public DateToString toString(String format) {
 		public static FormatBuilder dateOf(final AggregationExpression expression) {
 
 			Assert.notNull(expression, "Expression must not be null!");
+
 			return new FormatBuilder() {
+
 				@Override
 				public DateToString toString(String format) {
 
 					Assert.notNull(format, "Format must not be null!");
-
 					return new DateToString(argumentMap(expression, format));
 				}
 			};
@@ -6183,6 +6429,9 @@ public Sum and(AggregationExpression expression) {
 			return new Sum(append(expression));
 		}
 
+		/* (non-Javadoc)
+		 * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+		 */
 		@Override
 		public DBObject toDbObject(Object value, AggregationOperationContext context) {
 
@@ -6262,6 +6511,9 @@ public Avg and(AggregationExpression expression) {
 			return new Avg(append(expression));
 		}
 
+		/* (non-Javadoc)
+		 * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+		 */
 		@Override
 		public DBObject toDbObject(Object value, AggregationOperationContext context) {
 
@@ -6341,6 +6593,9 @@ public Max and(AggregationExpression expression) {
 			return new Max(append(expression));
 		}
 
+		/* (non-Javadoc)
+		 * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+		 */
 		@Override
 		public DBObject toDbObject(Object value, AggregationOperationContext context) {
 
@@ -6420,6 +6675,9 @@ public Min and(AggregationExpression expression) {
 			return new Min(append(expression));
 		}
 
+		/* (non-Javadoc)
+		 * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+		 */
 		@Override
 		public DBObject toDbObject(Object value, AggregationOperationContext context) {
 
@@ -6499,6 +6757,9 @@ public StdDevPop and(AggregationExpression expression) {
 			return new StdDevPop(append(expression));
 		}
 
+		/* (non-Javadoc)
+		 * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+		 */
 		@Override
 		public DBObject toDbObject(Object value, AggregationOperationContext context) {
 
@@ -6578,6 +6839,9 @@ public StdDevSamp and(AggregationExpression expression) {
 			return new StdDevSamp(append(expression));
 		}
 
+		/* (non-Javadoc)
+		 * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+		 */
 		@Override
 		public DBObject toDbObject(Object value, AggregationOperationContext context) {
 
@@ -7332,15 +7596,21 @@ private Map(Object sourceArray, String itemVariableName, AggregationExpression f
 		 */
 		static AsBuilder itemsOf(final String fieldReference) {
 
+			Assert.notNull(fieldReference, "FieldReference must not be null!");
+
 			return new AsBuilder() {
 
 				@Override
 				public FunctionBuilder as(final String variableName) {
 
+					Assert.notNull(variableName, "VariableName must not be null!");
+
 					return new FunctionBuilder() {
 
 						@Override
 						public Map andApply(final AggregationExpression expression) {
+
+							Assert.notNull(expression, "AggregationExpression must not be null!");
 							return new Map(Fields.field(fieldReference), variableName, expression);
 						}
 					};
@@ -7358,15 +7628,21 @@ public Map andApply(final AggregationExpression expression) {
 		 */
 		public static AsBuilder itemsOf(final AggregationExpression source) {
 
+			Assert.notNull(source, "AggregationExpression must not be null!");
+
 			return new AsBuilder() {
 
 				@Override
 				public FunctionBuilder as(final String variableName) {
 
+					Assert.notNull(variableName, "VariableName must not be null!");
+
 					return new FunctionBuilder() {
 
 						@Override
 						public Map andApply(final AggregationExpression expression) {
+
+							Assert.notNull(expression, "AggregationExpression must not be null!");
 							return new Map(source, variableName, expression);
 						}
 					};
@@ -7374,6 +7650,9 @@ public Map andApply(final AggregationExpression expression) {
 			};
 		}
 
+		/* (non-Javadoc)
+		 * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+		 */
 		@Override
 		public DBObject toDbObject(final AggregationOperationContext context) {
 			return toMap(ExposedFields.synthetic(Fields.fields(itemVariableName)), context);
@@ -7446,8 +7725,8 @@ private IfNull(Object condition, Object value) {
 		/**
 		 * Creates new {@link IfNull}.
 		 *
-		 * @param fieldReference the field to check for a {@literal null} value, field reference must not be
-		 *          {@literal null}.
+		 * @param fieldReference the field to check for a {@literal null} value, field reference must not be {@literal null}
+		 *          .
 		 * @return
 		 */
 		public static ThenBuilder ifNull(String fieldReference) {
@@ -8095,6 +8374,7 @@ public static LetBuilder define(final Collection<ExpressionVariable> variables)
 			Assert.notNull(variables, "Variables must not be null!");
 
 			return new LetBuilder() {
+
 				@Override
 				public Let andApply(final AggregationExpression expression) {
 
@@ -8115,6 +8395,7 @@ public static LetBuilder define(final ExpressionVariable... variables) {
 			Assert.notNull(variables, "Variables must not be null!");
 
 			return new LetBuilder() {
+
 				@Override
 				public Let andApply(final AggregationExpression expression) {
 
@@ -8135,6 +8416,9 @@ public interface LetBuilder {
 			Let andApply(AggregationExpression expression);
 		}
 
+		/* (non-Javadoc)
+		 * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+		 */
 		@Override
 		public DBObject toDbObject(final AggregationOperationContext context) {
 			return toLet(ExposedFields.synthetic(Fields.fields(getVariableNames())), context);
@@ -8235,7 +8519,6 @@ public ExpressionVariable forExpression(DBObject expressionObject) {
 		}
 	}
 
-
 	/**
 	 * {@link AggregationExpression} for {@code $switch}.
 	 *
@@ -8252,12 +8535,22 @@ protected String getMongoMethod() {
 			return "$switch";
 		}
 
+		/**
+		 * Creates new {@link Switch}.
+		 *
+		 * @param conditions must not be {@literal null}.
+		 */
 		public static Switch switchCases(CaseOperator... conditions) {
 
 			Assert.notNull(conditions, "Conditions must not be null!");
 			return switchCases(Arrays.asList(conditions));
 		}
 
+		/**
+		 * Creates new {@link Switch}.
+		 *
+		 * @param conditions must not be {@literal null}.
+		 */
 		public static Switch switchCases(List<CaseOperator> conditions) {
 
 			Assert.notNull(conditions, "Conditions must not be null!");
@@ -8268,6 +8561,9 @@ public Switch defaultTo(Object value) {
 			return new Switch(append("default", value));
 		}
 
+		/**
+		 * Encapsulates the aggregation framework case document inside a {@code $switch}-operation.
+		 */
 		public static class CaseOperator implements AggregationExpression {
 
 			private final AggregationExpression when;
@@ -8282,7 +8578,9 @@ private CaseOperator(AggregationExpression when, Object then) {
 			public static ThenBuilder when(final AggregationExpression condition) {
 
 				Assert.notNull(condition, "Condition must not be null!");
+
 				return new ThenBuilder() {
+
 					@Override
 					public CaseOperator then(Object value) {
 
@@ -8292,8 +8590,12 @@ public CaseOperator then(Object value) {
 				};
 			}
 
+			/* (non-Javadoc)
+			 * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+			 */
 			@Override
 			public DBObject toDbObject(AggregationOperationContext context) {
+
 				DBObject dbo = new BasicDBObject("case", when.toDbObject(context));
 
 				if (then instanceof AggregationExpression) {
@@ -8307,7 +8609,17 @@ public DBObject toDbObject(AggregationOperationContext context) {
 				return dbo;
 			}
 
+			/**
+			 * @author Christoph Strobl
+			 */
 			public interface ThenBuilder {
+
+				/**
+				 * Set the then {@literal value}.
+				 *
+				 * @param value must not be {@literal null}.
+				 * @return
+				 */
 				CaseOperator then(Object value);
 			}
 		}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java
index 5fff2b2e59..63d825134c 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java
@@ -1971,9 +1971,9 @@ public void shouldRenderReduceWithSimpleObjectCorrectly() {
 	public void shouldRenderReduceWithComplexObjectCorrectly() {
 
 		PropertyExpression sum = PropertyExpression.property("sum").definedAs(
-				ArithmeticOperators.valueOf(Variable.VALUE.referingTo("sum").getName()).add(Variable.THIS.getName()));
+				ArithmeticOperators.valueOf(Variable.VALUE.referringTo("sum").getName()).add(Variable.THIS.getName()));
 		PropertyExpression product = PropertyExpression.property("product").definedAs(ArithmeticOperators
-				.valueOf(Variable.VALUE.referingTo("product").getName()).multiplyBy(Variable.THIS.getName()));
+				.valueOf(Variable.VALUE.referringTo("product").getName()).multiplyBy(Variable.THIS.getName()));
 
 		DBObject agg = project()
 				.and(ArrayOperators.arrayOf("probabilityArr").reduce(sum, product)