Skip to content

Commit

Permalink
Merge pull request #1860 from eclipse-ditto/feature/flatten-in-arrays…
Browse files Browse the repository at this point in the history
…-for-condition

Provide same behavior for filtering in arrays via search's "filter" for specified "condition"
  • Loading branch information
thjaeckle authored Jan 17, 2024
2 parents c3c18ee + b00db38 commit 5d0a5af
Show file tree
Hide file tree
Showing 13 changed files with 403 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
*/
package org.eclipse.ditto.base.model.entity.metadata;

import static org.eclipse.ditto.json.JsonFactory.newValue;
import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;
import static org.eclipse.ditto.json.JsonFactory.newValue;

import java.io.IOException;
import java.util.Iterator;
Expand All @@ -27,6 +27,7 @@
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
import org.eclipse.ditto.json.JsonArray;
import org.eclipse.ditto.json.JsonCollectors;
import org.eclipse.ditto.json.JsonFactory;
Expand All @@ -38,7 +39,6 @@
import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.json.SerializationContext;
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;

/**
* Immutable implementation of {@link org.eclipse.ditto.base.model.entity.metadata.Metadata}.
Expand Down Expand Up @@ -211,6 +211,11 @@ public boolean contains(final CharSequence key) {
return wrapped.contains(key);
}

@Override
public boolean containsFlatteningArrays(final CharSequence key) {
return wrapped.containsFlatteningArrays(key);
}

@Override
public JsonObject get(final JsonPointer pointer) {
return wrapped.get(pointer);
Expand Down Expand Up @@ -241,6 +246,11 @@ public Optional<JsonValue> getValue(final CharSequence key) {
return wrapped.getValue(key);
}

@Override
public Optional<JsonValue> getValueFlatteningArrays(final CharSequence key) {
return wrapped.getValueFlatteningArrays(key);
}

@Override
public List<JsonKey> getKeys() {
return wrapped.getKeys();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
import org.eclipse.ditto.json.JsonArray;
import org.eclipse.ditto.json.JsonFactory;
import org.eclipse.ditto.json.JsonField;
Expand All @@ -35,7 +36,6 @@
import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.json.SerializationContext;
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;

/**
* JSON NULL value version of {@link org.eclipse.ditto.base.model.entity.metadata.Metadata}.
Expand Down Expand Up @@ -190,11 +190,21 @@ public boolean contains(final CharSequence key) {
return false;
}

@Override
public boolean containsFlatteningArrays(final CharSequence key) {
return false;
}

@Override
public Optional<JsonValue> getValue(final CharSequence name) {
return Optional.empty();
}

@Override
public Optional<JsonValue> getValueFlatteningArrays(final CharSequence key) {
return Optional.empty();
}

@Override
public JsonObject get(final JsonPointer pointer) {
return this;
Expand Down
115 changes: 85 additions & 30 deletions json/src/main/java/org/eclipse/ditto/json/ImmutableJsonObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -223,36 +224,60 @@ private static boolean isEmpty(final Iterable<?> iterable) {
@Override
public boolean contains(final CharSequence key) {
requireNonNull(key, "The key or pointer to check the existence of a value for must not be null!");
return internalContains(key, false);
}

@Override
public boolean containsFlatteningArrays(final CharSequence key) {
requireNonNull(key, "The key or pointer to check the existence of a value for must not be null!");
return internalContains(key, true);
}

boolean internalContains(final CharSequence key, final boolean flatteningArrays) {
final boolean result;

final JsonPointer pointer = JsonPointer.of(key);

if (1 >= pointer.getLevelCount()) {
result = pointer.getRoot().map(this::containsKey).orElse(false);
} else {
result = pointer.getRoot()
.flatMap(this::getValueForKey)
.filter(JsonValue::isObject)
.map(JsonValue::asObject)
.map(jsonObject -> jsonObject.contains(pointer.nextLevel()))
.orElse(false);
result = containsPointer(pointer, flatteningArrays);
}

return result;
}

private Boolean containsPointer(final JsonPointer pointer, final boolean flatteningArrays) {
return pointer.getRoot()
.flatMap(this::getValueForKey)
.filter(val -> val.isObject() || (flatteningArrays && val.isArray()))
.map(val -> val.isObject() ? Stream.of(val.asObject()) :
val.asArray().stream().filter(JsonValue::isObject).map(JsonValue::asObject)
)
.map(stream -> stream.anyMatch(jsonObject -> flatteningArrays ?
jsonObject.containsFlatteningArrays(pointer.nextLevel()) :
jsonObject.contains(pointer.nextLevel()))
)
.orElse(false);
}

private boolean containsKey(final CharSequence key) {
return fieldMap.containsKey(key.toString());
}

@Override
public Optional<JsonValue> getValue(final CharSequence key) {
requireNonNull(key, "The key or pointer of the value to be retrieved must not be null!");
return getValueForPointer(JsonPointer.of(key));
return getValueForPointer(JsonPointer.of(key), false);
}

@Override
public Optional<JsonValue> getValueFlatteningArrays(final CharSequence key) {
requireNonNull(key, "The key or pointer of the value to be retrieved must not be null!");
return getValueForPointer(JsonPointer.of(key), true);
}

private Optional<JsonValue> getValueForPointer(final JsonPointer pointer) {
private Optional<JsonValue> getValueForPointer(final JsonPointer pointer, final boolean flatteningArrays) {
final Optional<JsonValue> result;

final JsonKey rootKey = pointer.getRoot().orElse(ROOT_KEY);
Expand All @@ -263,10 +288,31 @@ private Optional<JsonValue> getValueForPointer(final JsonPointer pointer) {
// same as getting a value for a key
result = getValueForKey(rootKey);
} else {
result = getValueForKey(rootKey)
.filter(JsonValue::isObject)
.map(JsonValue::asObject)
.flatMap(jsonObject -> jsonObject.getValue(pointer.nextLevel()));
final AtomicReference<Boolean> valueIsArray = new AtomicReference<>(false);
final List<JsonValue> collected = getValueForKey(rootKey).map(Stream::of).orElse(Stream.empty())
.filter(val -> val.isObject() || (flatteningArrays && val.isArray()))
.flatMap(val -> {
if (val.isObject()) {
return Stream.of(val.asObject());
} else {
valueIsArray.set(true);
return val.asArray().stream().filter(JsonValue::isObject).map(JsonValue::asObject);
}
})
.flatMap(jsonObject -> flatteningArrays ?
jsonObject.getValueFlatteningArrays(pointer.nextLevel())
.map(Stream::of).orElseGet(Stream::empty) :
jsonObject.getValue(pointer.nextLevel())
.map(Stream::of).orElseGet(Stream::empty)
).collect(Collectors.toList());

if (collected.isEmpty()) {
result = Optional.empty();
} else if (Boolean.TRUE.equals(valueIsArray.get())) {
result = Optional.of(collected.stream().collect(JsonCollectors.valuesToArray()));
} else {
result = Optional.of(collected.get(0));
}
}

return result;
Expand All @@ -281,7 +327,7 @@ private Optional<JsonValue> getValueForKey(final CharSequence key) {
public <T> Optional<T> getValue(final JsonFieldDefinition<T> fieldDefinition) {
checkFieldDefinition(fieldDefinition);

return getValueForPointer(fieldDefinition.getPointer()).map(fieldDefinition::mapValue);
return getValueForPointer(fieldDefinition.getPointer(), false).map(fieldDefinition::mapValue);
}

private static void checkFieldDefinition(final JsonFieldDefinition<?> fieldDefinition) {
Expand All @@ -308,7 +354,7 @@ public JsonObject get(final JsonPointer pointer) {
final Optional<JsonFieldDefinition> rootKeyDefinition = getDefinitionForKey(rootKey);
if (1 >= pointer.getLevelCount()) {
result = rootKeyValue.map(
jsonValue -> JsonField.newInstance(rootKey, jsonValue, rootKeyDefinition.orElse(null)))
jsonValue -> JsonField.newInstance(rootKey, jsonValue, rootKeyDefinition.orElse(null)))
.map(jsonField -> Collections.singletonMap(jsonField.getKeyName(), jsonField))
.map(ImmutableJsonObject::of)
.orElseGet(ImmutableJsonObject::empty);
Expand All @@ -321,16 +367,16 @@ public JsonObject get(final JsonPointer pointer) {
.isPresent();

result = rootKeyValue.map(jsonValue -> {
if (jsonValue.isObject()) {
if (containsNextLevelRootKey.test(jsonValue.asObject())) {
return jsonValue.asObject().get(nextPointerLevel); // Recursion
} else {
return null;
}
} else {
return jsonValue;
}
})
if (jsonValue.isObject()) {
if (containsNextLevelRootKey.test(jsonValue.asObject())) {
return jsonValue.asObject().get(nextPointerLevel); // Recursion
} else {
return null;
}
} else {
return jsonValue;
}
})
.map(jsonValue -> JsonField.newInstance(rootKey, jsonValue, rootKeyDefinition.orElse(null)))
.map(jsonField -> Collections.singletonMap(jsonField.getKeyName(), jsonField))
.map(ImmutableJsonObject::of)
Expand Down Expand Up @@ -360,7 +406,7 @@ public JsonObject get(final JsonFieldSelector fieldSelector) {

final List<JsonPointer> pointersContainedInThis = fieldSelector.getPointers()
.stream()
.filter(this::contains)
.filter(this::containsFlatteningArrays)
.collect(Collectors.toList());

if (pointersContainedInThis.isEmpty()) {
Expand All @@ -381,9 +427,17 @@ private static JsonObject filterByTrie(final JsonObject self, final JsonFieldSel
for (final JsonKey key : trie.getKeys()) {
self.getField(key).ifPresent(child -> {
final JsonValue childValue = child.getValue();
final JsonValue filteredChildValue = childValue.isObject()
? filterByTrie(childValue.asObject(), trie.descend(key))
: childValue;
final JsonValue filteredChildValue;
if (childValue.isObject()) {
filteredChildValue = filterByTrie(childValue.asObject(), trie.descend(key)); // recurse!
} else if (childValue.isArray()) {
filteredChildValue = childValue.asArray().stream()
.filter(JsonValue::isObject)
.map(value -> filterByTrie(value.asObject(), trie.descend(key))) // recurse!
.collect(JsonCollectors.valuesToArray());
} else {
filteredChildValue = childValue;
}
final Optional<JsonFieldDefinition> childFieldDefinition = child.getDefinition();
if (childFieldDefinition.isPresent()) {
builder.set(childFieldDefinition.get(), filteredChildValue);
Expand Down Expand Up @@ -422,7 +476,8 @@ private JsonObject removeForPointer(final JsonPointer pointer) {
.map(JsonValue::asObject)
.filter(containsNextLevelRootKey)
.map(jsonObject -> jsonObject.remove(nextPointerLevel)) // Recursion
.map(withoutValue -> JsonField.newInstance(rootKey, withoutValue, getDefinitionForKey(rootKey).orElse(null)))
.map(withoutValue -> JsonField.newInstance(rootKey, withoutValue,
getDefinitionForKey(rootKey).orElse(null)))
.map(this::set)
.orElse(this);
}
Expand Down Expand Up @@ -568,7 +623,7 @@ private SoftReferencedFieldMap(final Map<String, JsonField> jsonFieldMap,
if (CBOR_FACTORY.isCborAvailable()) {
try {
this.cborObjectRepresentation = CBOR_FACTORY.createCborRepresentation(jsonFieldMap,
guessSerializedSize());
guessSerializedSize());
} catch (final IOException e) {
assert false; // this should not happen, so assertions will throw during testing
jsonObjectStringRepresentation = createStringRepresentation(jsonFieldMap);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ public boolean contains(final CharSequence key) {
return false;
}

@Override
public boolean containsFlatteningArrays(final CharSequence key) {
return false;
}

@Override
public ImmutableJsonObjectNull get(final JsonPointer pointer) {
return this;
Expand All @@ -127,6 +132,11 @@ public Optional<JsonValue> getValue(final CharSequence key) {
return Optional.empty();
}

@Override
public Optional<JsonValue> getValueFlatteningArrays(final CharSequence key) {
return Optional.empty();
}

@Override
public <T> Optional<T> getValue(final JsonFieldDefinition<T> fieldDefinition) {
return Optional.empty();
Expand Down
Loading

0 comments on commit 5d0a5af

Please sign in to comment.