private void modifyField(
      BSONObject document, String modifier, BSONObject change, Integer matchPos, boolean isUpsert)
      throws MongoServerException {

    if (!modifier.equals("$unset")) {
      for (String key : change.keySet()) {
        if (key.startsWith("$")) {
          throw new MongoServerError(15896, "Modified field name may not start with $");
        }
      }
    }

    if (modifier.equals("$set") || (modifier.equals("$setOnInsert") && isUpsert)) {
      for (String key : change.keySet()) {
        Object newValue = change.get(key);
        Object oldValue = getSubdocumentValue(document, key, matchPos);

        if (Utils.nullAwareEquals(newValue, oldValue)) {
          // no change
          continue;
        }

        assertNotKeyField(key);

        changeSubdocumentValue(document, key, newValue, matchPos);
      }
    } else if (modifier.equals("$setOnInsert")) {
      // no upsert → ignore
    } else if (modifier.equals("$unset")) {
      for (String key : change.keySet()) {
        assertNotKeyField(key);
        removeSubdocumentValue(document, key, matchPos);
      }
    } else if (modifier.equals("$push")
        || modifier.equals("$pushAll")
        || modifier.equals("$addToSet")) {
      updatePushAllAddToSet(document, modifier, change, matchPos);
    } else if (modifier.equals("$pull") || modifier.equals("$pullAll")) {
      // http://docs.mongodb.org/manual/reference/operator/pull/
      for (String key : change.keySet()) {
        Object value = getSubdocumentValue(document, key, matchPos);
        List<Object> list;
        if (value == null) {
          return;
        } else if (value instanceof List<?>) {
          list = Utils.asList(value);
        } else {
          throw new MongoServerError(10142, "Cannot apply " + modifier + " modifier to non-array");
        }

        Object pushValue = change.get(key);
        if (modifier.equals("$pullAll")) {
          if (!(pushValue instanceof Collection<?>)) {
            throw new MongoServerError(10153, "Modifier " + modifier + " allowed for arrays only");
          }
          @SuppressWarnings("unchecked")
          Collection<Object> valueList = (Collection<Object>) pushValue;
          do {} while (list.removeAll(valueList));
        } else {
          do {} while (list.remove(pushValue));
        }
        // no need to put something back
      }
    } else if (modifier.equals("$pop")) {
      for (String key : change.keySet()) {
        Object value = getSubdocumentValue(document, key, matchPos);
        List<Object> list;
        if (value == null) {
          return;
        } else if (value instanceof List<?>) {
          list = Utils.asList(value);
        } else {
          throw new MongoServerError(10143, "Cannot apply " + modifier + " modifier to non-array");
        }

        Object pushValue = change.get(key);
        if (!list.isEmpty()) {
          if (pushValue != null && Utils.normalizeValue(pushValue).equals(Double.valueOf(-1.0))) {
            list.remove(0);
          } else {
            list.remove(list.size() - 1);
          }
        }
        // no need to put something back
      }
    } else if (modifier.equals("$inc")) {
      // http://docs.mongodb.org/manual/reference/operator/inc/
      for (String key : change.keySet()) {
        assertNotKeyField(key);

        Object value = getSubdocumentValue(document, key, matchPos);
        Number number;
        if (value == null) {
          number = Integer.valueOf(0);
        } else if (value instanceof Number) {
          number = (Number) value;
        } else {
          throw new MongoServerException("can not increment '" + value + "'");
        }

        changeSubdocumentValue(
            document, key, Utils.addNumbers(number, (Number) change.get(key)), matchPos);
      }
    } else {
      throw new MongoServerError(10147, "Invalid modifier specified: " + modifier);
    }
  }