2121
2222import org .elasticsearch .common .ParseField ;
2323import org .elasticsearch .common .ParsingException ;
24+ import org .elasticsearch .common .xcontent .ObjectParser .NamedObjectParser ;
2425import org .elasticsearch .common .xcontent .ObjectParser .ValueType ;
2526
2627import 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 );
0 commit comments