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