org.springframework.data.mongodb.core.query.Update.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.data.mongodb.core.query.Update.java

Source

/*
 * Copyright 2010-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.mongodb.core.query;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.bson.Document;

import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * Class to easily construct MongoDB update clauses.
 *
 * @author Thomas Risberg
 * @author Mark Pollack
 * @author Oliver Gierke
 * @author Becca Gaspard
 * @author Christoph Strobl
 * @author Thomas Darimont
 * @author Alexey Plotnik
 * @author Mark Paluch
 * @author Pavel Vodrazka
 */
public class Update implements UpdateDefinition {

    public enum Position {
        LAST, FIRST
    }

    private boolean isolated = false;
    private Set<String> keysToUpdate = new HashSet<>();
    private Map<String, Object> modifierOps = new LinkedHashMap<>();
    private Map<String, PushOperatorBuilder> pushCommandBuilders = new LinkedHashMap<>(1);
    private List<ArrayFilter> arrayFilters = new ArrayList<>();

    /**
     * Static factory method to create an Update using the provided key
     *
     * @param key
     * @return
     */
    public static Update update(String key, Object value) {
        return new Update().set(key, value);
    }

    /**
     * Creates an {@link Update} instance from the given {@link Document}. Allows to explicitly exclude fields from making
     * it into the created {@link Update} object. Note, that this will set attributes directly and <em>not</em> use
     * {@literal $set}. This means fields not given in the {@link Document} will be nulled when executing the update. To
     * create an only-updating {@link Update} instance of a {@link Document}, call {@link #set(String, Object)} for each
     * value in it.
     *
     * @param object the source {@link Document} to create the update from.
     * @param exclude the fields to exclude.
     * @return
     */
    public static Update fromDocument(Document object, String... exclude) {

        Update update = new Update();
        List<String> excludeList = Arrays.asList(exclude);

        for (String key : object.keySet()) {

            if (excludeList.contains(key)) {
                continue;
            }

            Object value = object.get(key);
            update.modifierOps.put(key, value);
            if (isKeyword(key) && value instanceof Document) {
                update.keysToUpdate.addAll(((Document) value).keySet());
            } else {
                update.keysToUpdate.add(key);
            }
        }

        return update;
    }

    /**
     * Update using the {@literal $set} update modifier
     *
     * @param key
     * @param value
     * @return
     * @see <a href="https://docs.mongodb.com/manual/reference/operator/update/set/">MongoDB Update operator: $set</a>
     */
    public Update set(String key, Object value) {
        addMultiFieldOperation("$set", key, value);
        return this;
    }

    /**
     * Update using the {@literal $setOnInsert} update modifier
     *
     * @param key
     * @param value
     * @return
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/setOnInsert/">MongoDB Update operator:
     *      $setOnInsert</a>
     */
    public Update setOnInsert(String key, Object value) {
        addMultiFieldOperation("$setOnInsert", key, value);
        return this;
    }

    /**
     * Update using the {@literal $unset} update modifier
     *
     * @param key
     * @return
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/unset/">MongoDB Update operator: $unset</a>
     */
    public Update unset(String key) {
        addMultiFieldOperation("$unset", key, 1);
        return this;
    }

