Skip to content

Commit a3937b2

Browse files
committed
Modified ReflectionValueExtractor to a more up to date version
ReflectionValueExtractor version with capacity to parse expressions with arrays, lists and maps.
1 parent 2976eff commit a3937b2

File tree

3 files changed

+349
-54
lines changed

3 files changed

+349
-54
lines changed
Lines changed: 266 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
package 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
*
@@ -18,13 +26,6 @@
1826

1927
import 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

Comments
 (0)