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