    /**
     * Update using the {@literal $inc} update modifier
     *
     * @param key
     * @param inc
     * @return
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/inc/">MongoDB Update operator: $inc</a>
     */
    public Update inc(String key, Number inc) {
        addMultiFieldOperation("$inc", key, inc);
        return this;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.mongodb.core.query.UpdateDefinition#inc()
     */
    @Override
    public void inc(String key) {
        inc(key, 1L);
    }

    /**
     * Update using the {@literal $push} update modifier
     *
     * @param key
     * @param value
     * @return
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/push/">MongoDB Update operator: $push</a>
     */
    public Update push(String key, Object value) {
        addMultiFieldOperation("$push", key, value);
        return this;
    }

    /**
     * Update using {@code $push} modifier. <br/>
     * Allows creation of {@code $push} command for single or multiple (using {@code $each}) values as well as using
     * {@code $position}.
     *
     * @param key
     * @return {@link PushOperatorBuilder} for given key
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/push/">MongoDB Update operator: $push</a>
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/each/">MongoDB Update operator: $each</a>
     */
    public PushOperatorBuilder push(String key) {

        if (!pushCommandBuilders.containsKey(key)) {
            pushCommandBuilders.put(key, new PushOperatorBuilder(key));
        }
        return pushCommandBuilders.get(key);
    }

    /**
     * Update using the {@code $pushAll} update modifier. <br>
     * <b>Note</b>: In MongoDB 2.4 the usage of {@code $pushAll} has been deprecated in favor of {@code $push $each}.
     * <b>Important:</b> As of MongoDB 3.6 {@code $pushAll} is not longer supported. Use {@code $push $each} instead.
     * {@link #push(String)}) returns a builder that can be used to populate the {@code $each} object.
     *
     * @param key
     * @param values
     * @return
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/pushAll/">MongoDB Update operator:
     *      $pushAll</a>
     * @deprecated as of MongoDB 2.4. Removed in MongoDB 3.6. Use {@link #push(String) $push $each} instead.
     */
    @Deprecated
    public Update pushAll(String key, Object[] values) {
        addMultiFieldOperation("$pushAll", key, Arrays.asList(values));
        return this;
    }

    /**
     * Update using {@code $addToSet} modifier. <br/>
     * Allows creation of {@code $push} command for single or multiple (using {@code $each}) values
     *
     * @param key
     * @return
     * @since 1.5
     */
    public AddToSetBuilder addToSet(String key) {
        return new AddToSetBuilder(key);
    }

    /**
     * Update using the {@literal $addToSet} update modifier
     *
     * @param key
     * @param value
     * @return
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/addToSet/">MongoDB Update operator:
     *      $addToSet</a>
     */
    public Update addToSet(String key, Object value) {
        addMultiFieldOperation("$addToSet", key, value);
        return this;
    }

    /**
     * Update using the {@literal $pop} update modifier
     *
     * @param key
     * @param pos
     * @return
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/pop/">MongoDB Update operator: $pop</a>
     */
    public Update pop(String key, Position pos) {
        addMultiFieldOperation("$pop", key, pos == Position.FIRST ? -1 : 1);
        return this;
    }

    /**
     * Update using the {@literal $pull} update modifier
     *
     * @param key
     * @param value
     * @return
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/pull/">MongoDB Update operator: $pull</a>
     */
    public Update pull(String key, Object value) {
        addMultiFieldOperation("$pull", key, value);
        return this;
    }

    /**
     * Update using the {@literal $pullAll} update modifier
     *
     * @param key
     * @param values
     * @return
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/pullAll/">MongoDB Update operator:
     *      $pullAll</a>
     */
    public Update pullAll(String key, Object[] values) {
        addMultiFieldOperation("$pullAll", key, Arrays.asList(values));
        return this;
    }

    /**
     * Update using the {@literal $rename} update modifier
     *
     * @param oldName
     * @param newName
     * @return
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/rename/">MongoDB Update operator:
     *      $rename</a>
     */
    public Update rename(String oldName, String newName) {
        addMultiFieldOperation("$rename", oldName, newName);
        return this;
    }

    /**
     * Update given key to current date using {@literal $currentDate} modifier.
     *
     * @param key
     * @return
     * @since 1.6
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/currentDate/">MongoDB Update operator:
     *      $currentDate</a>
     */
    public Update currentDate(String key) {

        addMultiFieldOperation("$currentDate", key, true);
        return this;
    }

    /**
     * Update given key to current date using {@literal $currentDate : &#123; $type : "timestamp" &#125;} modifier.
     *
     * @param key
     * @return
     * @since 1.6
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/currentDate/">MongoDB Update operator:
     *      $currentDate</a>
     */
    public Update currentTimestamp(String key) {

        addMultiFieldOperation("$currentDate", key, new Document("$type", "timestamp"));
        return this;
    }

    /**
     * Multiply the value of given key by the given number.
     *
     * @param key must not be {@literal null}.
     * @param multiplier must not be {@literal null}.
     * @return
     * @since 1.7
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/mul/">MongoDB Update operator: $mul</a>
     */
    public Update multiply(String key, Number multiplier) {

        Assert.notNull(multiplier, "Multiplier must not be null.");
        addMultiFieldOperation("$mul", key, multiplier.doubleValue());
        return this;
    }

    /**
     * Update given key to the {@code value} if the {@code value} is greater than the current value of the field.
     *
     * @param key must not be {@literal null}.
     * @param value must not be {@literal null}.
     * @return
     * @since 1.10
     * @see <a href="https://docs.mongodb.com/manual/reference/bson-type-comparison-order/">Comparison/Sort Order</a>
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/max/">MongoDB Update operator: $max</a>
     */
    public Update max(String key, Object value) {

        Assert.notNull(value, "Value for max operation must not be null.");
        addMultiFieldOperation("$max", key, value);
        return this;
    }

    /**
     * Update given key to the {@code value} if the {@code value} is less than the current value of the field.
     *
     * @param key must not be {@literal null}.
     * @param value must not be {@literal null}.
     * @return
     * @since 1.10
     * @see <a href="https://docs.mongodb.com/manual/reference/bson-type-comparison-order/">Comparison/Sort Order</a>
     * @see <a href="https://docs.mongodb.org/manual/reference/operator/update/min/">MongoDB Update operator: $min</a>
     */
    public Update min(String key, Object value) {

        Assert.notNull(value, "Value for min operation must not be null.");
        addMultiFieldOperation("$min", key, value);
        return this;
    }

    /**
     * The operator supports bitwise {@code and}, bitwise {@code or}, and bitwise {@code xor} operations.
     *
     * @param key
     * @return
     * @since 1.7
     */
    public BitwiseOperatorBuilder bitwise(String key) {
        return new BitwiseOperatorBuilder(this, key);
    }

    /**
     * Prevents a write operation that affects <strong>multiple</strong> documents from yielding to other reads or writes
     * once the first document is written. <br />
     * Use with {@link org.springframework.data.mongodb.core.MongoOperations#updateMulti(Query, Update, Class)}.
     *
     * @return never {@literal null}.
     * @since 2.0
     */
    public Update isolated() {

        isolated = true;
        return this;
    }

    /**
     * Filter elements in an array that match the given criteria for update. {@link CriteriaDefinition} is passed directly
     * to the driver without further type or field mapping.
     *
     * @param criteria must not be {@literal null}.
     * @return this.
     * @since 2.2
     */
    public Update filterArray(CriteriaDefinition criteria) {

        this.arrayFilters.add(criteria::getCriteriaObject);
        return this;
    }

    /**
     * Filter elements in an array that match the given criteria for update. {@code expression} is used directly with the
     * driver without further further type or field mapping.
     *
     * @param identifier the positional operator identifier filter criteria name.
     * @param expression the positional operator filter expression.
     * @return this.
     * @since 2.2
     */
    public Update filterArray(String identifier, Object expression) {

        this.arrayFilters.add(() -> new Document(identifier, expression));
        return this;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.mongodb.core.query.UpdateDefinition#isIsolated()
     */
    public Boolean isIsolated() {
        return isolated;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.mongodb.core.query.UpdateDefinition#getUpdateObject()
     */
    public Document getUpdateObject() {
        return new Document(modifierOps);
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.mongodb.core.query.UpdateDefinition#getArrayFilters()
     */
    public List<ArrayFilter> getArrayFilters() {
        return Collections.unmodifiableList(this.arrayFilters);
    }

    /**
     * This method is not called anymore rather override {@link #addMultiFieldOperation(String, String, Object)}.
     *
     * @param operator
     * @param key
     * @param value
     * @deprectaed Use {@link #addMultiFieldOperation(String, String, Object)} instead.
     */
    @Deprecated
    protected void addFieldOperation(String operator, String key, Object value) {

        Assert.hasText(key, "Key/Path for update must not be null or blank.");

        modifierOps.put(operator, new Document(key, value));
        this.keysToUpdate.add(key);
    }

    protected void addMultiFieldOperation(String operator, String key, Object value) {

        Assert.hasText(key, "Key/Path for update must not be null or blank.");
        Object existingValue = this.modifierOps.get(operator);
        Document keyValueMap;

        if (existingValue == null) {
            keyValueMap = new Document();
            this.modifierOps.put(operator, keyValueMap);
        } else {
            if (existingValue instanceof Document) {
                keyValueMap = (Document) existingValue;
            } else {
                throw new InvalidDataAccessApiUsageException(
                        "Modifier Operations should be a LinkedHashMap but was " + existingValue.getClass());
            }
        }

        keyValueMap.put(key, value);
        this.keysToUpdate.add(key);
    }

    /**
     * Determine if a given {@code key} will be touched on execution.
     *
     * @param key
     * @return
     */
    public boolean modifies(String key) {
        return this.keysToUpdate.contains(key);
    }

    /**
     * Inspects given {@code key} for '$'.
     *
     * @param key
     * @return
     */
    private static boolean isKeyword(String key) {
        return StringUtils.startsWithIgnoreCase(key, "$");
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        return Objects.hash(getUpdateObject(), isolated);
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {

        if (this == obj) {
            return true;
        }

        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        Update that = (Update) obj;
        if (this.isolated != that.isolated) {
            return false;
        }

        return Objects.equals(this.getUpdateObject(), that.getUpdateObject());
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {

        Document doc = getUpdateObject();

        if (isIsolated()) {
            doc.append("$isolated", 1);
        }

        return SerializationUtils.serializeToJsonSafely(doc);
    }

    /**
     * Modifiers holds a distinct collection of {@link Modifier}
     *
     * @author Christoph Strobl
     * @author Thomas Darimont
     */
    public static class Modifiers {

        private Map<String, Modifier> modifiers;

        public Modifiers() {
            this.modifiers = new LinkedHashMap<>(1);
        }

        public Collection<Modifier> getModifiers() {
            return Collections.unmodifiableCollection(this.modifiers.values());
        }

        public void addModifier(Modifier modifier) {
            this.modifiers.put(modifier.getKey(), modifier);
        }

        /**
         * @return true if no modifiers present.
         * @since 2.0
         */
        public boolean isEmpty() {
            return modifiers.isEmpty();
        }

        /*
         * (non-Javadoc)
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            return Objects.hashCode(modifiers);
        }

        /*
         * (non-Javadoc)
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj) {

            if (this == obj) {
                return true;
            }

            if (obj == null || getClass() != obj.getClass()) {
                return false;
            }

            Modifiers that = (Modifiers) obj;
            return Objects.equals(this.modifiers, that.modifiers);
        }

        @Override
        public String toString() {
            return SerializationUtils.serializeToJsonSafely(this.modifiers);
        }
    }

    /**
     * Marker interface of nested commands.
     *
     * @author Christoph Strobl
     */
    public interface Modifier {

        /**
         * @return the command to send eg. {@code $push}
         */
        String getKey();

        /**
         * @return value to be sent with command
         */
        Object getValue();

        /**
         * @return a safely serialized JSON representation.
         * @since 2.0
         */
        default String toJsonString() {
            return SerializationUtils.serializeToJsonSafely(Collections.singletonMap(getKey(), getValue()));
        }
    }

    /**
     * Abstract {@link Modifier} implementation with defaults for {@link Object#equals(Object)}, {@link Object#hashCode()}
     * and {@link Object#toString()}.
     *
     * @author Christoph Strobl
     * @since 2.0
     */
    private static abstract class AbstractModifier implements Modifier {

        /*
         * (non-Javadoc)
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            return ObjectUtils.nullSafeHashCode(getKey()) + ObjectUtils.nullSafeHashCode(getValue());
        }

        /*
         * (non-Javadoc)
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object that) {

            if (this == that) {
                return true;
            }

            if (that == null || getClass() != that.getClass()) {
                return false;
            }

            if (!Objects.equals(getKey(), ((Modifier) that).getKey())) {
                return false;
            }

            return Objects.deepEquals(getValue(), ((Modifier) that).getValue());
        }

        /*
         * (non-Javadoc)
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return toJsonString();
        }
    }

    /**
     * Implementation of {@link Modifier} representing {@code $each}.
     *
     * @author Christoph Strobl
     * @author Thomas Darimont
     */
    private static class Each extends AbstractModifier {

        private Object[] values;

        Each(Object... values) {
            this.values = extractValues(values);
        }

        private Object[] extractValues(Object[] values) {

            if (values == null || values.length == 0) {
                return values;
            }

            if (values.length == 1 && values[0] instanceof Collection) {
                return ((Collection<?>) values[0]).toArray();
            }

            return Arrays.copyOf(values, values.length);
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.core.query.Update.Modifier#getKey()
         */
        @Override
        public String getKey() {
            return "$each";
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.core.query.Update.Modifier#getValue()
         */
        @Override
        public Object getValue() {
            return this.values;
        }
    }

    /**
     * {@link Modifier} implementation used to propagate {@code $position}.
     *
     * @author Christoph Strobl
     * @since 1.7
     */
    private static class PositionModifier extends AbstractModifier {

        private final int position;

        PositionModifier(int position) {
            this.position = position;
        }

        @Override
        public String getKey() {
            return "$position";
        }

        @Override
        public Object getValue() {
            return position;
        }
    }

    /**
     * Implementation of {@link Modifier} representing {@code $slice}.
     *
     * @author Mark Paluch
     * @since 1.10
     */
    private static class Slice extends AbstractModifier {

        private int count;

        Slice(int count) {
            this.count = count;
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.core.query.Update.Modifier#getKey()
         */
        @Override
        public String getKey() {
            return "$slice";
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.core.query.Update.Modifier#getValue()
         */
        @Override
        public Object getValue() {
            return this.count;
        }
    }

    /**
     * Implementation of {@link Modifier} representing {@code $sort}.
     *
     * @author Pavel Vodrazka
     * @author Mark Paluch
     * @since 1.10
     */
    private static class SortModifier extends AbstractModifier {

        private final Object sort;

        /**
         * Creates a new {@link SortModifier} instance given {@link Direction}.
         *
         * @param direction must not be {@literal null}.
         */
        SortModifier(Direction direction) {

            Assert.notNull(direction, "Direction must not be null!");
            this.sort = direction.isAscending() ? 1 : -1;
        }

        /**
         * Creates a new {@link SortModifier} instance given {@link Sort}.
         *
         * @param sort must not be {@literal null}.
         */
        SortModifier(Sort sort) {

            Assert.notNull(sort, "Sort must not be null!");

            for (Order order : sort) {

                if (order.isIgnoreCase()) {
                    throw new IllegalArgumentException(String.format(
                            "Given sort contained an Order for %s with ignore case! "
                                    + "MongoDB does not support sorting ignoring case currently!",
                            order.getProperty()));
                }
            }

            this.sort = sort;
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.core.query.Update.Modifier#getKey()
         */
        @Override
        public String getKey() {
            return "$sort";
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.core.query.Update.Modifier#getValue()
         */
        @Override
        public Object getValue() {
            return this.sort;
        }
    }

    /**
     * Builder for creating {@code $push} modifiers
     *
     * @author Christoph Strobl
     * @author Thomas Darimont
     */
    public class PushOperatorBuilder {

        private final String key;
        private final Modifiers modifiers;

        PushOperatorBuilder(String key) {
            this.key = key;
            this.modifiers = new Modifiers();
        }

        /**
         * Propagates {@code $each} to {@code $push}
         *
         * @param values
         * @return never {@literal null}.
         */
        public Update each(Object... values) {

            this.modifiers.addModifier(new Each(values));
            return Update.this.push(key, this.modifiers);
        }

        /**
         * Propagates {@code $slice} to {@code $push}. {@code $slice} requires the {@code $each operator}. <br />
         * If {@literal count} is zero, {@code $slice} updates the array to an empty array. <br />
         * If {@literal count} is negative, {@code $slice} updates the array to contain only the last {@code count}
         * elements. <br />
         * If {@literal count} is positive, {@code $slice} updates the array to contain only the first {@code count}
         * elements. <br />
         *
         * @param count
         * @return never {@literal null}.
         * @since 1.10
         */
        public PushOperatorBuilder slice(int count) {

            this.modifiers.addModifier(new Slice(count));
            return this;
        }

        /**
         * Propagates {@code $sort} to {@code $push}. {@code $sort} requires the {@code $each} operator. Forces elements to
         * be sorted by values in given {@literal direction}.
         *
         * @param direction must not be {@literal null}.
         * @return never {@literal null}.
         * @since 1.10
         */
        public PushOperatorBuilder sort(Direction direction) {

            Assert.notNull(direction, "Direction must not be null.");
            this.modifiers.addModifier(new SortModifier(direction));
            return this;
        }

        /**
         * Propagates {@code $sort} to {@code $push}. {@code $sort} requires the {@code $each} operator. Forces document
         * elements to be sorted in given {@literal order}.
         *
         * @param sort must not be {@literal null}.
         * @return never {@literal null}.
         * @since 1.10
         */
        public PushOperatorBuilder sort(Sort sort) {

            Assert.notNull(sort, "Sort must not be null.");
            this.modifiers.addModifier(new SortModifier(sort));
            return this;
        }

        /**
         * Forces values to be added at the given {@literal position}.
         *
         * @param position the position offset. As of MongoDB 3.6 use a negative value to indicate starting from the end,
         *          counting (but not including) the last element of the array.
         * @return never {@literal null}.
         * @since 1.7
         */
        public PushOperatorBuilder atPosition(int position) {

            this.modifiers.addModifier(new PositionModifier(position));
            return this;
        }

        /**
         * Forces values to be added at given {@literal position}.
         *
         * @param position can be {@literal null} which will be appended at the last position.
         * @return never {@literal null}.
         * @since 1.7
         */
        public PushOperatorBuilder atPosition(@Nullable Position position) {

            if (position == null || Position.LAST.equals(position)) {
                return this;
            }

            this.modifiers.addModifier(new PositionModifier(0));

            return this;
        }

        /**
         * Propagates {@link #value(Object)} to {@code $push}
         *
         * @param value
         * @return never {@literal null}.
         */
        public Update value(Object value) {

            if (this.modifiers.isEmpty()) {
                return Update.this.push(key, value);
            }

            this.modifiers.addModifier(new Each(Collections.singletonList(value)));
            return Update.this.push(key, this.modifiers);
        }

        /*
         * (non-Javadoc)
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            return Objects.hash(getOuterType(), key, modifiers);
        }

        /*
         * (non-Javadoc)
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj) {

            if (this == obj) {
                return true;
            }

            if (obj == null || getClass() != obj.getClass()) {
                return false;
            }

            PushOperatorBuilder that = (PushOperatorBuilder) obj;

            if (!Objects.equals(getOuterType(), that.getOuterType())) {
                return false;
            }

            return Objects.equals(this.key, that.key) && Objects.equals(this.modifiers, that.modifiers);
        }

        private Update getOuterType() {
            return Update.this;
        }
    }

    /**
     * Builder for creating {@code $addToSet} modifier.
     *
     * @author Christoph Strobl
     * @since 1.5
     */
    public class AddToSetBuilder {

        private final String key;

        public AddToSetBuilder(String key) {
            this.key = key;
        }

        /**
         * Propagates {@code $each} to {@code $addToSet}
         *
         * @param values
         * @return
         */
        public Update each(Object... values) {
            return Update.this.addToSet(this.key, new Each(values));
        }

        /**
         * Propagates {@link #value(Object)} to {@code $addToSet}
         *
         * @param values
         * @return
         */
        public Update value(Object value) {
            return Update.this.addToSet(this.key, value);
        }
    }

    /**
     * @author Christoph Strobl
     * @since 1.7
     */
    public static class BitwiseOperatorBuilder {

        private final String key;
        private final Update reference;
        private static final String BIT_OPERATOR = "$bit";

        private enum BitwiseOperator {
            AND, OR, XOR;

            @Override
            public String toString() {
                return super.toString().toLowerCase();
            };
        }

        /**
         * Creates a new {@link BitwiseOperatorBuilder}.
         *
         * @param reference must not be {@literal null}
         * @param key must not be {@literal null}
         */
        protected BitwiseOperatorBuilder(Update reference, String key) {

            Assert.notNull(reference, "Reference must not be null!");
            Assert.notNull(key, "Key must not be null!");

            this.reference = reference;
            this.key = key;
        }

        /**
         * Updates to the result of a bitwise and operation between the current value and the given one.
         *
         * @param value
         * @return
         */
        public Update and(long value) {

            addFieldOperation(BitwiseOperator.AND, value);
            return reference;
        }

        /**
         * Updates to the result of a bitwise or operation between the current value and the given one.
         *
         * @param value
         * @return
         */
        public Update or(long value) {

            addFieldOperation(BitwiseOperator.OR, value);
            return reference;
        }

        /**
         * Updates to the result of a bitwise xor operation between the current value and the given one.
         *
         * @param value
         * @return
         */
        public Update xor(long value) {

            addFieldOperation(BitwiseOperator.XOR, value);
            return reference;
        }

        private void addFieldOperation(BitwiseOperator operator, Number value) {
            reference.addMultiFieldOperation(BIT_OPERATOR, key, new Document(operator.toString(), value));
        }
    }
}