1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.model.interpolation.reflection;
20
21 import java.lang.ref.Reference;
22 import java.lang.ref.WeakReference;
23 import java.lang.reflect.Array;
24 import java.lang.reflect.InvocationTargetException;
25 import java.lang.reflect.Method;
26 import java.util.Arrays;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Optional;
30 import java.util.WeakHashMap;
31
32 import org.apache.maven.api.annotations.Nonnull;
33 import org.apache.maven.api.annotations.Nullable;
34 import org.apache.maven.model.interpolation.reflection.MethodMap.AmbiguousException;
35
36
37
38
39
40
41
42
43
44
45 @Deprecated(since = "4.0.0")
46 public class ReflectionValueExtractor {
47 private static final Object[] OBJECT_ARGS = new Object[0];
48
49
50
51
52
53
54 private static final Map<Class<?>, WeakReference<ClassMap>> CLASS_MAPS = new WeakHashMap<>();
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 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 public static Object evaluate(@Nonnull String expression, @Nullable Object root) throws IntrospectionException {
144 return evaluate(expression, root, true);
145 }
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165 public static Object evaluate(@Nonnull String expression, @Nullable Object root, boolean trimRootToken)
166 throws IntrospectionException {
167 Object value = root;
168
169
170
171
172
173
174 if (expression == null || expression.isEmpty() || !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 if (value instanceof Optional) {
221 value = ((Optional<?>) value).orElse(null);
222 }
223 return value;
224 }
225
226 private static Object getMappedValue(
227 final String expression, final int from, final int to, final Object value, final String key)
228 throws IntrospectionException {
229 if (value == null || key == null) {
230 return null;
231 }
232
233 if (value instanceof Map) {
234 return ((Map) value).get(key);
235 }
236
237 final String message = String.format(
238 "The token '%s' at position '%d' refers to a java.util.Map, but the value "
239 + "seems is an instance of '%s'",
240 expression.subSequence(from, to), from, value.getClass());
241
242 throw new IntrospectionException(message);
243 }
244
245 private static Object getIndexedValue(
246 final String expression, final int from, final int to, final Object value, final String indexStr)
247 throws IntrospectionException {
248 try {
249 int index = Integer.parseInt(indexStr);
250
251 if (value.getClass().isArray()) {
252 return Array.get(value, index);
253 }
254
255 if (value instanceof List) {
256 return ((List) value).get(index);
257 }
258 } catch (NumberFormatException | IndexOutOfBoundsException e) {
259 return null;
260 }
261
262 final String message = String.format(
263 "The token '%s' at position '%d' refers to a java.util.List or an array, but the value "
264 + "seems is an instance of '%s'",
265 expression.subSequence(from, to), from, value.getClass());
266
267 throw new IntrospectionException(message);
268 }
269
270 private static Object getPropertyValue(Object value, String property) throws IntrospectionException {
271 if (value == null || property == null || property.isEmpty()) {
272 return null;
273 }
274
275 ClassMap classMap = getClassMap(value.getClass());
276 String methodBase = Character.toTitleCase(property.charAt(0)) + property.substring(1);
277 try {
278 for (String prefix : Arrays.asList("get", "is", "to", "as")) {
279 Method method = classMap.findMethod(prefix + methodBase);
280 if (method != null) {
281 return method.invoke(value, OBJECT_ARGS);
282 }
283 }
284 return null;
285 } catch (InvocationTargetException e) {
286 throw new IntrospectionException(e.getTargetException());
287 } catch (AmbiguousException | IllegalAccessException e) {
288 throw new IntrospectionException(e);
289 }
290 }
291
292 private static ClassMap getClassMap(Class<?> clazz) {
293 Reference<ClassMap> ref = CLASS_MAPS.get(clazz);
294 ClassMap classMap = ref != null ? ref.get() : null;
295
296 if (classMap == null) {
297 classMap = new ClassMap(clazz);
298
299 CLASS_MAPS.put(clazz, new WeakReference<>(classMap));
300 }
301
302 return classMap;
303 }
304 }