1 package org.codehaus.plexus.interpolation.reflection;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import java.lang.ref.WeakReference;
19 import java.lang.reflect.Array;
20 import java.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.Method;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.WeakHashMap;
25
26 import org.codehaus.plexus.interpolation.util.StringUtils;
27
28
29
30
31
32
33
34
35 public class ReflectionValueExtractor {
36 private static final Class<?>[] CLASS_ARGS = new Class[0];
37
38 private static final Object[] OBJECT_ARGS = new Object[0];
39
40
41
42
43
44 private static final Map<Class<?>, WeakReference<ClassMap>> classMaps =
45 new WeakHashMap<Class<?>, WeakReference<ClassMap>>();
46
47 static final int EOF = -1;
48
49 static final char PROPERTY_START = '.';
50
51 static final char INDEXED_START = '[';
52
53 static final char INDEXED_END = ']';
54
55 static final char MAPPED_START = '(';
56
57 static final char MAPPED_END = ')';
58
59 static class Tokenizer {
60 final String expression;
61
62 int idx;
63
64 public Tokenizer(String expression) {
65 this.expression = expression;
66 }
67
68 public int peekChar() {
69 return idx < expression.length() ? expression.charAt(idx) : EOF;
70 }
71
72 public int skipChar() {
73 return idx < expression.length() ? expression.charAt(idx++) : EOF;
74 }
75
76 public String nextToken(char delimiter) {
77 int start = idx;
78
79 while (idx < expression.length() && delimiter != expression.charAt(idx)) {
80 idx++;
81 }
82
83
84 if (idx <= start || idx >= expression.length()) {
85 return null;
86 }
87
88 return expression.substring(start, idx++);
89 }
90
91 public String nextPropertyName() {
92 final int start = idx;
93
94 while (idx < expression.length() && Character.isJavaIdentifierPart(expression.charAt(idx))) {
95 idx++;
96 }
97
98
99 if (idx <= start || idx > expression.length()) {
100 return null;
101 }
102
103 return expression.substring(start, idx);
104 }
105
106 public int getPosition() {
107 return idx < expression.length() ? idx : EOF;
108 }
109
110
111 @Override
112 public String toString() {
113 return idx < expression.length() ? expression.substring(idx) : "<EOF>";
114 }
115 }
116
117 private ReflectionValueExtractor() {}
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136 public static Object evaluate(String expression, Object root) throws Exception {
137 return evaluate(expression, root, true);
138 }
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159 public static Object evaluate(String expression, final Object root, final boolean trimRootToken) throws Exception {
160 Object value = root;
161
162
163
164
165
166
167 if (expression == null
168 || "".equals(expression.trim())
169 || !Character.isJavaIdentifierStart(expression.charAt(0))) {
170 return null;
171 }
172
173 boolean hasDots = expression.indexOf(PROPERTY_START) >= 0;
174
175 final Tokenizer tokenizer;
176 if (trimRootToken && hasDots) {
177 tokenizer = new Tokenizer(expression);
178 tokenizer.nextPropertyName();
179 if (tokenizer.getPosition() == EOF) {
180 return null;
181 }
182 } else {
183 tokenizer = new Tokenizer("." + expression);
184 }
185
186 int propertyPosition = tokenizer.getPosition();
187 while (value != null && tokenizer.peekChar() != EOF) {
188 switch (tokenizer.skipChar()) {
189 case INDEXED_START:
190 value = getIndexedValue(
191 expression,
192 propertyPosition,
193 tokenizer.getPosition(),
194 value,
195 tokenizer.nextToken(INDEXED_END));
196 break;
197 case MAPPED_START:
198 value = getMappedValue(
199 expression,
200 propertyPosition,
201 tokenizer.getPosition(),
202 value,
203 tokenizer.nextToken(MAPPED_END));
204 break;
205 case PROPERTY_START:
206 propertyPosition = tokenizer.getPosition();
207 value = getPropertyValue(value, tokenizer.nextPropertyName());
208 break;
209 default:
210
211 return null;
212 }
213 }
214
215 return value;
216 }
217
218 private static Object getMappedValue(
219 final String expression, final int from, final int to, final Object value, final String key)
220 throws Exception {
221 if (value == null || key == null) {
222 return null;
223 }
224
225 if (value instanceof Map) {
226 Object[] localParams = new Object[] {key};
227 ClassMap classMap = getClassMap(value.getClass());
228 Method method = classMap.findMethod("get", localParams);
229 return method.invoke(value, localParams);
230 }
231
232 final String message = String.format(
233 "The token '%s' at position '%d' refers to a java.util.Map, but the value seems is an instance of '%s'",
234 expression.subSequence(from, to), from, value.getClass());
235
236 throw new Exception(message);
237 }
238
239 private static Object getIndexedValue(
240 final String expression, final int from, final int to, final Object value, final String indexStr)
241 throws Exception {
242 try {
243 int index = Integer.parseInt(indexStr);
244
245 if (value.getClass().isArray()) {
246 return Array.get(value, index);
247 }
248
249 if (value instanceof List) {
250 ClassMap classMap = getClassMap(value.getClass());
251
252 Object[] localParams = new Object[] {index};
253 Method method = classMap.findMethod("get", localParams);
254 return method.invoke(value, localParams);
255 }
256 } catch (NumberFormatException e) {
257 return null;
258 } catch (InvocationTargetException e) {
259
260 if (e.getCause() instanceof IndexOutOfBoundsException) {
261 return null;
262 }
263
264 throw e;
265 }
266
267 final String message = String.format(
268 "The token '%s' at position '%d' refers to a java.util.List or an array, but the value seems is an instance of '%s'",
269 expression.subSequence(from, to), from, value.getClass());
270
271 throw new Exception(message);
272 }
273
274 private static Object getPropertyValue(Object value, String property) throws Exception {
275 if (value == null || property == null) {
276 return null;
277 }
278
279 ClassMap classMap = getClassMap(value.getClass());
280 String methodBase = StringUtils.capitalizeFirstLetter(property);
281 String methodName = "get" + methodBase;
282 Method method = classMap.findMethod(methodName, CLASS_ARGS);
283
284 if (method == null) {
285
286 methodName = "is" + methodBase;
287
288 method = classMap.findMethod(methodName, CLASS_ARGS);
289 }
290
291 if (method == null) {
292 return null;
293 }
294
295 try {
296 return method.invoke(value, OBJECT_ARGS);
297 } catch (InvocationTargetException e) {
298 throw e;
299 }
300 }
301
302 private static ClassMap getClassMap(Class<?> clazz) {
303
304 WeakReference<ClassMap> softRef = classMaps.get(clazz);
305
306 ClassMap classMap;
307
308 if (softRef == null || (classMap = softRef.get()) == null) {
309 classMap = new ClassMap(clazz);
310
311 classMaps.put(clazz, new WeakReference<ClassMap>(classMap));
312 }
313
314 return classMap;
315 }
316 }