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