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