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
188 public static Object evaluate( @Nonnull String expression, @Nullable Object root, boolean trimRootToken )
189 throws IntrospectionException
190 {
191 Object value = root;
192
193
194
195
196
197
198 if ( StringUtils.isEmpty( expression ) || !Character.isJavaIdentifierStart( expression.charAt( 0 ) ) )
199 {
200 return null;
201 }
202
203 boolean hasDots = expression.indexOf( PROPERTY_START ) >= 0;
204
205 final Tokenizer tokenizer;
206 if ( trimRootToken && hasDots )
207 {
208 tokenizer = new Tokenizer( expression );
209 tokenizer.nextPropertyName();
210 if ( tokenizer.getPosition() == EOF )
211 {
212 return null;
213 }
214 }
215 else
216 {
217 tokenizer = new Tokenizer( "." + expression );
218 }
219
220 int propertyPosition = tokenizer.getPosition();
221 while ( value != null && tokenizer.peekChar() != EOF )
222 {
223 switch ( tokenizer.skipChar() )
224 {
225 case INDEXED_START:
226 value =
227 getIndexedValue( expression, propertyPosition, tokenizer.getPosition(), value,
228 tokenizer.nextToken( INDEXED_END ) );
229 break;
230 case MAPPED_START:
231 value =
232 getMappedValue( expression, propertyPosition, tokenizer.getPosition(), value,
233 tokenizer.nextToken( MAPPED_END ) );
234 break;
235 case PROPERTY_START:
236 propertyPosition = tokenizer.getPosition();
237 value = getPropertyValue( value, tokenizer.nextPropertyName() );
238 break;
239 default:
240
241 return null;
242 }
243 }
244
245 return value;
246 }
247
248 private static Object getMappedValue( final String expression, final int from, final int to, final Object value,
249 final String key )
250 throws IntrospectionException
251 {
252 if ( value == null || key == null )
253 {
254 return null;
255 }
256
257 if ( value instanceof Map )
258 {
259 Object[] localParams = new Object[] { key };
260 ClassMap classMap = getClassMap( value.getClass() );
261 try
262 {
263 Method method = classMap.findMethod( "get", localParams );
264 return method.invoke( value, localParams );
265 }
266 catch ( AmbiguousException e )
267 {
268 throw new IntrospectionException( e );
269 }
270 catch ( IllegalAccessException e )
271 {
272 throw new IntrospectionException( e );
273 }
274 catch ( InvocationTargetException e )
275 {
276 throw new IntrospectionException( e.getTargetException() );
277 }
278
279 }
280
281 final String message =
282 String.format( "The token '%s' at position '%d' refers to a java.util.Map, but the value "
283 + "seems is an instance of '%s'", expression.subSequence( from, to ), from, value.getClass() );
284
285 throw new IntrospectionException( message );
286 }
287
288 private static Object getIndexedValue( final String expression, final int from, final int to, final Object value,
289 final String indexStr )
290 throws IntrospectionException
291 {
292 try
293 {
294 int index = Integer.parseInt( indexStr );
295
296 if ( value.getClass().isArray() )
297 {
298 return Array.get( value, index );
299 }
300
301 if ( value instanceof List )
302 {
303 ClassMap classMap = getClassMap( value.getClass() );
304
305 Object[] localParams = new Object[] { index };
306 Method method = null;
307 try
308 {
309 method = classMap.findMethod( "get", localParams );
310 return method.invoke( value, localParams );
311 }
312 catch ( AmbiguousException e )
313 {
314 throw new IntrospectionException( e );
315 }
316 catch ( IllegalAccessException e )
317 {
318 throw new IntrospectionException( e );
319 }
320 }
321 }
322 catch ( NumberFormatException e )
323 {
324 return null;
325 }
326 catch ( InvocationTargetException e )
327 {
328
329 if ( e.getCause() instanceof IndexOutOfBoundsException )
330 {
331 return null;
332 }
333
334 throw new IntrospectionException( e.getTargetException() );
335 }
336
337 final String message =
338 String.format( "The token '%s' at position '%d' refers to a java.util.List or an array, but the value "
339 + "seems is an instance of '%s'", expression.subSequence( from, to ), from, value.getClass() );
340
341 throw new IntrospectionException( message );
342 }
343
344 private static Object getPropertyValue( Object value, String property )
345 throws IntrospectionException
346 {
347 if ( value == null || property == null )
348 {
349 return null;
350 }
351
352 ClassMap classMap = getClassMap( value.getClass() );
353 String methodBase = StringUtils.capitalizeFirstLetter( property );
354 String methodName = "get" + methodBase;
355 try
356 {
357 Method method = classMap.findMethod( methodName, CLASS_ARGS );
358
359 if ( method == null )
360 {
361
362 methodName = "is" + methodBase;
363
364 method = classMap.findMethod( methodName, CLASS_ARGS );
365 }
366
367 if ( method == null )
368 {
369 return null;
370 }
371
372 return method.invoke( value, OBJECT_ARGS );
373 }
374 catch ( InvocationTargetException e )
375 {
376 throw new IntrospectionException( e.getTargetException() );
377 }
378 catch ( AmbiguousException e )
379 {
380 throw new IntrospectionException( e );
381 }
382 catch ( IllegalAccessException e )
383 {
384 throw new IntrospectionException( e );
385 }
386 }
387
388 private static ClassMap getClassMap( Class<?> clazz )
389 {
390 ClassMap classMap = CLASS_MAPS.get( clazz );
391
392 if ( classMap == null )
393 {
394 classMap = new ClassMap( clazz );
395
396 CLASS_MAPS.put( clazz, classMap );
397 }
398
399 return classMap;
400 }
401 }