4343import java .util .Set ;
4444import java .util .function .BiConsumer ;
4545import java .util .stream .Collectors ;
46+ import java .util .stream .Stream ;
4647
4748/**
4849 * Represents a single document being captured before indexing and holds the source and metadata (like id, type and index).
@@ -201,7 +202,7 @@ public <T> T getFieldValue(String path, Class<T> clazz) {
201202 * or if the field that is found at the provided path is not of the expected type.
202203 */
203204 public <T > T getFieldValue (String path , Class <T > clazz , boolean ignoreMissing ) {
204- final FieldPath fieldPath = FieldPath .of (path );
205+ final FieldPath fieldPath = FieldPath .of (path , getCurrentAccessPattern () );
205206 Object context = fieldPath .initialContext (this );
206207 ResolveResult result = resolve (fieldPath .pathElements , fieldPath .pathElements .length , path , context , getCurrentAccessPatternSafe ());
207208 if (result .wasSuccessful ) {
@@ -270,7 +271,7 @@ public boolean hasField(String path) {
270271 * @throws IllegalArgumentException if the path is null, empty or invalid.
271272 */
272273 public boolean hasField (String path , boolean failOutOfRange ) {
273- final FieldPath fieldPath = FieldPath .of (path );
274+ final FieldPath fieldPath = FieldPath .of (path , getCurrentAccessPattern () );
274275 Object context = fieldPath .initialContext (this );
275276 int leafKeyIndex = fieldPath .pathElements .length - 1 ;
276277 int lastContainerIndex = fieldPath .pathElements .length - 2 ;
@@ -424,7 +425,7 @@ public void removeField(String path) {
424425 * @throws IllegalArgumentException if the path is null, empty, or invalid; or if the field doesn't exist (and ignoreMissing is false).
425426 */
426427 public void removeField (String path , boolean ignoreMissing ) {
427- final FieldPath fieldPath = FieldPath .of (path );
428+ final FieldPath fieldPath = FieldPath .of (path , getCurrentAccessPattern () );
428429 Object context = fieldPath .initialContext (this );
429430 String leafKey = fieldPath .pathElements [fieldPath .pathElements .length - 1 ];
430431 ResolveResult result = resolve (
@@ -734,7 +735,7 @@ public void setFieldValue(String path, Object value, boolean ignoreEmptyValue) {
734735 }
735736
736737 private void setFieldValue (String path , Object value , boolean append , boolean allowDuplicates ) {
737- final FieldPath fieldPath = FieldPath .of (path );
738+ final FieldPath fieldPath = FieldPath .of (path , getCurrentAccessPattern () );
738739 Object context = fieldPath .initialContext (this );
739740 int leafKeyIndex = fieldPath .pathElements .length - 1 ;
740741 int lastContainerIndex = fieldPath .pathElements .length - 2 ;
@@ -1288,8 +1289,37 @@ public String getFieldName() {
12881289
12891290 private static final class FieldPath {
12901291
1292+ private record Element (String fieldName , Integer arrayIndex ) {
1293+ private static final String EMPTY_STRING = "" ;
1294+
1295+ static Element field (String fieldName ) {
1296+ Objects .requireNonNull (fieldName , "fieldName cannot be null" );
1297+ if (fieldName .isEmpty ()) {
1298+ throw new IllegalArgumentException ("fieldName cannot be empty" );
1299+ }
1300+ return new Element (fieldName , null );
1301+ }
1302+
1303+ static Element index (int arrayIndex ) {
1304+ if (arrayIndex < 0 ) {
1305+ throw new IndexOutOfBoundsException (arrayIndex );
1306+ }
1307+ return new Element (EMPTY_STRING , arrayIndex );
1308+ }
1309+
1310+ boolean isFieldName () {
1311+ return fieldName .isEmpty () == false && arrayIndex == null ;
1312+ }
1313+
1314+ boolean isArrayIndex () {
1315+ return fieldName .isEmpty () &&
1316+ }
1317+ }
1318+
1319+ private record CacheKey (String path , IngestPipelineFieldAccessPattern accessPattern ) {}
1320+
12911321 private static final int MAX_SIZE = 512 ;
1292- private static final Map <String , FieldPath > CACHE = ConcurrentCollections .newConcurrentMapWithAggressiveConcurrency ();
1322+ private static final Map <CacheKey , FieldPath > CACHE = ConcurrentCollections .newConcurrentMapWithAggressiveConcurrency ();
12931323
12941324 // constructing a new FieldPath requires that we parse a String (e.g. "foo.bar.baz") into an array
12951325 // of path elements (e.g. ["foo", "bar", "baz"]). Calling String#split results in the allocation
@@ -1298,27 +1328,28 @@ private static final class FieldPath {
12981328 // do some processing ourselves on the path and path elements to validate and prepare them.
12991329 // the above CACHE and the below 'FieldPath.of' method allow us to almost always avoid this work.
13001330
1301- static FieldPath of (String path ) {
1331+ static FieldPath of (String path , IngestPipelineFieldAccessPattern accessPattern ) {
13021332 if (Strings .isEmpty (path )) {
13031333 throw new IllegalArgumentException ("path cannot be null nor empty" );
13041334 }
1305- FieldPath res = CACHE .get (path );
1335+ CacheKey cacheKey = new CacheKey (path , accessPattern );
1336+ FieldPath res = CACHE .get (cacheKey );
13061337 if (res != null ) {
13071338 return res ;
13081339 }
1309- res = new FieldPath (path );
1340+ res = new FieldPath (path , accessPattern );
13101341 if (CACHE .size () > MAX_SIZE ) {
13111342 CACHE .clear ();
13121343 }
1313- CACHE .put (path , res );
1344+ CACHE .put (cacheKey , res );
13141345 return res ;
13151346 }
13161347
1317- private final String [] pathElements ;
1348+ private final Element [] pathElements ;
13181349 private final boolean useIngestContext ;
13191350
13201351 // you shouldn't call this directly, use the FieldPath.of method above instead!
1321- private FieldPath (String path ) {
1352+ private FieldPath (String path , IngestPipelineFieldAccessPattern accessPattern ) {
13221353 String newPath ;
13231354 if (path .startsWith (INGEST_KEY_PREFIX )) {
13241355 useIngestContext = true ;
@@ -1331,10 +1362,53 @@ private FieldPath(String path) {
13311362 newPath = path ;
13321363 }
13331364 }
1334- this .pathElements = newPath .split ("\\ ." );
1335- if (pathElements .length == 1 && pathElements [0 ].isEmpty ()) {
1336- throw new IllegalArgumentException ("path [" + path + "] is not valid" );
1365+ String [] pathParts = newPath .split ("\\ ." );
1366+ this .pathElements = processPathParts (path , pathParts , accessPattern );
1367+ }
1368+
1369+ private static Element [] processPathParts (String fullPath , String [] pathParts , IngestPipelineFieldAccessPattern accessPattern ) {
1370+ if (pathParts .length == 1 && pathParts [0 ].isEmpty ()) {
1371+ throw new IllegalArgumentException ("path [" + fullPath + "] is not valid" );
13371372 }
1373+ return Arrays .stream (pathParts )
1374+ .flatMap (pathPart -> {
1375+ int openBracket = pathPart .indexOf ('[' );
1376+ if (openBracket == -1 ) {
1377+ return Stream .of (Element .field (pathPart ));
1378+ } else if (openBracket == 0 ) {
1379+ throw new IllegalArgumentException ("path [" + fullPath + "] is not valid" );
1380+ } else {
1381+ List <Element > resultElements = new ArrayList <>();
1382+ String rootField = pathPart .substring (0 , openBracket );
1383+ resultElements .add (Element .field (rootField ));
1384+
1385+ boolean elementsRemain = true ;
1386+ while (elementsRemain ) {
1387+ int closeBracket = pathPart .indexOf (']' , openBracket );
1388+ if (closeBracket <= openBracket ) {
1389+ throw new IllegalArgumentException ("path [" + fullPath + "] is not valid" );
1390+ }
1391+
1392+ String rawIndex = pathPart .substring (openBracket + 1 , closeBracket );
1393+ try {
1394+ resultElements .add (Element .index (Integer .parseInt (rawIndex )));
1395+ } catch (NumberFormatException numberFormatException ) {
1396+ throw new IllegalArgumentException ("path [" + fullPath + "] is not valid" );
1397+ }
1398+
1399+ if (closeBracket == pathPart .length () - 1 ) {
1400+ elementsRemain = false ;
1401+ } else {
1402+ if (pathPart .charAt (closeBracket + 1 ) != '[' ) {
1403+ throw new IllegalArgumentException ("path [" + fullPath + "] is not valid" );
1404+ }
1405+ openBracket = closeBracket + 1 ;
1406+ }
1407+ }
1408+ return resultElements .stream ();
1409+ }
1410+ })
1411+ .toArray (Element []::new );
13381412 }
13391413
13401414 public Object initialContext (IngestDocument document ) {
0 commit comments