15
15
* See the License for the specific language governing permissions and
16
16
* limitations under the License.
17
17
*/
18
-
19
18
import org .codehaus .plexus .interpolation .util .StringUtils ;
20
19
21
- import java .lang .ref .SoftReference ;
22
20
import java .lang .ref .WeakReference ;
21
+ import java .lang .reflect .Array ;
22
+ import java .lang .reflect .InvocationTargetException ;
23
23
import java .lang .reflect .Method ;
24
+ import java .util .List ;
24
25
import java .util .Map ;
25
- import java .util .StringTokenizer ;
26
26
import java .util .WeakHashMap ;
27
27
28
28
/**
29
- * <b>NOTE:</b> This class was copied from plexus-utils, to allow this library
30
- * to stand completely self-contained.
31
- * <br/>
32
- * Using simple dotted expressions extract the values from a MavenProject
33
- * instance, For example we might want to extract a value like:
34
- * project.build.sourceDirectory
29
+ * <b>NOTE:</b> This class was copied from plexus-utils, to allow this library to stand completely self-contained. <br/>
30
+ * Using simple dotted expressions extract the values from a MavenProject instance, For example we might want to extract
31
+ * a value like: project.build.sourceDirectory
35
32
*
36
33
* @author <a href="mailto:[email protected] ">Jason van Zyl </a>
37
34
* @version $Id$
@@ -43,90 +40,316 @@ public class ReflectionValueExtractor
43
40
private static final Object [] OBJECT_ARGS = new Object [0 ];
44
41
45
42
/**
46
- * Use a WeakHashMap here, so the keys (Class objects) can be garbage collected.
47
- * This approach prevents permgen space overflows due to retention of discarded
48
- * classloaders.
43
+ * Use a WeakHashMap here, so the keys (Class objects) can be garbage collected. This approach prevents permgen
44
+ * space overflows due to retention of discarded classloaders.
49
45
*/
50
- private static final Map <Class <?>, WeakReference <ClassMap >> classMaps = new WeakHashMap <Class <?>, WeakReference <ClassMap >>();
46
+ private static final Map <Class <?>, WeakReference <ClassMap >> classMaps =
47
+ new WeakHashMap <Class <?>, WeakReference <ClassMap >>();
48
+
49
+ static final int EOF = -1 ;
50
+
51
+ static final char PROPERTY_START = '.' ;
52
+
53
+ static final char INDEXED_START = '[' ;
54
+
55
+ static final char INDEXED_END = ']' ;
56
+
57
+ static final char MAPPED_START = '(' ;
58
+
59
+ static final char MAPPED_END = ')' ;
60
+
61
+ static class Tokenizer
62
+ {
63
+ final String expression ;
64
+
65
+ int idx ;
66
+
67
+ public Tokenizer ( String expression )
68
+ {
69
+ this .expression = expression ;
70
+ }
71
+
72
+ public int peekChar ()
73
+ {
74
+ return idx < expression .length () ? expression .charAt ( idx ) : EOF ;
75
+ }
76
+
77
+ public int skipChar ()
78
+ {
79
+ return idx < expression .length () ? expression .charAt ( idx ++ ) : EOF ;
80
+ }
81
+
82
+ public String nextToken ( char delimiter )
83
+ {
84
+ int start = idx ;
85
+
86
+ while ( idx < expression .length () && delimiter != expression .charAt ( idx ) )
87
+ {
88
+ idx ++;
89
+ }
90
+
91
+ // delimiter MUST be present
92
+ if ( idx <= start || idx >= expression .length () )
93
+ {
94
+ return null ;
95
+ }
96
+
97
+ return expression .substring ( start , idx ++ );
98
+ }
99
+
100
+ public String nextPropertyName ()
101
+ {
102
+ final int start = idx ;
103
+
104
+ while ( idx < expression .length () && Character .isJavaIdentifierPart ( expression .charAt ( idx ) ) )
105
+ {
106
+ idx ++;
107
+ }
108
+
109
+ // property name does not require delimiter
110
+ if ( idx <= start || idx > expression .length () )
111
+ {
112
+ return null ;
113
+ }
114
+
115
+ return expression .substring ( start , idx );
116
+ }
117
+
118
+ public int getPosition ()
119
+ {
120
+ return idx < expression .length () ? idx : EOF ;
121
+ }
122
+
123
+ // to make tokenizer look pretty in debugger
124
+ @ Override
125
+ public String toString ()
126
+ {
127
+ return idx < expression .length () ? expression .substring ( idx ) : "<EOF>" ;
128
+ }
129
+ }
51
130
52
131
private ReflectionValueExtractor ()
53
132
{
54
133
}
55
134
135
+ /**
136
+ * <p>
137
+ * The implementation supports indexed, nested and mapped properties.
138
+ * </p>
139
+ * <ul>
140
+ * <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
141
+ * <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
142
+ * pattern, i.e. "user.addresses[1].street"</li>
143
+ * <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern, i.e.
144
+ * "user.addresses(myAddress).street"</li>
145
+ * <ul>
146
+ *
147
+ * @param expression not null expression
148
+ * @param root not null object
149
+ * @return the object defined by the expression
150
+ * @throws Exception if any
151
+ */
56
152
public static Object evaluate ( String expression , Object root )
57
153
throws Exception
58
154
{
59
155
return evaluate ( expression , root , true );
60
156
}
61
157
158
+ /**
159
+ * <p>
160
+ * The implementation supports indexed, nested and mapped properties.
161
+ * </p>
162
+ * <ul>
163
+ * <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
164
+ * <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
165
+ * pattern, i.e. "user.addresses[1].street"</li>
166
+ * <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern, i.e.
167
+ * "user.addresses(myAddress).street"</li>
168
+ * <ul>
169
+ *
170
+ * @param expression not null expression
171
+ * @param root not null object
172
+ * @return the object defined by the expression
173
+ * @throws Exception if any
174
+ */
62
175
// TODO: don't throw Exception
63
- public static Object evaluate ( String expression , Object root , boolean trimRootToken )
176
+ public static Object evaluate ( String expression , final Object root , final boolean trimRootToken )
64
177
throws Exception
65
178
{
66
- // if the root token refers to the supplied root object parameter, remove it.
67
- if ( trimRootToken )
68
- {
69
- expression = expression .substring ( expression .indexOf ( '.' ) + 1 );
70
- }
71
-
72
179
Object value = root ;
73
180
74
181
// ----------------------------------------------------------------------
75
182
// Walk the dots and retrieve the ultimate value desired from the
76
183
// MavenProject instance.
77
184
// ----------------------------------------------------------------------
78
185
79
- StringTokenizer parser = new StringTokenizer ( expression , "." );
80
-
81
- while ( parser .hasMoreTokens () )
186
+ if ( expression == null || "" .equals ( expression .trim () )
187
+ || !Character .isJavaIdentifierStart ( expression .charAt ( 0 ) ) )
82
188
{
83
- String token = parser .nextToken ();
189
+ return null ;
190
+ }
84
191
85
- if ( value == null )
192
+ boolean hasDots = expression .indexOf ( PROPERTY_START ) >= 0 ;
193
+
194
+ final Tokenizer tokenizer ;
195
+ if ( trimRootToken && hasDots )
196
+ {
197
+ tokenizer = new Tokenizer ( expression );
198
+ tokenizer .nextPropertyName ();
199
+ if ( tokenizer .getPosition () == EOF )
86
200
{
87
201
return null ;
88
202
}
203
+ }
204
+ else
205
+ {
206
+ tokenizer = new Tokenizer ( "." + expression );
207
+ }
208
+
209
+ int propertyPosition = tokenizer .getPosition ();
210
+ while ( value != null && tokenizer .peekChar () != EOF )
211
+ {
212
+ switch ( tokenizer .skipChar () )
213
+ {
214
+ case INDEXED_START :
215
+ value = getIndexedValue ( expression , propertyPosition , tokenizer .getPosition (), value ,
216
+ tokenizer .nextToken ( INDEXED_END ) );
217
+ break ;
218
+ case MAPPED_START :
219
+ value = getMappedValue ( expression , propertyPosition , tokenizer .getPosition (), value ,
220
+ tokenizer .nextToken ( MAPPED_END ) );
221
+ break ;
222
+ case PROPERTY_START :
223
+ propertyPosition = tokenizer .getPosition ();
224
+ value = getPropertyValue ( value , tokenizer .nextPropertyName () );
225
+ break ;
226
+ default :
227
+ // could not parse expression
228
+ return null ;
229
+ }
230
+ }
231
+
232
+ return value ;
233
+ }
89
234
235
+ private static Object getMappedValue ( final String expression , final int from , final int to , final Object value ,
236
+ final String key )
237
+ throws Exception
238
+ {
239
+ if ( value == null || key == null )
240
+ {
241
+ return null ;
242
+ }
243
+
244
+ if ( value instanceof Map )
245
+ {
246
+ Object [] localParams = new Object [] { key };
90
247
ClassMap classMap = getClassMap ( value .getClass () );
248
+ Method method = classMap .findMethod ( "get" , localParams );
249
+ return method .invoke ( value , localParams );
250
+ }
91
251
92
- String methodBase = StringUtils .capitalizeFirstLetter ( token );
252
+ final String message =
253
+ String .format ( "The token '%s' at position '%d' refers to a java.util.Map, but the value seems is an instance of '%s'" ,
254
+ expression .subSequence ( from , to ), from , value .getClass () );
93
255
94
- String methodName = "get" + methodBase ;
256
+ throw new Exception ( message );
257
+ }
95
258
96
- Method method = classMap .findMethod ( methodName , CLASS_ARGS );
259
+ private static Object getIndexedValue ( final String expression , final int from , final int to , final Object value ,
260
+ final String indexStr )
261
+ throws Exception
262
+ {
263
+ try
264
+ {
265
+ int index = Integer .parseInt ( indexStr );
97
266
98
- if ( method == null )
267
+ if ( value . getClass (). isArray () )
99
268
{
100
- // perhaps this is a boolean property??
101
- methodName = "is" + methodBase ;
102
-
103
- method = classMap .findMethod ( methodName , CLASS_ARGS );
269
+ return Array .get ( value , index );
104
270
}
105
271
106
- if ( method == null )
272
+ if ( value instanceof List )
273
+ {
274
+ ClassMap classMap = getClassMap ( value .getClass () );
275
+ // use get method on List interface
276
+ Object [] localParams = new Object [] { index };
277
+ Method method = classMap .findMethod ( "get" , localParams );
278
+ return method .invoke ( value , localParams );
279
+ }
280
+ }
281
+ catch ( NumberFormatException e )
282
+ {
283
+ return null ;
284
+ }
285
+ catch ( InvocationTargetException e )
286
+ {
287
+ // catch array index issues gracefully, otherwise release
288
+ if ( e .getCause () instanceof IndexOutOfBoundsException )
107
289
{
108
290
return null ;
109
291
}
110
292
111
- value = method . invoke ( value , OBJECT_ARGS ) ;
293
+ throw e ;
112
294
}
113
295
114
- return value ;
296
+ final String message =
297
+ String .format ( "The token '%s' at position '%d' refers to a java.util.List or an array, but the value seems is an instance of '%s'" ,
298
+ expression .subSequence ( from , to ), from , value .getClass () );
299
+
300
+ throw new Exception ( message );
301
+ }
302
+
303
+ private static Object getPropertyValue ( Object value , String property )
304
+ throws Exception
305
+ {
306
+ if ( value == null || property == null )
307
+ {
308
+ return null ;
309
+ }
310
+
311
+ ClassMap classMap = getClassMap ( value .getClass () );
312
+ String methodBase = StringUtils .capitalizeFirstLetter ( property );
313
+ String methodName = "get" + methodBase ;
314
+ Method method = classMap .findMethod ( methodName , CLASS_ARGS );
315
+
316
+ if ( method == null )
317
+ {
318
+ // perhaps this is a boolean property??
319
+ methodName = "is" + methodBase ;
320
+
321
+ method = classMap .findMethod ( methodName , CLASS_ARGS );
322
+ }
323
+
324
+ if ( method == null )
325
+ {
326
+ return null ;
327
+ }
328
+
329
+ try
330
+ {
331
+ return method .invoke ( value , OBJECT_ARGS );
332
+ }
333
+ catch ( InvocationTargetException e )
334
+ {
335
+ throw e ;
336
+ }
115
337
}
116
338
117
339
private static ClassMap getClassMap ( Class <?> clazz )
118
340
{
119
- WeakReference <ClassMap > ref = classMaps .get ( clazz );
341
+
342
+ WeakReference <ClassMap > softRef = classMaps .get ( clazz );
120
343
121
344
ClassMap classMap ;
122
345
123
- if ( ref == null || (classMap = ref .get ()) == null )
346
+ if ( softRef == null || ( classMap = softRef .get () ) == null )
124
347
{
125
348
classMap = new ClassMap ( clazz );
126
349
127
- classMaps .put ( clazz , new WeakReference <ClassMap >(classMap ) );
350
+ classMaps .put ( clazz , new WeakReference <ClassMap >( classMap ) );
128
351
}
129
352
130
353
return classMap ;
131
354
}
132
- }
355
+ }
0 commit comments