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