All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.pincette.mongo.streams.Unwind Maven / Gradle / Ivy

package net.pincette.mongo.streams;

import static java.util.UUID.randomUUID;
import static javax.json.JsonValue.NULL;
import static net.pincette.json.JsonUtil.asString;
import static net.pincette.json.JsonUtil.createObjectBuilder;
import static net.pincette.json.JsonUtil.createValue;
import static net.pincette.json.JsonUtil.getArray;
import static net.pincette.json.JsonUtil.isObject;
import static net.pincette.json.JsonUtil.isString;
import static net.pincette.json.JsonUtil.toJsonPointer;
import static net.pincette.mongo.streams.Util.ID;
import static net.pincette.rs.Box.box;
import static net.pincette.rs.Chain.with;
import static net.pincette.rs.Filter.filter;
import static net.pincette.rs.Flatten.flatMap;
import static net.pincette.rs.streams.Message.message;
import static net.pincette.util.Builder.create;
import static net.pincette.util.StreamUtil.rangeExclusive;
import static net.pincette.util.StreamUtil.zip;
import static net.pincette.util.Util.must;

import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.Flow.Processor;
import java.util.concurrent.Flow.Publisher;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonValue;
import net.pincette.json.JsonUtil;
import net.pincette.rs.Source;
import net.pincette.rs.streams.Message;

/**
 * The $unwind operator.
 *
 * @author Werner Donné
 */
class Unwind {
  private static final String INCLUDE_ARRAY_INDEX = "includeArrayIndex";
  private static final String NEW_IDS = "newIds";
  private static final String PATH = "path";
  private static final String PRESERVE_NULL_AND_EMPTY_ARRAYS = "preserveNullAndEmptyArrays";

  private Unwind() {}

  private static Stream emptyArray(
      final JsonObject json, final String includeArrayIndex) {
    return Stream.of(
        create(() -> createObjectBuilder(json))
            .updateIf(b -> includeArrayIndex != null, b -> b.add(includeArrayIndex, NULL))
            .build()
            .build());
  }

  private static boolean hasArray(final JsonObject json, final String path) {
    return getArray(json, toJsonPointer(path)).filter(array -> !array.isEmpty()).isPresent();
  }

  private static Optional object(final JsonValue value) {
    return Optional.of(value).filter(JsonUtil::isObject).map(JsonValue::asJsonObject);
  }

  private static JsonObjectBuilder set(
      final JsonObject json, final String path, final JsonValue value, final String currentPath) {
    final String prefix = !currentPath.isEmpty() ? (currentPath + ".") : "";
    final Function, JsonValue> tryObject =
        e ->
            isObject(e.getValue())
                ? set(e.getValue().asJsonObject(), path, value, prefix + e.getKey()).build()
                : e.getValue();

    return json.entrySet().stream()
        .reduce(
            createObjectBuilder(),
            (b, e) ->
                b.add(e.getKey(), path.equals(prefix + e.getKey()) ? value : tryObject.apply(e)),
            (b1, b2) -> b1);
  }

  static Processor, Message> stage(
      final JsonValue expression) {
    must(isObject(expression) || isString(expression));

    final String includeArrayIndex =
        object(expression).map(json -> json.getString(INCLUDE_ARRAY_INDEX, null)).orElse(null);
    final boolean newIds =
        object(expression).map(json -> json.getBoolean(NEW_IDS, false)).orElse(false);
    final String path =
        (isString(expression)
                ? asString(expression).getString()
                : expression.asJsonObject().getString(PATH))
            .substring(1);
    final boolean preserveNullAndEmptyArrays =
        object(expression)
            .map(json -> json.getBoolean(PRESERVE_NULL_AND_EMPTY_ARRAYS, false))
            .orElse(false);

    return box(
        filter(m -> preserveNullAndEmptyArrays || hasArray(m.value, path)),
        flatMap(
            m ->
                with(unwind(m.value, path, includeArrayIndex, newIds))
                    .map(unwound -> message(newIds ? unwound.getString(ID) : m.key, unwound))
                    .get()));
  }

  private static Publisher unwind(
      final JsonObject json,
      final String path,
      final String includeArrayIndex,
      final boolean newIds) {
    return getArray(json, toJsonPointer(path))
        .filter(array -> !array.isEmpty())
        .map(array -> Source.of(unwind(json, path, array, includeArrayIndex, newIds)))
        .orElseGet(() -> Source.of(emptyArray(json, includeArrayIndex).toList()));
  }

  private static List unwind(
      final JsonObject json,
      final String path,
      final JsonArray array,
      final String includeArrayIndex,
      final boolean newIds) {
    return zip(array.stream(), rangeExclusive(0, array.size()))
        .map(
            pair ->
                create(() -> set(json, path, pair.first, ""))
                    .updateIf(b -> newIds, b -> b.add(ID, createValue(randomUUID().toString())))
                    .updateIf(
                        b -> includeArrayIndex != null, b -> b.add(includeArrayIndex, pair.second))
                    .build()
                    .build())
        .toList();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy