Skip to content

Commit

Permalink
Add out-of-order object/array checks
Browse files Browse the repository at this point in the history
  • Loading branch information
thecoop committed Sep 9, 2024
1 parent 2e3bdb0 commit a5a6a84
Showing 1 changed file with 44 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
package org.elasticsearch.common.xcontent;

import org.elasticsearch.common.CheckedBiConsumer;
import org.elasticsearch.core.Assertions;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.xcontent.ToXContent;
import org.elasticsearch.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.Map;
import java.util.function.BiConsumer;
Expand All @@ -26,10 +29,14 @@
*/
public class ChunkedToXContentBuilder implements Iterator<ToXContent> {

private enum ElementType { OBJECT, ARRAY };

private final ToXContent.Params params;
private final Stream.Builder<ToXContent> builder = Stream.builder();
private Iterator<ToXContent> iterator;

private final Deque<ElementType> elementTracker = Assertions.ENABLED ? new ArrayDeque<>() : null;

public ChunkedToXContentBuilder(ToXContent.Params params) {
this.params = params;
}
Expand All @@ -40,16 +47,25 @@ private void addChunk(ToXContent content) {
}

public ChunkedToXContentBuilder startObject() {
if (elementTracker != null) {
elementTracker.push(ElementType.OBJECT);
}
addChunk((b, p) -> b.startObject());
return this;
}

public ChunkedToXContentBuilder startObject(String name) {
if (elementTracker != null) {
elementTracker.push(ElementType.OBJECT);
}
addChunk((b, p) -> b.startObject(name));
return this;
}

public ChunkedToXContentBuilder endObject() {
if (elementTracker != null && elementTracker.poll() != ElementType.OBJECT) {
throw new IllegalStateException("Out-of-order object close");
}
addChunk((b, p) -> b.endObject());
return this;
}
Expand All @@ -59,16 +75,25 @@ public ChunkedToXContentBuilder object(String name, Consumer<ChunkedToXContentBu
}

public ChunkedToXContentBuilder startArray() {
if (elementTracker != null) {
elementTracker.push(ElementType.ARRAY);
}
addChunk((b, p) -> b.startArray());
return this;
}

public ChunkedToXContentBuilder startArray(String name) {
if (elementTracker != null) {
elementTracker.push(ElementType.ARRAY);
}
addChunk((b, p) -> b.startArray(name));
return this;
}

public ChunkedToXContentBuilder endArray() {
if (elementTracker != null && elementTracker.poll() != ElementType.ARRAY) {
throw new IllegalStateException("Out-of-order array close");
}
addChunk((b, p) -> b.endArray());
return this;
}
Expand Down Expand Up @@ -109,11 +134,18 @@ public ChunkedToXContentBuilder execute(Consumer<ChunkedToXContentBuilder> consu
return this;
}

/**
* Adds chunks from an iterator. Each item is passed to {@code create} to add chunks to this builder.
*/
public <T> ChunkedToXContentBuilder forEach(Iterator<T> items, BiConsumer<? super T, ChunkedToXContentBuilder> create) {
items.forEachRemaining(t -> create.accept(t, this));
return this;
}

/**
* Adds chunks from an iterator. Each item is passed to {@code create}, and the resulting {@code ToXContent}-like objects
* are added to this builder in order.
*/
public <T> ChunkedToXContentBuilder forEach(
Iterator<T> items,
Function<? super T, CheckedBiConsumer<XContentBuilder, ToXContent.Params, IOException>> create
Expand All @@ -122,14 +154,23 @@ public <T> ChunkedToXContentBuilder forEach(
return this;
}

/**
* Each entry in {@code map} is added to the builder as a separate field, named with the entry key.
*/
public <T> ChunkedToXContentBuilder appendEntries(Map<String, ?> map) {
return forEach(map.entrySet().iterator(), (e, b) -> b.field(e.getKey(), e.getValue()));
}

/**
* Each entry in {@code map} is added to the builder as a separate object, named with the entry key.
*/
public <T> ChunkedToXContentBuilder appendXContentObjects(Map<String, ? extends ToXContent> map) {
return forEach(map.entrySet().iterator(), (e, b) -> b.startObject(e.getKey()).appendXContent(e.getValue()).endObject());
}

/**
* Each entry in {@code map} is added to the builder as a separate XContent field, named with the entry key.
*/
public <T> ChunkedToXContentBuilder appendXContentFields(Map<String, ? extends ToXContent> map) {
return forEach(map.entrySet().iterator(), (e, b) -> b.field(e.getKey()).appendXContent(e.getValue()));
}
Expand Down Expand Up @@ -183,6 +224,9 @@ public ChunkedToXContentBuilder appendIfPresent(@Nullable ChunkedToXContent chun

private Iterator<ToXContent> checkCreateIterator() {
if (iterator == null) {
if (elementTracker != null && elementTracker.isEmpty() == false) {
throw new IllegalStateException("Unclosed XContent elements present: " + elementTracker);
}
iterator = builder.build().iterator();
}
return iterator;
Expand Down

0 comments on commit a5a6a84

Please sign in to comment.