@@ -45,11 +45,15 @@ class SimplePropertyPath implements PropertyPath {
4545
4646 private static final String PARSE_DEPTH_EXCEEDED = "Trying to parse a path with depth greater than 1000; This has been disabled for security reasons to prevent parsing overflows" ;
4747
48- private static final String DELIMITERS = "_\\ ." ;
48+ private static final String DOT_DELIMITER = "\\ ." ;
49+ private static final String DELIMITERS = "_" + DOT_DELIMITER ;
50+ private static final Pattern DOT_SPLITTER = Pattern
51+ .compile ("(?:[%s]?([%s]*?[^%s]+))" .replaceAll ("%s" , DOT_DELIMITER ));
4952 private static final Pattern SPLITTER = Pattern .compile ("(?:[%s]?([%s]*?[^%s]+))" .replaceAll ("%s" , DELIMITERS ));
5053 private static final Pattern SPLITTER_FOR_QUOTED = Pattern .compile ("(?:[%s]?([%s]*?[^%s]+))" .replaceAll ("%s" , "\\ ." ));
5154 private static final Pattern NESTED_PROPERTY_PATTERN = Pattern .compile ("\\ p{Lu}[\\ p{Ll}\\ p{Nd}]*$" );
5255 private static final Map <Property , SimplePropertyPath > cache = new ConcurrentReferenceHashMap <>();
56+ private static final Map <Property , SimplePropertyPath > dotPathCache = new ConcurrentReferenceHashMap <>();
5357
5458 private final TypeInformation <?> owningType ;
5559 private final String name ;
@@ -201,24 +205,6 @@ public int hashCode() {
201205 return Objects .hash (owningType , name , typeInformation , actualTypeInformation , isCollection , next );
202206 }
203207
204- /**
205- * Returns the next {@link SimplePropertyPath}.
206- *
207- * @return the next {@link SimplePropertyPath}.
208- * @throws IllegalStateException it there's no next one.
209- */
210- private SimplePropertyPath requiredNext () {
211-
212- SimplePropertyPath result = next ;
213-
214- if (result == null ) {
215- throw new IllegalStateException (
216- "No next path available; Clients should call hasNext() before invoking this method" );
217- }
218-
219- return result ;
220- }
221-
222208 /**
223209 * Extracts the {@link SimplePropertyPath} chain from the given source {@link String} and {@link TypeInformation}.
224210 * <br />
@@ -234,36 +220,69 @@ private SimplePropertyPath requiredNext() {
234220 * @param type the owning type of the property path, must not be {@literal null}.
235221 * @return a new {@link SimplePropertyPath} guaranteed to be not {@literal null}.
236222 */
237- public static SimplePropertyPath from (String source , Class <?> type ) {
238- return from (source , TypeInformation .of (type ));
223+ public static SimplePropertyPath from (String source , TypeInformation <?> type ) {
224+
225+ Assert .hasText (source , "Source must not be null or empty" );
226+ Assert .notNull (type , "TypeInformation must not be null or empty" );
227+
228+ return cache .computeIfAbsent (new Property (type , source ), it -> {
229+
230+ List <String > iteratorSource = new ArrayList <>();
231+
232+ Matcher matcher = isQuoted (it .path ) ? SPLITTER_FOR_QUOTED .matcher (it .path .replace ("\\ Q" , "" ).replace ("\\ E" , "" ))
233+ : SPLITTER .matcher ("_" + it .path );
234+
235+ while (matcher .find ()) {
236+ iteratorSource .add (matcher .group (1 ));
237+ }
238+
239+ Iterator <String > parts = iteratorSource .iterator ();
240+
241+ SimplePropertyPath result = null ;
242+ Stack <SimplePropertyPath > current = new Stack <>();
243+
244+ while (parts .hasNext ()) {
245+ if (result == null ) {
246+ result = create (parts .next (), it .type , current , true );
247+ current .push (result );
248+ } else {
249+ current .push (create (parts .next (), current , true ));
250+ }
251+ }
252+
253+ if (result == null ) {
254+ throw new IllegalStateException (
255+ String .format ("Expected parsing to yield a PropertyPath from '%s' but got null" , source ));
256+ }
257+
258+ return result ;
259+ });
239260 }
240261
241262 /**
242263 * Extracts the {@link SimplePropertyPath} chain from the given source {@link String} and {@link TypeInformation}.
243264 * <br />
244- * Uses {@link #SPLITTER } by default and {@link #SPLITTER_FOR_QUOTED} for {@link Pattern#quote(String) quoted}
265+ * Uses {@link #DOT_SPLITTER } by default and {@link #SPLITTER_FOR_QUOTED} for {@link Pattern#quote(String) quoted}
245266 * literals.
246267 * <p>
247- * Separate parts of the path may be separated by {@code "."} or by {@code "_"} or by camel case. When the match to
248- * properties is ambiguous longer property names are preferred. So for "userAddressCity" the interpretation
249- * "userAddress.city" is preferred over "user.address.city".
268+ * Separate parts of the path may be separated by {@code "."}.
250269 * </p>
251270 *
252271 * @param source a String denoting the property path, must not be {@literal null}.
253272 * @param type the owning type of the property path, must not be {@literal null}.
254273 * @return a new {@link SimplePropertyPath} guaranteed to be not {@literal null}.
255274 */
256- public static SimplePropertyPath from (String source , TypeInformation <?> type ) {
275+ public static SimplePropertyPath of (String source , TypeInformation <?> type ) {
257276
258277 Assert .hasText (source , "Source must not be null or empty" );
259278 Assert .notNull (type , "TypeInformation must not be null or empty" );
260279
261- return cache .computeIfAbsent (new Property (type , source ), it -> {
280+ return dotPathCache .computeIfAbsent (new Property (type , source ), it -> {
262281
263282 List <String > iteratorSource = new ArrayList <>();
264283
265284 Matcher matcher = isQuoted (it .path ) ? SPLITTER_FOR_QUOTED .matcher (it .path .replace ("\\ Q" , "" ).replace ("\\ E" , "" ))
266- : SPLITTER .matcher ("_ " + it .path );
285+ : DOT_SPLITTER .matcher (". " + it .path );
267286
268287 while (matcher .find ()) {
269288 iteratorSource .add (matcher .group (1 ));
@@ -276,16 +295,16 @@ public static SimplePropertyPath from(String source, TypeInformation<?> type) {
276295
277296 while (parts .hasNext ()) {
278297 if (result == null ) {
279- result = create (parts .next (), it .type , current );
298+ result = create (parts .next (), it .type , current , false );
280299 current .push (result );
281300 } else {
282- current .push (create (parts .next (), current ));
301+ current .push (create (parts .next (), current , false ));
283302 }
284303 }
285304
286305 if (result == null ) {
287306 throw new IllegalStateException (
288- String .format ("Expected parsing to yield a PropertyPath from %s but got null" , source ));
307+ String .format ("Expected parsing to yield a PropertyPath from '%s' but got null" , source ));
289308 }
290309
291310 return result ;
@@ -303,11 +322,12 @@ private static boolean isQuoted(String source) {
303322 * @param base
304323 * @return
305324 */
306- private static SimplePropertyPath create (String source , Stack <SimplePropertyPath > base ) {
325+ private static SimplePropertyPath create (String source , Stack <SimplePropertyPath > base , boolean considerNested ) {
307326
308327 SimplePropertyPath previous = base .peek ();
309328
310- SimplePropertyPath propertyPath = create (source , previous .typeInformation .getRequiredActualType (), base );
329+ SimplePropertyPath propertyPath = create (source , previous .typeInformation .getRequiredActualType (), base ,
330+ considerNested );
311331 previous .next = propertyPath ;
312332 return propertyPath ;
313333 }
@@ -322,8 +342,9 @@ private static SimplePropertyPath create(String source, Stack<SimplePropertyPath
322342 * @param type
323343 * @return
324344 */
325- private static SimplePropertyPath create (String source , TypeInformation <?> type , List <SimplePropertyPath > base ) {
326- return create (source , type , "" , base );
345+ private static SimplePropertyPath create (String source , TypeInformation <?> type , List <SimplePropertyPath > base ,
346+ boolean considerNested ) {
347+ return create (source , type , "" , base , considerNested );
327348 }
328349
329350 /**
@@ -337,7 +358,7 @@ private static SimplePropertyPath create(String source, TypeInformation<?> type,
337358 * @return
338359 */
339360 private static SimplePropertyPath create (String source , TypeInformation <?> type , String addTail ,
340- List <SimplePropertyPath > base ) {
361+ List <SimplePropertyPath > base , boolean considerNested ) {
341362
342363 if (base .size () > 1000 ) {
343364 throw new IllegalArgumentException (PARSE_DEPTH_EXCEEDED );
@@ -358,7 +379,7 @@ private static SimplePropertyPath create(String source, TypeInformation<?> type,
358379 newBase .add (current );
359380
360381 if (StringUtils .hasText (addTail )) {
361- current .next = create (addTail , current .actualTypeInformation , newBase );
382+ current .next = create (addTail , current .actualTypeInformation , newBase , considerNested );
362383 }
363384
364385 return current ;
@@ -372,18 +393,21 @@ private static SimplePropertyPath create(String source, TypeInformation<?> type,
372393 exception = e ;
373394 }
374395
375- Matcher matcher = NESTED_PROPERTY_PATTERN .matcher (source );
396+ if (considerNested ) {
397+
398+ Matcher matcher = NESTED_PROPERTY_PATTERN .matcher (source );
376399
377- if (matcher .find () && matcher .start () != 0 ) {
400+ if (matcher .find () && matcher .start () != 0 ) {
378401
379- int position = matcher .start ();
380- String head = source .substring (0 , position );
381- String tail = source .substring (position );
402+ int position = matcher .start ();
403+ String head = source .substring (0 , position );
404+ String tail = source .substring (position );
382405
383- try {
384- return create (head , type , tail + addTail , base );
385- } catch (PropertyReferenceException e ) {
386- throw e .hasDeeperResolutionDepthThan (exception ) ? e : exception ;
406+ try {
407+ return create (head , type , tail + addTail , base , considerNested );
408+ } catch (PropertyReferenceException e ) {
409+ throw e .hasDeeperResolutionDepthThan (exception ) ? e : exception ;
410+ }
387411 }
388412 }
389413
0 commit comments