|
10 | 10 | package org.elasticsearch.xcontent;
|
11 | 11 |
|
12 | 12 | import org.elasticsearch.core.RestApiVersion;
|
| 13 | +import org.elasticsearch.core.SuppressForbidden; |
13 | 14 | import org.elasticsearch.xcontent.ObjectParser.NamedObjectParser;
|
14 | 15 | import org.elasticsearch.xcontent.ObjectParser.ValueType;
|
15 | 16 |
|
16 | 17 | import java.io.IOException;
|
| 18 | +import java.lang.invoke.MethodHandle; |
| 19 | +import java.lang.invoke.MethodHandles; |
| 20 | +import java.lang.invoke.MethodType; |
| 21 | +import java.lang.reflect.RecordComponent; |
17 | 22 | import java.util.ArrayList;
|
| 23 | +import java.util.Arrays; |
18 | 24 | import java.util.Collections;
|
19 | 25 | import java.util.EnumMap;
|
20 | 26 | import java.util.List;
|
@@ -130,6 +136,70 @@ public ConstructingObjectParser(String name, boolean ignoreUnknownFields, Functi
|
130 | 136 | this(name, ignoreUnknownFields, (args, context) -> builder.apply(args));
|
131 | 137 | }
|
132 | 138 |
|
| 139 | + /** |
| 140 | + * Build a parser for the given {@code recordClass}. |
| 141 | + * |
| 142 | + * @param name The name given to the delegate ObjectParser for error identification. Use what you'd use if the object worked with |
| 143 | + * ObjectParser. |
| 144 | + * @param ignoreUnknownFields Should this parser ignore unknown fields? This should generally be set to true only when parsing responses |
| 145 | + * from external systems, never when parsing requests from users. |
| 146 | + * @param recordClass the {@link Class} of the {@link Record} type to build |
| 147 | + * @param lookup a {@link java.lang.invoke.MethodHandles.Lookup Lookup} object that can access the record's canonical constructor; |
| 148 | + * typically just {@code MethodHandles.lookup()} called by some code that can access the constructor. |
| 149 | + * @return a function suitable to use as the {@code builder} argument for one of the constructors of this class. |
| 150 | + */ |
| 151 | + public static <R extends Record, Context> ConstructingObjectParser<R, Context> forRecord( |
| 152 | + String name, |
| 153 | + boolean ignoreUnknownFields, |
| 154 | + Class<R> recordClass, |
| 155 | + MethodHandles.Lookup lookup |
| 156 | + ) { |
| 157 | + Function<Object[], R> builder = recordBuilder(recordClass, lookup); |
| 158 | + return new ConstructingObjectParser<>(name, ignoreUnknownFields, (args, context) -> builder.apply(args)); |
| 159 | + } |
| 160 | + |
| 161 | + /** |
| 162 | + * Build a parser for the given {@code recordClass}. |
| 163 | + * |
| 164 | + * @param name The name given to the delegate ObjectParser for error identification. Use what you'd use if the object worked with |
| 165 | + * ObjectParser. |
| 166 | + * @param ignoreUnknownFields Should this parser ignore unknown fields? This should generally be set to true only when parsing responses |
| 167 | + * from external systems, never when parsing requests from users. |
| 168 | + * @param recordClass the {@link Class} of the {@link Record} type to build. |
| 169 | + * It must be a public class with a public canonical constructor, in a package that is exported unconditionally; |
| 170 | + * otherwise, you'll need {@link #forRecord(String, boolean, Class, MethodHandles.Lookup)} instead. |
| 171 | + * @return a function suitable to use as the {@code builder} argument for one of the constructors of this class. |
| 172 | + */ |
| 173 | + public static <R extends Record, Context> ConstructingObjectParser<R, Context> forRecord( |
| 174 | + String name, |
| 175 | + boolean ignoreUnknownFields, |
| 176 | + Class<R> recordClass |
| 177 | + ) { |
| 178 | + return forRecord(name, ignoreUnknownFields, recordClass, MethodHandles.publicLookup()); |
| 179 | + } |
| 180 | + |
| 181 | + private static <R extends Record> Function<Object[], R> recordBuilder(Class<R> recordClass, MethodHandles.Lookup lookup) { |
| 182 | + Class<?>[] ctorArgs = Arrays.stream(recordClass.getRecordComponents()).map(RecordComponent::getType).toArray(Class<?>[]::new); |
| 183 | + MethodHandle ctor; |
| 184 | + try { |
| 185 | + ctor = lookup.findConstructor(recordClass, MethodType.methodType(void.class, ctorArgs)); |
| 186 | + } catch (NoSuchMethodException | IllegalAccessException e) { |
| 187 | + throw new IllegalStateException("Cannot access record constructor", e); |
| 188 | + } |
| 189 | + return (args) -> recordClass.cast(constructRecord(args, ctor)); |
| 190 | + } |
| 191 | + |
| 192 | + @SuppressForbidden(reason="We can't use invokeExact because we don't know the argument types statically") |
| 193 | + private static Object constructRecord(Object[] args, MethodHandle ctor) { |
| 194 | + Object result; |
| 195 | + try { |
| 196 | + result = ctor.invokeWithArguments(args); |
| 197 | + } catch (Throwable e) { |
| 198 | + throw new IllegalStateException("Unable to call record constructor", e); |
| 199 | + } |
| 200 | + return result; |
| 201 | + } |
| 202 | + |
133 | 203 | /**
|
134 | 204 | * Build the parser.
|
135 | 205 | *
|
|
0 commit comments