diff --git a/src/main/java/org/springframework/data/mapping/PropertyPath.java b/src/main/java/org/springframework/data/mapping/PropertyPath.java index 8b6f333d2..ee709665f 100644 --- a/src/main/java/org/springframework/data/mapping/PropertyPath.java +++ b/src/main/java/org/springframework/data/mapping/PropertyPath.java @@ -17,11 +17,13 @@ package org.springframework.data.mapping; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.Value; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -30,6 +32,7 @@ import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.Streamable; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; +import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.StringUtils; /** @@ -46,6 +49,7 @@ public class PropertyPath implements Streamable { private static final String ALL_UPPERCASE = "[A-Z0-9._$]+"; private static final Pattern SPLITTER = Pattern.compile("(?:[%s]?([%s]*?[^%s]+))".replaceAll("%s", DELIMITERS)); private static final Pattern SPLITTER_FOR_QUOTED = Pattern.compile("(?:[%s]?([%s]*?[^%s]+))".replaceAll("%s", "\\.")); + private static final Map CACHE = new ConcurrentReferenceHashMap<>(); private final TypeInformation owningType; private final String name; @@ -229,30 +233,33 @@ public class PropertyPath implements Streamable { Assert.hasText(source, "Source must not be null or empty!"); Assert.notNull(type, "TypeInformation must not be null or empty!"); - List iteratorSource = new ArrayList<>(); + return CACHE.computeIfAbsent(Key.of(type, source), it -> { - Matcher matcher = isQuoted(source) ? SPLITTER_FOR_QUOTED.matcher(source.replace("\\Q", "").replace("\\E", "")) - : SPLITTER.matcher("_" + source); + List iteratorSource = new ArrayList<>(); - while (matcher.find()) { - iteratorSource.add(matcher.group(1)); - } + Matcher matcher = isQuoted(it.path) ? SPLITTER_FOR_QUOTED.matcher(it.path.replace("\\Q", "").replace("\\E", "")) + : SPLITTER.matcher("_" + it.path); - Iterator parts = iteratorSource.iterator(); - - PropertyPath result = null; - Stack current = new Stack<>(); - - while (parts.hasNext()) { - if (result == null) { - result = create(parts.next(), type, current); - current.push(result); - } else { - current.push(create(parts.next(), current)); + while (matcher.find()) { + iteratorSource.add(matcher.group(1)); } - } - return result; + Iterator parts = iteratorSource.iterator(); + + PropertyPath result = null; + Stack current = new Stack<>(); + + while (parts.hasNext()) { + if (result == null) { + result = create(parts.next(), it.type, current); + current.push(result); + } else { + current.push(create(parts.next(), current)); + } + } + + return result; + }); } private static boolean isQuoted(String source) { @@ -357,4 +364,11 @@ public class PropertyPath implements Streamable { public String toString() { return String.format("%s.%s", owningType.getType().getSimpleName(), toDotPath()); } + + @Value(staticConstructor = "of") + private static class Key { + + TypeInformation type; + String path; + } } diff --git a/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java b/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java index 70cd17a6d..427de3510 100755 --- a/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java @@ -16,7 +16,7 @@ package org.springframework.data.mapping; import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.mapping.PropertyPath.*; +import static org.springframework.data.mapping.PropertyPath.from; import java.util.Iterator; import java.util.Map; @@ -345,6 +345,11 @@ public class PropertyPathUnitTests { assertThat(path.hasNext()).isFalse(); } + @Test // DATACMNS-1120 + public void cachesPropertyPathsByPathAndType() { + assertThat(from("userName", Foo.class)).isSameAs(from("userName", Foo.class)); + } + private class Foo { String userName;