1212import org .elasticsearch .common .Strings ;
1313import org .elasticsearch .common .util .CollectionUtils ;
1414import org .elasticsearch .common .util .Maps ;
15+ import org .elasticsearch .common .util .concurrent .ConcurrentCollections ;
1516import org .elasticsearch .common .util .set .Sets ;
1617import org .elasticsearch .core .UpdateForV10 ;
1718import org .elasticsearch .index .VersionType ;
@@ -190,8 +191,8 @@ public <T> T getFieldValue(String path, Class<T> clazz) {
190191 * or if the field that is found at the provided path is not of the expected type.
191192 */
192193 public <T > T getFieldValue (String path , Class <T > clazz , boolean ignoreMissing ) {
193- FieldPath fieldPath = new FieldPath (path );
194- Object context = fieldPath .initialContext ;
194+ final FieldPath fieldPath = FieldPath . of (path );
195+ Object context = fieldPath .initialContext ( this ) ;
195196 for (String pathElement : fieldPath .pathElements ) {
196197 ResolveResult result = resolve (pathElement , path , context );
197198 if (result .wasSuccessful ) {
@@ -261,8 +262,8 @@ public boolean hasField(String path) {
261262 * @throws IllegalArgumentException if the path is null, empty or invalid.
262263 */
263264 public boolean hasField (String path , boolean failOutOfRange ) {
264- FieldPath fieldPath = new FieldPath (path );
265- Object context = fieldPath .initialContext ;
265+ final FieldPath fieldPath = FieldPath . of (path );
266+ Object context = fieldPath .initialContext ( this ) ;
266267 for (int i = 0 ; i < fieldPath .pathElements .length - 1 ; i ++) {
267268 String pathElement = fieldPath .pathElements [i ];
268269 if (context == null ) {
@@ -329,8 +330,8 @@ public boolean hasField(String path, boolean failOutOfRange) {
329330 * @throws IllegalArgumentException if the path is null, empty, invalid or if the field doesn't exist.
330331 */
331332 public void removeField (String path ) {
332- FieldPath fieldPath = new FieldPath (path );
333- Object context = fieldPath .initialContext ;
333+ final FieldPath fieldPath = FieldPath . of (path );
334+ Object context = fieldPath .initialContext ( this ) ;
334335 for (int i = 0 ; i < fieldPath .pathElements .length - 1 ; i ++) {
335336 ResolveResult result = resolve (fieldPath .pathElements [i ], path , context );
336337 if (result .wasSuccessful ) {
@@ -544,8 +545,8 @@ public void setFieldValue(String path, Object value, boolean ignoreEmptyValue) {
544545 }
545546
546547 private void setFieldValue (String path , Object value , boolean append , boolean allowDuplicates ) {
547- FieldPath fieldPath = new FieldPath (path );
548- Object context = fieldPath .initialContext ;
548+ final FieldPath fieldPath = FieldPath . of (path );
549+ Object context = fieldPath .initialContext ( this ) ;
549550 for (int i = 0 ; i < fieldPath .pathElements .length - 1 ; i ++) {
550551 String pathElement = fieldPath .pathElements [i ];
551552 if (context == null ) {
@@ -998,21 +999,45 @@ public String getFieldName() {
998999 }
9991000 }
10001001
1001- private class FieldPath {
1002+ private static final class FieldPath {
10021003
1003- private final String [] pathElements ;
1004- private final Object initialContext ;
1004+ private static final int MAX_SIZE = 512 ;
1005+ private static final Map < String , FieldPath > CACHE = ConcurrentCollections . newConcurrentMapWithAggressiveConcurrency () ;
10051006
1006- private FieldPath (String path ) {
1007+ // constructing a new FieldPath requires that we parse a String (e.g. "foo.bar.baz") into an array
1008+ // of path elements (e.g. ["foo", "bar", "baz"]). Calling String#split results in the allocation
1009+ // of an ArrayList to hold the results, then a new String is created for each path element, and
1010+ // then finally a String[] is allocated to hold the actual result -- in addition to all that, we
1011+ // do some processing ourselves on the path and path elements to validate and prepare them.
1012+ // the above CACHE and the below 'FieldPath.of' method allow us to almost always avoid this work.
1013+
1014+ static FieldPath of (String path ) {
10071015 if (Strings .isEmpty (path )) {
10081016 throw new IllegalArgumentException ("path cannot be null nor empty" );
10091017 }
1018+ FieldPath res = CACHE .get (path );
1019+ if (res != null ) {
1020+ return res ;
1021+ }
1022+ res = new FieldPath (path );
1023+ if (CACHE .size () > MAX_SIZE ) {
1024+ CACHE .clear ();
1025+ }
1026+ CACHE .put (path , res );
1027+ return res ;
1028+ }
1029+
1030+ private final String [] pathElements ;
1031+ private final boolean useIngestContext ;
1032+
1033+ // you shouldn't call this directly, use the FieldPath.of method above instead!
1034+ private FieldPath (String path ) {
10101035 String newPath ;
10111036 if (path .startsWith (INGEST_KEY_PREFIX )) {
1012- initialContext = ingestMetadata ;
1037+ useIngestContext = true ;
10131038 newPath = path .substring (INGEST_KEY_PREFIX .length ());
10141039 } else {
1015- initialContext = ctxMap ;
1040+ useIngestContext = false ;
10161041 if (path .startsWith (SOURCE_PREFIX )) {
10171042 newPath = path .substring (SOURCE_PREFIX .length ());
10181043 } else {
@@ -1025,6 +1050,9 @@ private FieldPath(String path) {
10251050 }
10261051 }
10271052
1053+ public Object initialContext (IngestDocument document ) {
1054+ return useIngestContext ? document .getIngestMetadata () : document .getCtxMap ();
1055+ }
10281056 }
10291057
10301058 private static class ResolveResult {
0 commit comments