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