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 .index .VersionType ;
1718import org .elasticsearch .index .mapper .IdFieldMapper ;
@@ -189,8 +190,8 @@ public <T> T getFieldValue(String path, Class<T> clazz) {
189190 * or if the field that is found at the provided path is not of the expected type.
190191 */
191192 public <T > T getFieldValue (String path , Class <T > clazz , boolean ignoreMissing ) {
192- FieldPath fieldPath = new FieldPath (path );
193- Object context = fieldPath .initialContext ;
193+ final FieldPath fieldPath = FieldPath . of (path );
194+ Object context = fieldPath .initialContext ( this ) ;
194195 for (String pathElement : fieldPath .pathElements ) {
195196 ResolveResult result = resolve (pathElement , path , context );
196197 if (result .wasSuccessful ) {
@@ -260,8 +261,8 @@ public boolean hasField(String path) {
260261 * @throws IllegalArgumentException if the path is null, empty or invalid.
261262 */
262263 public boolean hasField (String path , boolean failOutOfRange ) {
263- FieldPath fieldPath = new FieldPath (path );
264- Object context = fieldPath .initialContext ;
264+ final FieldPath fieldPath = FieldPath . of (path );
265+ Object context = fieldPath .initialContext ( this ) ;
265266 for (int i = 0 ; i < fieldPath .pathElements .length - 1 ; i ++) {
266267 String pathElement = fieldPath .pathElements [i ];
267268 if (context == null ) {
@@ -328,8 +329,8 @@ public boolean hasField(String path, boolean failOutOfRange) {
328329 * @throws IllegalArgumentException if the path is null, empty, invalid or if the field doesn't exist.
329330 */
330331 public void removeField (String path ) {
331- FieldPath fieldPath = new FieldPath (path );
332- Object context = fieldPath .initialContext ;
332+ final FieldPath fieldPath = FieldPath . of (path );
333+ Object context = fieldPath .initialContext ( this ) ;
333334 for (int i = 0 ; i < fieldPath .pathElements .length - 1 ; i ++) {
334335 ResolveResult result = resolve (fieldPath .pathElements [i ], path , context );
335336 if (result .wasSuccessful ) {
@@ -543,8 +544,8 @@ public void setFieldValue(String path, Object value, boolean ignoreEmptyValue) {
543544 }
544545
545546 private void setFieldValue (String path , Object value , boolean append , boolean allowDuplicates ) {
546- FieldPath fieldPath = new FieldPath (path );
547- Object context = fieldPath .initialContext ;
547+ final FieldPath fieldPath = FieldPath . of (path );
548+ Object context = fieldPath .initialContext ( this ) ;
548549 for (int i = 0 ; i < fieldPath .pathElements .length - 1 ; i ++) {
549550 String pathElement = fieldPath .pathElements [i ];
550551 if (context == null ) {
@@ -995,21 +996,45 @@ public String getFieldName() {
995996 }
996997 }
997998
998- private class FieldPath {
999+ private static final class FieldPath {
9991000
1000- private final String [] pathElements ;
1001- private final Object initialContext ;
1001+ private static final int MAX_SIZE = 512 ;
1002+ private static final Map < String , FieldPath > CACHE = ConcurrentCollections . newConcurrentMapWithAggressiveConcurrency () ;
10021003
1003- private FieldPath (String path ) {
1004+ // constructing a new FieldPath requires that we parse a String (e.g. "foo.bar.baz") into an array
1005+ // of path elements (e.g. ["foo", "bar", "baz"]). Calling String#split results in the allocation
1006+ // of an ArrayList to hold the results, then a new String is created for each path element, and
1007+ // then finally a String[] is allocated to hold the actual result -- in addition to all that, we
1008+ // do some processing ourselves on the path and path elements to validate and prepare them.
1009+ // the above CACHE and the below 'FieldPath.of' method allow us to almost always avoid this work.
1010+
1011+ static FieldPath of (String path ) {
10041012 if (Strings .isEmpty (path )) {
10051013 throw new IllegalArgumentException ("path cannot be null nor empty" );
10061014 }
1015+ FieldPath res = CACHE .get (path );
1016+ if (res != null ) {
1017+ return res ;
1018+ }
1019+ res = new FieldPath (path );
1020+ if (CACHE .size () > MAX_SIZE ) {
1021+ CACHE .clear ();
1022+ }
1023+ CACHE .put (path , res );
1024+ return res ;
1025+ }
1026+
1027+ private final String [] pathElements ;
1028+ private final boolean useIngestContext ;
1029+
1030+ // you shouldn't call this directly, use the FieldPath.of method above instead!
1031+ private FieldPath (String path ) {
10071032 String newPath ;
10081033 if (path .startsWith (INGEST_KEY_PREFIX )) {
1009- initialContext = ingestMetadata ;
1034+ useIngestContext = true ;
10101035 newPath = path .substring (INGEST_KEY_PREFIX .length ());
10111036 } else {
1012- initialContext = ctxMap ;
1037+ useIngestContext = false ;
10131038 if (path .startsWith (SOURCE_PREFIX )) {
10141039 newPath = path .substring (SOURCE_PREFIX .length ());
10151040 } else {
@@ -1022,6 +1047,9 @@ private FieldPath(String path) {
10221047 }
10231048 }
10241049
1050+ public Object initialContext (IngestDocument document ) {
1051+ return useIngestContext ? document .getIngestMetadata () : document .getCtxMap ();
1052+ }
10251053 }
10261054
10271055 private static class ResolveResult {
0 commit comments