11package org .codehaus .plexus .interpolation .reflection ;
22
3+ import java .lang .ref .WeakReference ;
4+ import java .lang .reflect .Array ;
5+ import java .lang .reflect .InvocationTargetException ;
6+ import java .lang .reflect .Method ;
7+ import java .util .List ;
8+ import java .util .Map ;
9+ import java .util .WeakHashMap ;
10+
311/*
412 * Copyright 2001-2006 Codehaus Foundation.
513 *
1826
1927import org .codehaus .plexus .interpolation .util .StringUtils ;
2028
21- import java .lang .ref .SoftReference ;
22- import java .lang .ref .WeakReference ;
23- import java .lang .reflect .Method ;
24- import java .util .Map ;
25- import java .util .StringTokenizer ;
26- import java .util .WeakHashMap ;
27-
2829/**
2930 * <b>NOTE:</b> This class was copied from plexus-utils, to allow this library
3031 * to stand completely self-contained.
@@ -43,90 +44,317 @@ public class ReflectionValueExtractor
4344 private static final Object [] OBJECT_ARGS = new Object [0 ];
4445
4546 /**
46- * Use a WeakHashMap here, so the keys (Class objects) can be garbage collected.
47- * This approach prevents permgen space overflows due to retention of discarded
48- * classloaders.
47+ * Use a WeakHashMap here, so the keys (Class objects) can be garbage collected. This approach prevents permgen
48+ * space overflows due to retention of discarded classloaders.
4949 */
50- private static final Map <Class <?>, WeakReference <ClassMap >> classMaps = new WeakHashMap <Class <?>, WeakReference <ClassMap >>();
50+ private static final Map <Class <?>, WeakReference <ClassMap >> classMaps =
51+ new WeakHashMap <Class <?>, WeakReference <ClassMap >>();
52+
53+ static final int EOF = -1 ;
54+
55+ static final char PROPERTY_START = '.' ;
56+
57+ static final char INDEXED_START = '[' ;
58+
59+ static final char INDEXED_END = ']' ;
60+
61+ static final char MAPPED_START = '(' ;
62+
63+ static final char MAPPED_END = ')' ;
64+
65+ static class Tokenizer
66+ {
67+ final String expression ;
68+
69+ int idx ;
70+
71+ public Tokenizer ( String expression )
72+ {
73+ this .expression = expression ;
74+ }
75+
76+ public int peekChar ()
77+ {
78+ return idx < expression .length () ? expression .charAt ( idx ) : EOF ;
79+ }
80+
81+ public int skipChar ()
82+ {
83+ return idx < expression .length () ? expression .charAt ( idx ++ ) : EOF ;
84+ }
85+
86+ public String nextToken ( char delimiter )
87+ {
88+ int start = idx ;
89+
90+ while ( idx < expression .length () && delimiter != expression .charAt ( idx ) )
91+ {
92+ idx ++;
93+ }
94+
95+ // delimiter MUST be present
96+ if ( idx <= start || idx >= expression .length () )
97+ {
98+ return null ;
99+ }
100+
101+ return expression .substring ( start , idx ++ );
102+ }
103+
104+ public String nextPropertyName ()
105+ {
106+ final int start = idx ;
107+
108+ while ( idx < expression .length () && Character .isJavaIdentifierPart ( expression .charAt ( idx ) ) )
109+ {
110+ idx ++;
111+ }
112+
113+ // property name does not require delimiter
114+ if ( idx <= start || idx > expression .length () )
115+ {
116+ return null ;
117+ }
118+
119+ return expression .substring ( start , idx );
120+ }
121+
122+ public int getPosition ()
123+ {
124+ return idx < expression .length () ? idx : EOF ;
125+ }
126+
127+ // to make tokenizer look pretty in debugger
128+ @ Override
129+ public String toString ()
130+ {
131+ return idx < expression .length () ? expression .substring ( idx ) : "<EOF>" ;
132+ }
133+ }
51134
52135 private ReflectionValueExtractor ()
53136 {
54137 }
55138
139+ /**
140+ * <p>
141+ * The implementation supports indexed, nested and mapped properties.
142+ * </p>
143+ * <ul>
144+ * <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
145+ * <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
146+ * pattern, i.e. "user.addresses[1].street"</li>
147+ * <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern, i.e.
148+ * "user.addresses(myAddress).street"</li>
149+ * <ul>
150+ *
151+ * @param expression not null expression
152+ * @param root not null object
153+ * @return the object defined by the expression
154+ * @throws Exception if any
155+ */
56156 public static Object evaluate ( String expression , Object root )
57157 throws Exception
58158 {
59159 return evaluate ( expression , root , true );
60160 }
61161
162+ /**
163+ * <p>
164+ * The implementation supports indexed, nested and mapped properties.
165+ * </p>
166+ * <ul>
167+ * <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
168+ * <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
169+ * pattern, i.e. "user.addresses[1].street"</li>
170+ * <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern, i.e.
171+ * "user.addresses(myAddress).street"</li>
172+ * <ul>
173+ *
174+ * @param expression not null expression
175+ * @param root not null object
176+ * @return the object defined by the expression
177+ * @throws Exception if any
178+ */
62179 // TODO: don't throw Exception
63- public static Object evaluate ( String expression , Object root , boolean trimRootToken )
180+ public static Object evaluate ( String expression , final Object root , final boolean trimRootToken )
64181 throws Exception
65182 {
66- // if the root token refers to the supplied root object parameter, remove it.
67- if ( trimRootToken )
68- {
69- expression = expression .substring ( expression .indexOf ( '.' ) + 1 );
70- }
71-
72183 Object value = root ;
73184
74185 // ----------------------------------------------------------------------
75186 // Walk the dots and retrieve the ultimate value desired from the
76187 // MavenProject instance.
77188 // ----------------------------------------------------------------------
78189
79- StringTokenizer parser = new StringTokenizer ( expression , "." );
80-
81- while ( parser .hasMoreTokens () )
190+ if ( expression == null || "" .equals (expression .trim ()) || !Character .isJavaIdentifierStart ( expression .charAt ( 0 ) ) )
82191 {
83- String token = parser .nextToken ();
192+ return null ;
193+ }
84194
85- if ( value == null )
195+ boolean hasDots = expression .indexOf ( PROPERTY_START ) >= 0 ;
196+
197+ final Tokenizer tokenizer ;
198+ if ( trimRootToken && hasDots )
199+ {
200+ tokenizer = new Tokenizer ( expression );
201+ tokenizer .nextPropertyName ();
202+ if ( tokenizer .getPosition () == EOF )
86203 {
87204 return null ;
88205 }
206+ }
207+ else
208+ {
209+ tokenizer = new Tokenizer ( "." + expression );
210+ }
211+
212+ int propertyPosition = tokenizer .getPosition ();
213+ while ( value != null && tokenizer .peekChar () != EOF )
214+ {
215+ switch ( tokenizer .skipChar () )
216+ {
217+ case INDEXED_START :
218+ value =
219+ getIndexedValue ( expression , propertyPosition , tokenizer .getPosition (), value ,
220+ tokenizer .nextToken ( INDEXED_END ) );
221+ break ;
222+ case MAPPED_START :
223+ value =
224+ getMappedValue ( expression , propertyPosition , tokenizer .getPosition (), value ,
225+ tokenizer .nextToken ( MAPPED_END ) );
226+ break ;
227+ case PROPERTY_START :
228+ propertyPosition = tokenizer .getPosition ();
229+ value = getPropertyValue ( value , tokenizer .nextPropertyName () );
230+ break ;
231+ default :
232+ // could not parse expression
233+ return null ;
234+ }
235+ }
236+
237+ return value ;
238+ }
89239
240+ private static Object getMappedValue ( final String expression , final int from , final int to , final Object value ,
241+ final String key )
242+ throws Exception
243+ {
244+ if ( value == null || key == null )
245+ {
246+ return null ;
247+ }
248+
249+ if ( value instanceof Map )
250+ {
251+ Object [] localParams = new Object [] { key };
90252 ClassMap classMap = getClassMap ( value .getClass () );
253+ Method method = classMap .findMethod ( "get" , localParams );
254+ return method .invoke ( value , localParams );
255+ }
91256
92- String methodBase = StringUtils .capitalizeFirstLetter ( token );
257+ final String message =
258+ String .format ( "The token '%s' at position '%d' refers to a java.util.Map, but the value seems is an instance of '%s'" ,
259+ expression .subSequence ( from , to ), from , value .getClass () );
93260
94- String methodName = "get" + methodBase ;
261+ throw new Exception ( message );
262+ }
95263
96- Method method = classMap .findMethod ( methodName , CLASS_ARGS );
264+ private static Object getIndexedValue ( final String expression , final int from , final int to , final Object value ,
265+ final String indexStr )
266+ throws Exception
267+ {
268+ try
269+ {
270+ int index = Integer .parseInt ( indexStr );
97271
98- if ( method == null )
272+ if ( value . getClass (). isArray () )
99273 {
100- // perhaps this is a boolean property??
101- methodName = "is" + methodBase ;
102-
103- method = classMap .findMethod ( methodName , CLASS_ARGS );
274+ return Array .get ( value , index );
104275 }
105276
106- if ( method == null )
277+ if ( value instanceof List )
278+ {
279+ ClassMap classMap = getClassMap ( value .getClass () );
280+ // use get method on List interface
281+ Object [] localParams = new Object [] { index };
282+ Method method = classMap .findMethod ( "get" , localParams );
283+ return method .invoke ( value , localParams );
284+ }
285+ }
286+ catch ( NumberFormatException e )
287+ {
288+ return null ;
289+ }
290+ catch ( InvocationTargetException e )
291+ {
292+ // catch array index issues gracefully, otherwise release
293+ if ( e .getCause () instanceof IndexOutOfBoundsException )
107294 {
108295 return null ;
109296 }
110297
111- value = method . invoke ( value , OBJECT_ARGS ) ;
298+ throw e ;
112299 }
113300
114- return value ;
301+ final String message =
302+ 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'" ,
303+ expression .subSequence ( from , to ), from , value .getClass () );
304+
305+ throw new Exception ( message );
306+ }
307+
308+ private static Object getPropertyValue ( Object value , String property )
309+ throws Exception
310+ {
311+ if ( value == null || property == null )
312+ {
313+ return null ;
314+ }
315+
316+ ClassMap classMap = getClassMap ( value .getClass () );
317+ String methodBase = StringUtils .capitalizeFirstLetter ( property );
318+ String methodName = "get" + methodBase ;
319+ Method method = classMap .findMethod ( methodName , CLASS_ARGS );
320+
321+ if ( method == null )
322+ {
323+ // perhaps this is a boolean property??
324+ methodName = "is" + methodBase ;
325+
326+ method = classMap .findMethod ( methodName , CLASS_ARGS );
327+ }
328+
329+ if ( method == null )
330+ {
331+ return null ;
332+ }
333+
334+ try
335+ {
336+ return method .invoke ( value , OBJECT_ARGS );
337+ }
338+ catch ( InvocationTargetException e )
339+ {
340+ throw e ;
341+ }
115342 }
116343
117344 private static ClassMap getClassMap ( Class <?> clazz )
118345 {
119- WeakReference <ClassMap > ref = classMaps .get ( clazz );
346+
347+ WeakReference <ClassMap > softRef = classMaps .get ( clazz );
120348
121349 ClassMap classMap ;
122350
123- if ( ref == null || (classMap = ref .get ()) == null )
351+ if ( softRef == null || ( classMap = softRef .get () ) == null )
124352 {
125353 classMap = new ClassMap ( clazz );
126354
127- classMaps .put ( clazz , new WeakReference <ClassMap >(classMap ) );
355+ classMaps .put ( clazz , new WeakReference <ClassMap >( classMap ) );
128356 }
129357
130358 return classMap ;
131359 }
132- }
360+ }
0 commit comments