1515 * See the License for the specific language governing permissions and
1616 * limitations under the License.
1717 */
18-
1918import org .codehaus .plexus .interpolation .util .StringUtils ;
2019
21- import java .lang .ref .SoftReference ;
2220import java .lang .ref .WeakReference ;
21+ import java .lang .reflect .Array ;
22+ import java .lang .reflect .InvocationTargetException ;
2323import java .lang .reflect .Method ;
24+ import java .util .List ;
2425import java .util .Map ;
25- import java .util .StringTokenizer ;
2626import java .util .WeakHashMap ;
2727
2828/**
29- * <b>NOTE:</b> This class was copied from plexus-utils, to allow this library
30- * to stand completely self-contained.
31- * <br/>
32- * Using simple dotted expressions extract the values from a MavenProject
33- * instance, For example we might want to extract a value like:
34- * project.build.sourceDirectory
29+ * <b>NOTE:</b> This class was copied from plexus-utils, to allow this library to stand completely self-contained. <br/>
30+ * Using simple dotted expressions extract the values from a MavenProject instance, For example we might want to extract
31+ * a value like: project.build.sourceDirectory
3532 *
3633 * @author <a href="mailto:[email protected] ">Jason van Zyl </a> 3734 * @version $Id$
@@ -43,90 +40,316 @@ public class ReflectionValueExtractor
4340 private static final Object [] OBJECT_ARGS = new Object [0 ];
4441
4542 /**
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.
43+ * Use a WeakHashMap here, so the keys (Class objects) can be garbage collected. This approach prevents permgen
44+ * space overflows due to retention of discarded classloaders.
4945 */
50- private static final Map <Class <?>, WeakReference <ClassMap >> classMaps = new WeakHashMap <Class <?>, WeakReference <ClassMap >>();
46+ private static final Map <Class <?>, WeakReference <ClassMap >> classMaps =
47+ new WeakHashMap <Class <?>, WeakReference <ClassMap >>();
48+
49+ static final int EOF = -1 ;
50+
51+ static final char PROPERTY_START = '.' ;
52+
53+ static final char INDEXED_START = '[' ;
54+
55+ static final char INDEXED_END = ']' ;
56+
57+ static final char MAPPED_START = '(' ;
58+
59+ static final char MAPPED_END = ')' ;
60+
61+ static class Tokenizer
62+ {
63+ final String expression ;
64+
65+ int idx ;
66+
67+ public Tokenizer ( String expression )
68+ {
69+ this .expression = expression ;
70+ }
71+
72+ public int peekChar ()
73+ {
74+ return idx < expression .length () ? expression .charAt ( idx ) : EOF ;
75+ }
76+
77+ public int skipChar ()
78+ {
79+ return idx < expression .length () ? expression .charAt ( idx ++ ) : EOF ;
80+ }
81+
82+ public String nextToken ( char delimiter )
83+ {
84+ int start = idx ;
85+
86+ while ( idx < expression .length () && delimiter != expression .charAt ( idx ) )
87+ {
88+ idx ++;
89+ }
90+
91+ // delimiter MUST be present
92+ if ( idx <= start || idx >= expression .length () )
93+ {
94+ return null ;
95+ }
96+
97+ return expression .substring ( start , idx ++ );
98+ }
99+
100+ public String nextPropertyName ()
101+ {
102+ final int start = idx ;
103+
104+ while ( idx < expression .length () && Character .isJavaIdentifierPart ( expression .charAt ( idx ) ) )
105+ {
106+ idx ++;
107+ }
108+
109+ // property name does not require delimiter
110+ if ( idx <= start || idx > expression .length () )
111+ {
112+ return null ;
113+ }
114+
115+ return expression .substring ( start , idx );
116+ }
117+
118+ public int getPosition ()
119+ {
120+ return idx < expression .length () ? idx : EOF ;
121+ }
122+
123+ // to make tokenizer look pretty in debugger
124+ @ Override
125+ public String toString ()
126+ {
127+ return idx < expression .length () ? expression .substring ( idx ) : "<EOF>" ;
128+ }
129+ }
51130
52131 private ReflectionValueExtractor ()
53132 {
54133 }
55134
135+ /**
136+ * <p>
137+ * The implementation supports indexed, nested and mapped properties.
138+ * </p>
139+ * <ul>
140+ * <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
141+ * <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
142+ * pattern, i.e. "user.addresses[1].street"</li>
143+ * <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern, i.e.
144+ * "user.addresses(myAddress).street"</li>
145+ * <ul>
146+ *
147+ * @param expression not null expression
148+ * @param root not null object
149+ * @return the object defined by the expression
150+ * @throws Exception if any
151+ */
56152 public static Object evaluate ( String expression , Object root )
57153 throws Exception
58154 {
59155 return evaluate ( expression , root , true );
60156 }
61157
158+ /**
159+ * <p>
160+ * The implementation supports indexed, nested and mapped properties.
161+ * </p>
162+ * <ul>
163+ * <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
164+ * <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
165+ * pattern, i.e. "user.addresses[1].street"</li>
166+ * <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern, i.e.
167+ * "user.addresses(myAddress).street"</li>
168+ * <ul>
169+ *
170+ * @param expression not null expression
171+ * @param root not null object
172+ * @return the object defined by the expression
173+ * @throws Exception if any
174+ */
62175 // TODO: don't throw Exception
63- public static Object evaluate ( String expression , Object root , boolean trimRootToken )
176+ public static Object evaluate ( String expression , final Object root , final boolean trimRootToken )
64177 throws Exception
65178 {
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-
72179 Object value = root ;
73180
74181 // ----------------------------------------------------------------------
75182 // Walk the dots and retrieve the ultimate value desired from the
76183 // MavenProject instance.
77184 // ----------------------------------------------------------------------
78185
79- StringTokenizer parser = new StringTokenizer ( expression , "." );
80-
81- while ( parser .hasMoreTokens () )
186+ if ( expression == null || "" .equals ( expression .trim () )
187+ || !Character .isJavaIdentifierStart ( expression .charAt ( 0 ) ) )
82188 {
83- String token = parser .nextToken ();
189+ return null ;
190+ }
84191
85- if ( value == null )
192+ boolean hasDots = expression .indexOf ( PROPERTY_START ) >= 0 ;
193+
194+ final Tokenizer tokenizer ;
195+ if ( trimRootToken && hasDots )
196+ {
197+ tokenizer = new Tokenizer ( expression );
198+ tokenizer .nextPropertyName ();
199+ if ( tokenizer .getPosition () == EOF )
86200 {
87201 return null ;
88202 }
203+ }
204+ else
205+ {
206+ tokenizer = new Tokenizer ( "." + expression );
207+ }
208+
209+ int propertyPosition = tokenizer .getPosition ();
210+ while ( value != null && tokenizer .peekChar () != EOF )
211+ {
212+ switch ( tokenizer .skipChar () )
213+ {
214+ case INDEXED_START :
215+ value = getIndexedValue ( expression , propertyPosition , tokenizer .getPosition (), value ,
216+ tokenizer .nextToken ( INDEXED_END ) );
217+ break ;
218+ case MAPPED_START :
219+ value = getMappedValue ( expression , propertyPosition , tokenizer .getPosition (), value ,
220+ tokenizer .nextToken ( MAPPED_END ) );
221+ break ;
222+ case PROPERTY_START :
223+ propertyPosition = tokenizer .getPosition ();
224+ value = getPropertyValue ( value , tokenizer .nextPropertyName () );
225+ break ;
226+ default :
227+ // could not parse expression
228+ return null ;
229+ }
230+ }
231+
232+ return value ;
233+ }
89234
235+ private static Object getMappedValue ( final String expression , final int from , final int to , final Object value ,
236+ final String key )
237+ throws Exception
238+ {
239+ if ( value == null || key == null )
240+ {
241+ return null ;
242+ }
243+
244+ if ( value instanceof Map )
245+ {
246+ Object [] localParams = new Object [] { key };
90247 ClassMap classMap = getClassMap ( value .getClass () );
248+ Method method = classMap .findMethod ( "get" , localParams );
249+ return method .invoke ( value , localParams );
250+ }
91251
92- String methodBase = StringUtils .capitalizeFirstLetter ( token );
252+ final String message =
253+ String .format ( "The token '%s' at position '%d' refers to a java.util.Map, but the value seems is an instance of '%s'" ,
254+ expression .subSequence ( from , to ), from , value .getClass () );
93255
94- String methodName = "get" + methodBase ;
256+ throw new Exception ( message );
257+ }
95258
96- Method method = classMap .findMethod ( methodName , CLASS_ARGS );
259+ private static Object getIndexedValue ( final String expression , final int from , final int to , final Object value ,
260+ final String indexStr )
261+ throws Exception
262+ {
263+ try
264+ {
265+ int index = Integer .parseInt ( indexStr );
97266
98- if ( method == null )
267+ if ( value . getClass (). isArray () )
99268 {
100- // perhaps this is a boolean property??
101- methodName = "is" + methodBase ;
102-
103- method = classMap .findMethod ( methodName , CLASS_ARGS );
269+ return Array .get ( value , index );
104270 }
105271
106- if ( method == null )
272+ if ( value instanceof List )
273+ {
274+ ClassMap classMap = getClassMap ( value .getClass () );
275+ // use get method on List interface
276+ Object [] localParams = new Object [] { index };
277+ Method method = classMap .findMethod ( "get" , localParams );
278+ return method .invoke ( value , localParams );
279+ }
280+ }
281+ catch ( NumberFormatException e )
282+ {
283+ return null ;
284+ }
285+ catch ( InvocationTargetException e )
286+ {
287+ // catch array index issues gracefully, otherwise release
288+ if ( e .getCause () instanceof IndexOutOfBoundsException )
107289 {
108290 return null ;
109291 }
110292
111- value = method . invoke ( value , OBJECT_ARGS ) ;
293+ throw e ;
112294 }
113295
114- return value ;
296+ final String message =
297+ 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'" ,
298+ expression .subSequence ( from , to ), from , value .getClass () );
299+
300+ throw new Exception ( message );
301+ }
302+
303+ private static Object getPropertyValue ( Object value , String property )
304+ throws Exception
305+ {
306+ if ( value == null || property == null )
307+ {
308+ return null ;
309+ }
310+
311+ ClassMap classMap = getClassMap ( value .getClass () );
312+ String methodBase = StringUtils .capitalizeFirstLetter ( property );
313+ String methodName = "get" + methodBase ;
314+ Method method = classMap .findMethod ( methodName , CLASS_ARGS );
315+
316+ if ( method == null )
317+ {
318+ // perhaps this is a boolean property??
319+ methodName = "is" + methodBase ;
320+
321+ method = classMap .findMethod ( methodName , CLASS_ARGS );
322+ }
323+
324+ if ( method == null )
325+ {
326+ return null ;
327+ }
328+
329+ try
330+ {
331+ return method .invoke ( value , OBJECT_ARGS );
332+ }
333+ catch ( InvocationTargetException e )
334+ {
335+ throw e ;
336+ }
115337 }
116338
117339 private static ClassMap getClassMap ( Class <?> clazz )
118340 {
119- WeakReference <ClassMap > ref = classMaps .get ( clazz );
341+
342+ WeakReference <ClassMap > softRef = classMaps .get ( clazz );
120343
121344 ClassMap classMap ;
122345
123- if ( ref == null || (classMap = ref .get ()) == null )
346+ if ( softRef == null || ( classMap = softRef .get () ) == null )
124347 {
125348 classMap = new ClassMap ( clazz );
126349
127- classMaps .put ( clazz , new WeakReference <ClassMap >(classMap ) );
350+ classMaps .put ( clazz , new WeakReference <ClassMap >( classMap ) );
128351 }
129352
130353 return classMap ;
131354 }
132- }
355+ }
0 commit comments