1 package org.codehaus.plexus.interpolation.object;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import java.lang.reflect.Array;
20 import java.lang.reflect.Field;
21 import java.security.AccessController;
22 import java.security.PrivilegedAction;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashSet;
27 import java.util.LinkedList;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.WeakHashMap;
32
33 import org.codehaus.plexus.interpolation.BasicInterpolator;
34 import org.codehaus.plexus.interpolation.InterpolationException;
35 import org.codehaus.plexus.interpolation.Interpolator;
36 import org.codehaus.plexus.interpolation.RecursionInterceptor;
37 import org.codehaus.plexus.interpolation.SimpleRecursionInterceptor;
38
39
40
41
42
43
44
45
46
47
48
49 public class FieldBasedObjectInterpolator implements ObjectInterpolator {
50 public static final Set<String> DEFAULT_BLACKLISTED_FIELD_NAMES;
51
52 public static final Set<String> DEFAULT_BLACKLISTED_PACKAGE_PREFIXES;
53
54 private static final Map<Class, Field[]> fieldsByClass = new WeakHashMap<Class, Field[]>();
55
56 private static final Map<Class, Boolean> fieldIsPrimitiveByClass = new WeakHashMap<Class, Boolean>();
57
58 static {
59 Set<String> blacklistedFields = new HashSet<String>();
60 blacklistedFields.add("parent");
61
62 DEFAULT_BLACKLISTED_FIELD_NAMES = Collections.unmodifiableSet(blacklistedFields);
63
64 Set<String> blacklistedPackages = new HashSet<String>();
65 blacklistedPackages.add("java");
66
67 DEFAULT_BLACKLISTED_PACKAGE_PREFIXES = Collections.unmodifiableSet(blacklistedPackages);
68 }
69
70
71
72
73
74 public static void clearCaches() {
75 fieldsByClass.clear();
76 fieldIsPrimitiveByClass.clear();
77 }
78
79 private Set<String> blacklistedFieldNames;
80
81 private Set<String> blacklistedPackagePrefixes;
82
83 private List<ObjectInterpolationWarning> warnings = new ArrayList<ObjectInterpolationWarning>();
84
85
86
87
88
89 public FieldBasedObjectInterpolator() {
90 this.blacklistedFieldNames = DEFAULT_BLACKLISTED_FIELD_NAMES;
91 this.blacklistedPackagePrefixes = DEFAULT_BLACKLISTED_PACKAGE_PREFIXES;
92 }
93
94
95
96
97
98
99
100 public FieldBasedObjectInterpolator(Set<String> blacklistedFieldNames, Set<String> blacklistedPackagePrefixes) {
101 this.blacklistedFieldNames = blacklistedFieldNames;
102 this.blacklistedPackagePrefixes = blacklistedPackagePrefixes;
103 }
104
105
106
107
108 public boolean hasWarnings() {
109 return warnings != null && !warnings.isEmpty();
110 }
111
112
113
114
115
116 public List<ObjectInterpolationWarning> getWarnings() {
117 return new ArrayList<ObjectInterpolationWarning>(warnings);
118 }
119
120
121
122
123
124
125
126
127
128
129 public void interpolate(Object target, BasicInterpolator interpolator) throws InterpolationException {
130 interpolate(target, interpolator, new SimpleRecursionInterceptor());
131 }
132
133
134
135
136
137
138
139
140
141
142 public void interpolate(Object target, BasicInterpolator interpolator, RecursionInterceptor recursionInterceptor)
143 throws InterpolationException {
144 warnings.clear();
145
146 InterpolateObjectAction action = new InterpolateObjectAction(
147 target,
148 interpolator,
149 recursionInterceptor,
150 blacklistedFieldNames,
151 blacklistedPackagePrefixes,
152 warnings);
153
154 InterpolationException error = (InterpolationException) AccessController.doPrivileged(action);
155
156 if (error != null) {
157 throw error;
158 }
159 }
160
161 private static final class InterpolateObjectAction implements PrivilegedAction {
162
163 private final LinkedList<InterpolationTarget> interpolationTargets;
164
165 private final BasicInterpolator interpolator;
166
167 private final Set blacklistedFieldNames;
168
169 private final String[] blacklistedPackagePrefixes;
170
171 private final List<ObjectInterpolationWarning> warningCollector;
172
173 private final RecursionInterceptor recursionInterceptor;
174
175
176
177
178
179 public InterpolateObjectAction(
180 Object target,
181 BasicInterpolator interpolator,
182 RecursionInterceptor recursionInterceptor,
183 Set blacklistedFieldNames,
184 Set blacklistedPackagePrefixes,
185 List<ObjectInterpolationWarning> warningCollector) {
186 this.recursionInterceptor = recursionInterceptor;
187 this.blacklistedFieldNames = blacklistedFieldNames;
188 this.warningCollector = warningCollector;
189 this.blacklistedPackagePrefixes =
190 (String[]) blacklistedPackagePrefixes.toArray(new String[blacklistedPackagePrefixes.size()]);
191
192 this.interpolationTargets = new LinkedList<InterpolationTarget>();
193 interpolationTargets.add(new InterpolationTarget(target, ""));
194
195 this.interpolator = interpolator;
196 }
197
198
199
200
201
202 public Object run() {
203 while (!interpolationTargets.isEmpty()) {
204 InterpolationTarget target = interpolationTargets.removeFirst();
205
206 try {
207 traverseObjectWithParents(target.value.getClass(), target);
208 } catch (InterpolationException e) {
209 return e;
210 }
211 }
212
213 return null;
214 }
215
216
217
218
219
220 private void traverseObjectWithParents(Class cls, InterpolationTarget target) throws InterpolationException {
221 Object obj = target.value;
222 String basePath = target.path;
223
224 if (cls == null) {
225 return;
226 }
227
228 if (cls.isArray()) {
229 evaluateArray(obj, basePath);
230 } else if (isQualifiedForInterpolation(cls)) {
231 Field[] fields = fieldsByClass.get(cls);
232 if (fields == null) {
233 fields = cls.getDeclaredFields();
234 fieldsByClass.put(cls, fields);
235 }
236
237 for (Field field : fields) {
238 Class type = field.getType();
239 if (isQualifiedForInterpolation(field, type)) {
240 boolean isAccessible = field.isAccessible();
241 synchronized (cls) {
242 field.setAccessible(true);
243 try {
244 try {
245 if (String.class == type) {
246 interpolateString(obj, field);
247 } else if (Collection.class.isAssignableFrom(type)) {
248 if (interpolateCollection(obj, basePath, field)) {
249 continue;
250 }
251 } else if (Map.class.isAssignableFrom(type)) {
252 interpolateMap(obj, basePath, field);
253 } else {
254 interpolateObject(obj, basePath, field);
255 }
256 } catch (IllegalArgumentException e) {
257 warningCollector.add(new ObjectInterpolationWarning(
258 "Failed to interpolate field. Skipping.",
259 basePath + "." + field.getName(),
260 e));
261 } catch (IllegalAccessException e) {
262 warningCollector.add(new ObjectInterpolationWarning(
263 "Failed to interpolate field. Skipping.",
264 basePath + "." + field.getName(),
265 e));
266 }
267 } finally {
268 field.setAccessible(isAccessible);
269 }
270 }
271 }
272 }
273
274 traverseObjectWithParents(cls.getSuperclass(), target);
275 }
276 }
277
278 private void interpolateObject(Object obj, String basePath, Field field)
279 throws IllegalAccessException, InterpolationException {
280 Object value = field.get(obj);
281 if (value != null) {
282 if (field.getType().isArray()) {
283 evaluateArray(value, basePath + "." + field.getName());
284 } else {
285 interpolationTargets.add(new InterpolationTarget(value, basePath + "." + field.getName()));
286 }
287 }
288 }
289
290 private void interpolateMap(Object obj, String basePath, Field field)
291 throws IllegalAccessException, InterpolationException {
292 Map m = (Map) field.get(obj);
293 if (m != null && !m.isEmpty()) {
294 for (Object o : m.entrySet()) {
295 Map.Entry entry = (Map.Entry) o;
296
297 Object value = entry.getValue();
298
299 if (value != null) {
300 if (String.class == value.getClass()) {
301 String interpolated = interpolator.interpolate((String) value, recursionInterceptor);
302
303 if (!interpolated.equals(value)) {
304 try {
305 entry.setValue(interpolated);
306 } catch (UnsupportedOperationException e) {
307 warningCollector.add(new ObjectInterpolationWarning(
308 "Field is an unmodifiable collection. Skipping interpolation.",
309 basePath + "." + field.getName(),
310 e));
311 continue;
312 }
313 }
314 } else {
315 if (value.getClass().isArray()) {
316 evaluateArray(value, basePath + "." + field.getName());
317 } else {
318 interpolationTargets.add(
319 new InterpolationTarget(value, basePath + "." + field.getName()));
320 }
321 }
322 }
323 }
324 }
325 }
326
327 private boolean interpolateCollection(Object obj, String basePath, Field field)
328 throws IllegalAccessException, InterpolationException {
329 Collection c = (Collection) field.get(obj);
330 if (c != null && !c.isEmpty()) {
331 List originalValues = new ArrayList(c);
332 try {
333 c.clear();
334 } catch (UnsupportedOperationException e) {
335 warningCollector.add(new ObjectInterpolationWarning(
336 "Field is an unmodifiable collection. Skipping interpolation.",
337 basePath + "." + field.getName(),
338 e));
339 return true;
340 }
341
342 for (Object value : originalValues) {
343 if (value != null) {
344 if (String.class == value.getClass()) {
345 String interpolated = interpolator.interpolate((String) value, recursionInterceptor);
346
347 if (!interpolated.equals(value)) {
348 c.add(interpolated);
349 } else {
350 c.add(value);
351 }
352 } else {
353 c.add(value);
354 if (value.getClass().isArray()) {
355 evaluateArray(value, basePath + "." + field.getName());
356 } else {
357 interpolationTargets.add(
358 new InterpolationTarget(value, basePath + "." + field.getName()));
359 }
360 }
361 } else {
362
363 c.add(value);
364 }
365 }
366 }
367 return false;
368 }
369
370 private void interpolateString(Object obj, Field field) throws IllegalAccessException, InterpolationException {
371 String value = (String) field.get(obj);
372 if (value != null) {
373 String interpolated = interpolator.interpolate(value, recursionInterceptor);
374
375 if (!interpolated.equals(value)) {
376 field.set(obj, interpolated);
377 }
378 }
379 }
380
381
382
383
384
385 private boolean isQualifiedForInterpolation(Class cls) {
386 String pkgName = cls.getPackage().getName();
387 for (String prefix : blacklistedPackagePrefixes) {
388 if (pkgName.startsWith(prefix)) {
389 return false;
390 }
391 }
392
393 return true;
394 }
395
396
397
398
399
400
401
402 private boolean isQualifiedForInterpolation(Field field, Class fieldType) {
403 if (!fieldIsPrimitiveByClass.containsKey(fieldType)) {
404 fieldIsPrimitiveByClass.put(fieldType, fieldType.isPrimitive());
405 }
406
407
408 if (fieldIsPrimitiveByClass.get(fieldType)) {
409 return false;
410 }
411
412 return !blacklistedFieldNames.contains(field.getName());
413 }
414
415
416
417
418 private void evaluateArray(Object target, String basePath) throws InterpolationException {
419 int len = Array.getLength(target);
420 for (int i = 0; i < len; i++) {
421 Object value = Array.get(target, i);
422 if (value != null) {
423 if (String.class == value.getClass()) {
424 String interpolated = interpolator.interpolate((String) value, recursionInterceptor);
425
426 if (!interpolated.equals(value)) {
427 Array.set(target, i, interpolated);
428 }
429 } else {
430 interpolationTargets.add(new InterpolationTarget(value, basePath + "[" + i + "]"));
431 }
432 }
433 }
434 }
435 }
436
437 private static final class InterpolationTarget {
438 private Object value;
439
440 private String path;
441
442 private InterpolationTarget(Object value, String path) {
443 this.value = value;
444 this.path = path;
445 }
446 }
447 }