@@ -21,22 +21,44 @@ public class GetByPath {
2121 protected final String path ;
2222 protected final List <String > splitPath ;
2323 protected final boolean throwExceptions ;
24+ protected final boolean forceAccess ;
25+ protected final boolean logErrors ;
26+
27+ public static final class Flags {
28+ private boolean throwExceptions = false ;
29+ private boolean forceAccess = false ;
30+ private boolean logErrors = false ;
31+
32+ public Flags setThrowExceptions (boolean throwExceptions ) {
33+ this .throwExceptions = throwExceptions ;
34+ return this ;
35+ }
36+
37+ public Flags setForceAccess (boolean forceAccess ) {
38+ this .forceAccess = forceAccess ;
39+ return this ;
40+ }
41+
42+ public Flags setLogErrors (boolean logErrors ) {
43+ this .logErrors = logErrors ;
44+ return this ;
45+ }
46+ }
2447
2548 /**
2649 * Protected constructor.
2750 * <p>
28- * Use {@link #newWith(String)} or {@link #newWith(String, EscapingStringTokenizer, boolean )}.
51+ * Use {@link #newWith(String)} or {@link #newWith(String, EscapingStringTokenizer)}.
2952 *
3053 * @param path The path to use. Example "/data/field".
3154 * @param stringTokenizer The Tokenizer to use. Arbitrary escape and delimiters can be set via this.
32- * @param throwExceptions Whether to throw exceptions when paths do not match the scanned object .
55+ * @param flags Configuration flags .
3356 */
34- protected GetByPath (
35- String path ,
36- EscapingStringTokenizer stringTokenizer ,
37- boolean throwExceptions ) {
57+ protected GetByPath (String path , EscapingStringTokenizer stringTokenizer , Flags flags ) {
3858 this .path = path ;
39- this .throwExceptions = throwExceptions ;
59+ this .throwExceptions = flags .throwExceptions ;
60+ this .forceAccess = flags .forceAccess ;
61+ this .logErrors = flags .logErrors ;
4062
4163 if (path != null && path .charAt (0 ) == stringTokenizer .delimiter ) {
4264 splitPath = stringTokenizer .build (path );
@@ -50,44 +72,59 @@ protected GetByPath(
5072 *
5173 * @param path The path to use. Example "/data/field".
5274 * @param stringTokenizer The Tokenizer to use. Arbitrary escape and delimiters can be set via this.
53- * @param throwExceptions Whether to throw exceptions when paths do not match the scanned object .
75+ * @param flags Configuration flags .
5476 * @return New instance of {@link GetByPath}
5577 */
5678 public static GetByPath newWith (
5779 String path ,
5880 EscapingStringTokenizer stringTokenizer ,
59- boolean throwExceptions ) {
60- return new GetByPath (path , stringTokenizer , throwExceptions );
81+ Flags flags ) {
82+ return new GetByPath (path , stringTokenizer , flags );
83+ }
84+
85+ /**
86+ * Static constructor
87+ *
88+ * @param path The path to use. Example "/data/field".
89+ * @param stringTokenizer The Tokenizer to use. Arbitrary escape and delimiters can be set via this.
90+ * @return New instance of {@link GetByPath}
91+ */
92+ public static GetByPath newWith (
93+ String path ,
94+ EscapingStringTokenizer stringTokenizer ) {
95+ return newWith (
96+ path ,
97+ stringTokenizer ,
98+ new Flags ());
6199 }
62100
63101 /**
64102 * Static constructor. Uses default tokenizer with delimiter '/' and escape char '\'.
65103 *
66- * @param path The path to use. Example "/data/field".
104+ * @param path The path to use. Example "/data/field".
105+ * @param flags Configuration flags.
67106 * @return New instance of {@link GetByPath}
68107 */
69- public static GetByPath newWithExceptions (
70- String path ) {
71- return newWith (path , EscapingStringTokenizer .newInstance ()
72- .setIncludeEmpty (false )
73- .setDelimiter ('/' )
74- .setEscape ('\\' ),
75- true );
108+ public static GetByPath newWith (String path , Flags flags ) {
109+ return newWith (
110+ path ,
111+ EscapingStringTokenizer .newInstance ()
112+ .setIncludeEmpty (false )
113+ .setDelimiter ('/' )
114+ .setEscape ('\\' ),
115+ flags );
76116 }
77117
78118 /**
79- * Static constructor. Uses default tokenizer with delimiter '/' and escape char '\' and
80- * {@link #throwExceptions} = false.
119+ * Static constructor. Uses default tokenizer with delimiter '/' and escape char '\'.
81120 *
82121 * @param path The path to use. Example "/data/field".
83122 * @return New instance of {@link GetByPath}
84123 */
85124 public static GetByPath newWith (String path ) {
86- return newWith (path , EscapingStringTokenizer .newInstance ()
87- .setIncludeEmpty (false )
88- .setDelimiter ('/' )
89- .setEscape ('\\' ),
90- false );
125+ return newWith (
126+ path ,
127+ new Flags ());
91128 }
92129
93130 /**
@@ -117,14 +154,14 @@ public Object getByNameArray(List<String> nameArray, Object scannedData) {
117154
118155 String currentName = nameArray .remove (0 );
119156
157+ String errorMessage = null ;
120158 if (scannedData instanceof Map ) {
121159 Map <?, ?> scannedMap = ((Map <?, ?>) scannedData );
122160 Object value = scannedMap .get (currentName );
123161 if (value != null || scannedMap .containsKey (currentName )) {
124162 return getByNameArray (nameArray , value );
125163 }
126164 } else if (scannedData instanceof Collection ) {
127- Collection <?> scannedCollection = ((Collection <?>) scannedData );
128165 Object [] scannedArray = ((Collection <?>) scannedData ).toArray ();
129166
130167 if (scannedArray .length > 0 ) {
@@ -133,29 +170,47 @@ public Object getByNameArray(List<String> nameArray, Object scannedData) {
133170 } else {
134171 int pos = Integer .parseInt (currentName );
135172 if (pos > 0 && pos < scannedArray .length ) {
136- return getByNameArray (nameArray , pos );
173+ return getByNameArray (nameArray , scannedArray [pos ]);
174+ } else {
175+ errorMessage = String .format ("Index '%s' of path '%s' is out of bounds for '%s' of length %d." ,
176+ currentName ,
177+ path , scannedData .getClass ().getName (), scannedArray .length );
137178 }
138179 }
180+ } else {
181+ errorMessage = String .format ("Cannot access '%s' of path '%s' for '%s' because it is empty." , currentName ,
182+ path , scannedData .getClass ().getName ());
139183 }
140- } else if (scannedData != null ) {
184+ } else if (!(scannedData instanceof String ) &&
185+ !(scannedData instanceof Number ) &&
186+ !(scannedData instanceof Boolean )) {
141187 Field field = Reflections .findFieldByName (scannedData .getClass (), currentName );
142188 if (field != null ) {
143189 try {
144- // field.setAccessible(true);
190+ if (forceAccess )
191+ field .setAccessible (true );
145192 return getByNameArray (nameArray , field .get (scannedData ));
146193 } catch (IllegalAccessError | IllegalAccessException e ) {
147- log . error ("Field '{} ' of '{} ' is not accessible. {} " , currentName , scannedData . getClass (). getName () ,
148- e .getMessage ());
194+ errorMessage = String . format ("Field '%s ' of '%s ' is not accessible. %s " , currentName ,
195+ scannedData . getClass (). getName (), e .getMessage ());
149196 }
150197 }
198+ } else {
199+ errorMessage = String .format ("Cannot access '%s' of path '%s': Path too deep, no more data to scan." ,
200+ currentName , path );
151201 }
152202
153- if (this .throwExceptions ) {
154- throw new IllegalArgumentException (
155- String .format ("Cannot find '%s' of '%s' in '%s'." ,
156- currentName ,
157- path ,
158- scannedData != null ? scannedData .getClass ().getName () : "null" ));
203+ if (throwExceptions ) {
204+ if (StringUtils .isBlank (errorMessage ))
205+ errorMessage = String .format ("Cannot find '%s' of path '%s' in '%s'." ,
206+ currentName ,
207+ path ,
208+ scannedData .getClass ().getName ());
209+ if (logErrors )
210+ log .error (errorMessage );
211+ throw new IllegalArgumentException (errorMessage );
212+ } else if (StringUtils .isNotBlank (errorMessage ) && logErrors ) {
213+ log .warn ("Returning null because of: " + errorMessage );
159214 }
160215
161216 return null ;
@@ -168,7 +223,7 @@ public Object getByNameArray(List<String> nameArray, Object scannedData) {
168223 * i.e '\\/' for a literal '/' as part of a name inside the path.
169224 * <p>
170225 * Delimiter and escape characters can be changed by supplying your own {@link EscapingStringTokenizer} in the
171- * Constructor {@link GetByPath#GetByPath(String, EscapingStringTokenizer, boolean )} of this class.
226+ * Constructor {@link GetByPath#GetByPath(String, EscapingStringTokenizer, Flags )} of this class.
172227 *
173228 * @param data The data that is searched with the {@link #path}.
174229 * @return The value pointed at by path or null when nothing can be found.
@@ -179,13 +234,6 @@ public Object get(Object data) {
179234 if (StringUtils .equals (path , "/" ))
180235 return data ;
181236
182- try {
183- return getByNameArray (splitPath , data );
184- } catch (Exception e ) {
185- if (this .throwExceptions )
186- throw e ;
187- log .error ("Error while applying path '{}' to '{}': {}" , path , data .getClass ().getName (), e );
188- return null ;
189- }
237+ return getByNameArray (splitPath , data );
190238 }
191239}
0 commit comments