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 }