/**
   * Reads the given {@link DBObject} into a {@link Map}. will recursively resolve nested {@link
   * Map}s as well.
   *
   * @param type the {@link Map} {@link TypeInformation} to be used to unmarshall this {@link
   *     DBObject}.
   * @param dbObject must not be {@literal null}
   * @param path must not be {@literal null}
   * @return
   */
  @SuppressWarnings("unchecked")
  protected Map<Object, Object> readMap(
      TypeInformation<?> type, DBObject dbObject, ObjectPath path) {

    Assert.notNull(dbObject, "DBObject must not be null!");
    Assert.notNull(path, "Object path must not be null!");

    Class<?> mapType = typeMapper.readType(dbObject, type).getType();

    TypeInformation<?> keyType = type.getComponentType();
    Class<?> rawKeyType = keyType == null ? null : keyType.getType();

    TypeInformation<?> valueType = type.getMapValueType();
    Class<?> rawValueType = valueType == null ? null : valueType.getType();

    Map<Object, Object> map =
        CollectionFactory.createMap(mapType, rawKeyType, dbObject.keySet().size());
    Map<String, Object> sourceMap = dbObject.toMap();

    for (Entry<String, Object> entry : sourceMap.entrySet()) {
      if (typeMapper.isTypeKey(entry.getKey())) {
        continue;
      }

      Object key = potentiallyUnescapeMapKey(entry.getKey());

      if (rawKeyType != null) {
        key = conversionService.convert(key, rawKeyType);
      }

      Object value = entry.getValue();

      if (value instanceof DBObject) {
        map.put(key, read(valueType, (DBObject) value, path));
      } else if (value instanceof DBRef) {
        map.put(
            key,
            DBRef.class.equals(rawValueType) ? value : read(valueType, readRef((DBRef) value)));
      } else {
        Class<?> valueClass = valueType == null ? null : valueType.getType();
        map.put(key, getPotentiallyConvertedSimpleRead(value, valueClass));
      }
    }

    return map;
  }
  /**
   * Reads the given {@link BasicDBList} into a collection of the given {@link TypeInformation}.
   *
   * @param targetType must not be {@literal null}.
   * @param sourceValue must not be {@literal null}.
   * @param path must not be {@literal null}.
   * @return the converted {@link Collection} or array, will never be {@literal null}.
   */
  private Object readCollectionOrArray(
      TypeInformation<?> targetType, BasicDBList sourceValue, ObjectPath path) {

    Assert.notNull(targetType, "Target type must not be null!");
    Assert.notNull(path, "Object path must not be null!");

    Class<?> collectionType = targetType.getType();

    if (sourceValue.isEmpty()) {
      return getPotentiallyConvertedSimpleRead(new HashSet<Object>(), collectionType);
    }

    TypeInformation<?> componentType = targetType.getComponentType();
    Class<?> rawComponentType = componentType == null ? null : componentType.getType();

    collectionType =
        Collection.class.isAssignableFrom(collectionType) ? collectionType : List.class;
    Collection<Object> items =
        targetType.getType().isArray()
            ? new ArrayList<Object>()
            : CollectionFactory.createCollection(
                collectionType, rawComponentType, sourceValue.size());

    for (int i = 0; i < sourceValue.size(); i++) {

      Object dbObjItem = sourceValue.get(i);

      if (dbObjItem instanceof DBRef) {
        items.add(
            DBRef.class.equals(rawComponentType)
                ? dbObjItem
                : read(componentType, readRef((DBRef) dbObjItem), path));
      } else if (dbObjItem instanceof DBObject) {
        items.add(read(componentType, (DBObject) dbObjItem, path));
      } else {
        items.add(getPotentiallyConvertedSimpleRead(dbObjItem, rawComponentType));
      }
    }

    return getPotentiallyConvertedSimpleRead(items, targetType.getType());
  }
  /**
   * Populates the given {@link BasicDBList} with values from the given {@link Collection}.
   *
   * @param source the collection to create a {@link BasicDBList} for, must not be {@literal null}.
   * @param type the {@link TypeInformation} to consider or {@literal null} if unknown.
   * @param sink the {@link BasicDBList} to write to.
   * @return
   */
  private BasicDBList writeCollectionInternal(
      Collection<?> source, TypeInformation<?> type, BasicDBList sink) {

    TypeInformation<?> componentType = type == null ? null : type.getComponentType();

    for (Object element : source) {

      Class<?> elementType = element == null ? null : element.getClass();

      if (elementType == null || conversions.isSimpleType(elementType)) {
        sink.add(getPotentiallyConvertedSimpleWrite(element));
      } else if (element instanceof Collection || elementType.isArray()) {
        sink.add(writeCollectionInternal(asCollection(element), componentType, new BasicDBList()));
      } else {
        BasicDBObject propDbObj = new BasicDBObject();
        writeInternal(element, propDbObj, componentType);
        sink.add(propDbObj);
      }
    }

    return sink;
  }