Skip to content

Commit 3c7c4bc

Browse files
authored
Adds declareNamedObjects methods to ConstructingObjectParser (#24219)
* Adds declareNamedObjects methods to ConstructingObjectParser * Addresses review comments
1 parent 2b8fa64 commit 3c7c4bc

File tree

4 files changed

+375
-58
lines changed

4 files changed

+375
-58
lines changed

core/src/main/java/org/elasticsearch/common/xcontent/AbstractObjectParser.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.common.CheckedFunction;
2323
import org.elasticsearch.common.ParseField;
2424
import org.elasticsearch.common.bytes.BytesReference;
25+
import org.elasticsearch.common.xcontent.ObjectParser.NamedObjectParser;
2526
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
2627
import org.elasticsearch.common.xcontent.json.JsonXContent;
2728

@@ -30,6 +31,7 @@
3031
import java.util.List;
3132
import java.util.function.BiConsumer;
3233
import java.util.function.BiFunction;
34+
import java.util.function.Consumer;
3335

3436
/**
3537
* Superclass for {@link ObjectParser} and {@link ConstructingObjectParser}. Defines most of the "declare" methods so they can be shared.
@@ -44,6 +46,94 @@ public abstract class AbstractObjectParser<Value, Context>
4446
public abstract <T> void declareField(BiConsumer<Value, T> consumer, ContextParser<Context, T> parser, ParseField parseField,
4547
ValueType type);
4648

49+
/**
50+
* Declares named objects in the style of aggregations. These are named
51+
* inside and object like this:
52+
*
53+
* <pre>
54+
* <code>
55+
* {
56+
* "aggregations": {
57+
* "name_1": { "aggregation_type": {} },
58+
* "name_2": { "aggregation_type": {} },
59+
* "name_3": { "aggregation_type": {} }
60+
* }
61+
* }
62+
* }
63+
* </code>
64+
* </pre>
65+
*
66+
* Unlike the other version of this method, "ordered" mode (arrays of
67+
* objects) is not supported.
68+
*
69+
* See NamedObjectHolder in ObjectParserTests for examples of how to invoke
70+
* this.
71+
*
72+
* @param consumer
73+
* sets the values once they have been parsed
74+
* @param namedObjectParser
75+
* parses each named object
76+
* @param parseField
77+
* the field to parse
78+
*/
79+
public abstract <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedObjectParser<T, Context> namedObjectParser,
80+
ParseField parseField);
81+
82+
/**
83+
* Declares named objects in the style of highlighting's field element.
84+
* These are usually named inside and object like this:
85+
*
86+
* <pre>
87+
* <code>
88+
* {
89+
* "highlight": {
90+
* "fields": { &lt;------ this one
91+
* "title": {},
92+
* "body": {},
93+
* "category": {}
94+
* }
95+
* }
96+
* }
97+
* </code>
98+
* </pre>
99+
*
100+
* but, when order is important, some may be written this way:
101+
*
102+
* <pre>
103+
* <code>
104+
* {
105+
* "highlight": {
106+
* "fields": [ &lt;------ this one
107+
* {"title": {}},
108+
* {"body": {}},
109+
* {"category": {}}
110+
* ]
111+
* }
112+
* }
113+
* </code>
114+
* </pre>
115+
*
116+
* This is because json doesn't enforce ordering. Elasticsearch reads it in
117+
* the order sent but tools that generate json are free to put object
118+
* members in an unordered Map, jumbling them. Thus, if you care about order
119+
* you can send the object in the second way.
120+
*
121+
* See NamedObjectHolder in ObjectParserTests for examples of how to invoke
122+
* this.
123+
*
124+
* @param consumer
125+
* sets the values once they have been parsed
126+
* @param namedObjectParser
127+
* parses each named object
128+
* @param orderedModeCallback
129+
* called when the named object is parsed using the "ordered"
130+
* mode (the array of objects)
131+
* @param parseField
132+
* the field to parse
133+
*/
134+
public abstract <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedObjectParser<T, Context> namedObjectParser,
135+
Consumer<Value> orderedModeCallback, ParseField parseField);
136+
47137
public <T> void declareField(BiConsumer<Value, T> consumer, CheckedFunction<XContentParser, T, IOException> parser,
48138
ParseField parseField, ValueType type) {
49139
if (parser == null) {

core/src/main/java/org/elasticsearch/common/xcontent/ConstructingObjectParser.java

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.elasticsearch.common.ParseField;
2323
import org.elasticsearch.common.ParsingException;
24+
import org.elasticsearch.common.xcontent.ObjectParser.NamedObjectParser;
2425
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
2526

2627
import java.io.IOException;
@@ -77,14 +78,14 @@ public final class ConstructingObjectParser<Value, Context> extends AbstractObje
7778
/**
7879
* Consumer that marks a field as a required constructor argument instead of a real object field.
7980
*/
80-
private static final BiConsumer<Object, Object> REQUIRED_CONSTRUCTOR_ARG_MARKER = (a, b) -> {
81+
private static final BiConsumer<?, ?> REQUIRED_CONSTRUCTOR_ARG_MARKER = (a, b) -> {
8182
throw new UnsupportedOperationException("I am just a marker I should never be called.");
8283
};
8384

8485
/**
8586
* Consumer that marks a field as an optional constructor argument instead of a real object field.
8687
*/
87-
private static final BiConsumer<Object, Object> OPTIONAL_CONSTRUCTOR_ARG_MARKER = (a, b) -> {
88+
private static final BiConsumer<?, ?> OPTIONAL_CONSTRUCTOR_ARG_MARKER = (a, b) -> {
8889
throw new UnsupportedOperationException("I am just a marker I should never be called.");
8990
};
9091

@@ -189,7 +190,7 @@ public <T> void declareField(BiConsumer<Value, T> consumer, ContextParser<Contex
189190

190191
if (consumer == REQUIRED_CONSTRUCTOR_ARG_MARKER || consumer == OPTIONAL_CONSTRUCTOR_ARG_MARKER) {
191192
/*
192-
* Constructor arguments are detected by this "marker" consumer. It keeps the API looking clean even if it is a bit sleezy. We
193+
* Constructor arguments are detected by these "marker" consumers. It keeps the API looking clean even if it is a bit sleezy. We
193194
* then build a new consumer directly against the object parser that triggers the "constructor arg just arrived behavior" of the
194195
* parser. Conveniently, we can close over the position of the constructor in the argument list so we don't need to do any fancy
195196
* or expensive lookups whenever the constructor args come in.
@@ -204,6 +205,91 @@ public <T> void declareField(BiConsumer<Value, T> consumer, ContextParser<Contex
204205
}
205206
}
206207

208+
@Override
209+
public <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedObjectParser<T, Context> namedObjectParser,
210+
ParseField parseField) {
211+
if (consumer == null) {
212+
throw new IllegalArgumentException("[consumer] is required");
213+
}
214+
if (namedObjectParser == null) {
215+
throw new IllegalArgumentException("[parser] is required");
216+
}
217+
if (parseField == null) {
218+
throw new IllegalArgumentException("[parseField] is required");
219+
}
220+
221+
if (consumer == REQUIRED_CONSTRUCTOR_ARG_MARKER || consumer == OPTIONAL_CONSTRUCTOR_ARG_MARKER) {
222+
/*
223+
* Constructor arguments are detected by this "marker" consumer. It
224+
* keeps the API looking clean even if it is a bit sleezy. We then
225+
* build a new consumer directly against the object parser that
226+
* triggers the "constructor arg just arrived behavior" of the
227+
* parser. Conveniently, we can close over the position of the
228+
* constructor in the argument list so we don't need to do any fancy
229+
* or expensive lookups whenever the constructor args come in.
230+
*/
231+
int position = constructorArgInfos.size();
232+
boolean required = consumer == REQUIRED_CONSTRUCTOR_ARG_MARKER;
233+
constructorArgInfos.add(new ConstructorArgInfo(parseField, required));
234+
objectParser.declareNamedObjects((target, v) -> target.constructorArg(position, parseField, v), namedObjectParser, parseField);
235+
} else {
236+
numberOfFields += 1;
237+
objectParser.declareNamedObjects(queueingConsumer(consumer, parseField), namedObjectParser, parseField);
238+
}
239+
}
240+
241+
@Override
242+
public <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedObjectParser<T, Context> namedObjectParser,
243+
Consumer<Value> orderedModeCallback, ParseField parseField) {
244+
if (consumer == null) {
245+
throw new IllegalArgumentException("[consumer] is required");
246+
}
247+
if (namedObjectParser == null) {
248+
throw new IllegalArgumentException("[parser] is required");
249+
}
250+
if (orderedModeCallback == null) {
251+
throw new IllegalArgumentException("[orderedModeCallback] is required");
252+
}
253+
if (parseField == null) {
254+
throw new IllegalArgumentException("[parseField] is required");
255+
}
256+
257+
if (consumer == REQUIRED_CONSTRUCTOR_ARG_MARKER || consumer == OPTIONAL_CONSTRUCTOR_ARG_MARKER) {
258+
/*
259+
* Constructor arguments are detected by this "marker" consumer. It
260+
* keeps the API looking clean even if it is a bit sleezy. We then
261+
* build a new consumer directly against the object parser that
262+
* triggers the "constructor arg just arrived behavior" of the
263+
* parser. Conveniently, we can close over the position of the
264+
* constructor in the argument list so we don't need to do any fancy
265+
* or expensive lookups whenever the constructor args come in.
266+
*/
267+
int position = constructorArgInfos.size();
268+
boolean required = consumer == REQUIRED_CONSTRUCTOR_ARG_MARKER;
269+
constructorArgInfos.add(new ConstructorArgInfo(parseField, required));
270+
objectParser.declareNamedObjects((target, v) -> target.constructorArg(position, parseField, v), namedObjectParser,
271+
wrapOrderedModeCallBack(orderedModeCallback), parseField);
272+
} else {
273+
numberOfFields += 1;
274+
objectParser.declareNamedObjects(queueingConsumer(consumer, parseField), namedObjectParser,
275+
wrapOrderedModeCallBack(orderedModeCallback), parseField);
276+
}
277+
}
278+
279+
private Consumer<Target> wrapOrderedModeCallBack(Consumer<Value> callback) {
280+
return (target) -> {
281+
if (target.targetObject != null) {
282+
// The target has already been built. Call the callback now.
283+
callback.accept(target.targetObject);
284+
return;
285+
}
286+
/*
287+
* The target hasn't been built. Queue the callback.
288+
*/
289+
target.queuedOrderedModeCallback = callback;
290+
};
291+
}
292+
207293
/**
208294
* Creates the consumer that does the "field just arrived" behavior. If the targetObject hasn't been built then it queues the value.
209295
* Otherwise it just applies the value just like {@linkplain ObjectParser} does.
@@ -258,6 +344,11 @@ private class Target {
258344
* Fields to be saved to the target object when we can build it. This is only allocated if a field has to be queued.
259345
*/
260346
private Consumer<Value>[] queuedFields;
347+
/**
348+
* OrderedModeCallback to be called with the target object when we can
349+
* build it. This is only allocated if the callback has to be queued.
350+
*/
351+
private Consumer<Value> queuedOrderedModeCallback;
261352
/**
262353
* The count of fields already queued.
263354
*/
@@ -343,6 +434,9 @@ private Value finish() {
343434
private void buildTarget() {
344435
try {
345436
targetObject = builder.apply(constructorArgs);
437+
if (queuedOrderedModeCallback != null) {
438+
queuedOrderedModeCallback.accept(targetObject);
439+
}
346440
while (queuedFieldsCount > 0) {
347441
queuedFieldsCount -= 1;
348442
queuedFields[queuedFieldsCount].accept(targetObject);

core/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java

Lines changed: 2 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -227,41 +227,7 @@ public <T> void declareObjectOrDefault(BiConsumer<Value, T> consumer, BiFunction
227227
}, field, ValueType.OBJECT_OR_BOOLEAN);
228228
}
229229

230-
/**
231-
* Declares named objects in the style of highlighting's field element. These are usually named inside and object like this:
232-
* <pre><code>
233-
* {
234-
* "highlight": {
235-
* "fields": { &lt;------ this one
236-
* "title": {},
237-
* "body": {},
238-
* "category": {}
239-
* }
240-
* }
241-
* }
242-
* </code></pre>
243-
* but, when order is important, some may be written this way:
244-
* <pre><code>
245-
* {
246-
* "highlight": {
247-
* "fields": [ &lt;------ this one
248-
* {"title": {}},
249-
* {"body": {}},
250-
* {"category": {}}
251-
* ]
252-
* }
253-
* }
254-
* </code></pre>
255-
* This is because json doesn't enforce ordering. Elasticsearch reads it in the order sent but tools that generate json are free to put
256-
* object members in an unordered Map, jumbling them. Thus, if you care about order you can send the object in the second way.
257-
*
258-
* See NamedObjectHolder in ObjectParserTests for examples of how to invoke this.
259-
*
260-
* @param consumer sets the values once they have been parsed
261-
* @param namedObjectParser parses each named object
262-
* @param orderedModeCallback called when the named object is parsed using the "ordered" mode (the array of objects)
263-
* @param field the field to parse
264-
*/
230+
@Override
265231
public <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedObjectParser<T, Context> namedObjectParser,
266232
Consumer<Value> orderedModeCallback, ParseField field) {
267233
// This creates and parses the named object
@@ -311,26 +277,7 @@ public <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedOb
311277
}, field, ValueType.OBJECT_ARRAY);
312278
}
313279

314-
/**
315-
* Declares named objects in the style of aggregations. These are named inside and object like this:
316-
* <pre><code>
317-
* {
318-
* "aggregations": {
319-
* "name_1": { "aggregation_type": {} },
320-
* "name_2": { "aggregation_type": {} },
321-
* "name_3": { "aggregation_type": {} }
322-
* }
323-
* }
324-
* }
325-
* </code></pre>
326-
* Unlike the other version of this method, "ordered" mode (arrays of objects) is not supported.
327-
*
328-
* See NamedObjectHolder in ObjectParserTests for examples of how to invoke this.
329-
*
330-
* @param consumer sets the values once they have been parsed
331-
* @param namedObjectParser parses each named object
332-
* @param field the field to parse
333-
*/
280+
@Override
334281
public <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedObjectParser<T, Context> namedObjectParser,
335282
ParseField field) {
336283
Consumer<Value> orderedModeCallback = (v) -> {

0 commit comments

Comments
 (0)