1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.project.interpolation;
20
21 import java.io.File;
22 import java.lang.reflect.Array;
23 import java.lang.reflect.Field;
24 import java.security.AccessController;
25 import java.security.PrivilegedAction;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.LinkedList;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.WeakHashMap;
32 import org.apache.maven.model.Model;
33 import org.apache.maven.project.ProjectBuilderConfiguration;
34 import org.apache.maven.project.path.PathTranslator;
35 import org.codehaus.plexus.component.annotations.Component;
36 import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
37 import org.codehaus.plexus.interpolation.Interpolator;
38 import org.codehaus.plexus.interpolation.StringSearchInterpolator;
39 import org.codehaus.plexus.interpolation.ValueSource;
40 import org.codehaus.plexus.logging.Logger;
41
42
43
44
45 @Deprecated
46 @Component(role = ModelInterpolator.class)
47 public class StringSearchModelInterpolator extends AbstractStringBasedModelInterpolator {
48
49 private static final Map<Class<?>, Field[]> FIELDS_BY_CLASS = new WeakHashMap<>();
50 private static final Map<Class<?>, Boolean> PRIMITIVE_BY_CLASS = new WeakHashMap<>();
51
52 public StringSearchModelInterpolator() {}
53
54 public StringSearchModelInterpolator(PathTranslator pathTranslator) {
55 super(pathTranslator);
56 }
57
58 public Model interpolate(Model model, File projectDir, ProjectBuilderConfiguration config, boolean debugEnabled)
59 throws ModelInterpolationException {
60 interpolateObject(model, model, projectDir, config, debugEnabled);
61
62 return model;
63 }
64
65 protected void interpolateObject(
66 Object obj, Model model, File projectDir, ProjectBuilderConfiguration config, boolean debugEnabled)
67 throws ModelInterpolationException {
68 try {
69 List<ValueSource> valueSources = createValueSources(model, projectDir, config);
70 List<InterpolationPostProcessor> postProcessors = createPostProcessors(model, projectDir, config);
71
72 InterpolateObjectAction action =
73 new InterpolateObjectAction(obj, valueSources, postProcessors, debugEnabled, this, getLogger());
74
75 ModelInterpolationException error = AccessController.doPrivileged(action);
76
77 if (error != null) {
78 throw error;
79 }
80 } finally {
81 getInterpolator().clearAnswers();
82 }
83 }
84
85 protected Interpolator createInterpolator() {
86 StringSearchInterpolator interpolator = new StringSearchInterpolator();
87 interpolator.setCacheAnswers(true);
88
89 return interpolator;
90 }
91
92 private static final class InterpolateObjectAction implements PrivilegedAction<ModelInterpolationException> {
93
94 private final boolean debugEnabled;
95 private final LinkedList<Object> interpolationTargets;
96 private final StringSearchModelInterpolator modelInterpolator;
97 private final Logger logger;
98 private final List<ValueSource> valueSources;
99 private final List<InterpolationPostProcessor> postProcessors;
100
101 InterpolateObjectAction(
102 Object target,
103 List<ValueSource> valueSources,
104 List<InterpolationPostProcessor> postProcessors,
105 boolean debugEnabled,
106 StringSearchModelInterpolator modelInterpolator,
107 Logger logger) {
108 this.valueSources = valueSources;
109 this.postProcessors = postProcessors;
110 this.debugEnabled = debugEnabled;
111
112 this.interpolationTargets = new LinkedList<>();
113 interpolationTargets.add(target);
114
115 this.modelInterpolator = modelInterpolator;
116 this.logger = logger;
117 }
118
119 public ModelInterpolationException run() {
120 while (!interpolationTargets.isEmpty()) {
121 Object obj = interpolationTargets.removeFirst();
122
123 try {
124 traverseObjectWithParents(obj.getClass(), obj);
125 } catch (ModelInterpolationException e) {
126 return e;
127 }
128 }
129
130 return null;
131 }
132
133 @SuppressWarnings({"unchecked", "checkstyle:methodlength"})
134 private void traverseObjectWithParents(Class<?> cls, Object target) throws ModelInterpolationException {
135 if (cls == null) {
136 return;
137 }
138
139 if (cls.isArray()) {
140 evaluateArray(target);
141 } else if (isQualifiedForInterpolation(cls)) {
142 Field[] fields = FIELDS_BY_CLASS.computeIfAbsent(cls, k -> cls.getDeclaredFields());
143
144 for (Field field : fields) {
145 Class<?> type = field.getType();
146 if (isQualifiedForInterpolation(field, type)) {
147 boolean isAccessible = field.isAccessible();
148 field.setAccessible(true);
149 try {
150 try {
151 if (String.class == type) {
152 String value = (String) field.get(target);
153 if (value != null) {
154 String interpolated = modelInterpolator.interpolateInternal(
155 value, valueSources, postProcessors, debugEnabled);
156
157 if (!interpolated.equals(value)) {
158 field.set(target, interpolated);
159 }
160 }
161 } else if (Collection.class.isAssignableFrom(type)) {
162 Collection<Object> c = (Collection<Object>) field.get(target);
163 if (c != null && !c.isEmpty()) {
164 List<Object> originalValues = new ArrayList<>(c);
165 try {
166 c.clear();
167 } catch (UnsupportedOperationException e) {
168 if (debugEnabled && logger != null) {
169 logger.debug("Skipping interpolation of field: " + field + " in: "
170 + cls.getName()
171 + "; it is an unmodifiable collection.");
172 }
173 continue;
174 }
175
176 for (Object value : originalValues) {
177 if (value != null) {
178 if (String.class == value.getClass()) {
179 String interpolated = modelInterpolator.interpolateInternal(
180 (String) value, valueSources, postProcessors, debugEnabled);
181
182 if (!interpolated.equals(value)) {
183 c.add(interpolated);
184 } else {
185 c.add(value);
186 }
187 } else {
188 c.add(value);
189 if (value.getClass().isArray()) {
190 evaluateArray(value);
191 } else {
192 interpolationTargets.add(value);
193 }
194 }
195 } else {
196
197 c.add(value);
198 }
199 }
200 }
201 } else if (Map.class.isAssignableFrom(type)) {
202 Map<Object, Object> m = (Map<Object, Object>) field.get(target);
203 if (m != null && !m.isEmpty()) {
204 for (Map.Entry<Object, Object> entry : m.entrySet()) {
205 Object value = entry.getValue();
206
207 if (value != null) {
208 if (String.class == value.getClass()) {
209 String interpolated = modelInterpolator.interpolateInternal(
210 (String) value, valueSources, postProcessors, debugEnabled);
211
212 if (!interpolated.equals(value)) {
213 try {
214 entry.setValue(interpolated);
215 } catch (UnsupportedOperationException e) {
216 if (debugEnabled && logger != null) {
217 logger.debug("Skipping interpolation of field: " + field
218 + " (key: " + entry.getKey() + ") in: "
219 + cls.getName()
220 + "; it is an unmodifiable collection.");
221 }
222 }
223 }
224 } else {
225 if (value.getClass().isArray()) {
226 evaluateArray(value);
227 } else {
228 interpolationTargets.add(value);
229 }
230 }
231 }
232 }
233 }
234 } else {
235 Object value = field.get(target);
236 if (value != null) {
237 if (field.getType().isArray()) {
238 evaluateArray(value);
239 } else {
240 interpolationTargets.add(value);
241 }
242 }
243 }
244 } catch (IllegalArgumentException | IllegalAccessException e) {
245 throw new ModelInterpolationException(
246 "Failed to interpolate field: " + field + " on class: " + cls.getName(), e);
247 }
248 } finally {
249 field.setAccessible(isAccessible);
250 }
251 }
252 }
253
254 traverseObjectWithParents(cls.getSuperclass(), target);
255 }
256 }
257
258 private boolean isQualifiedForInterpolation(Class<?> cls) {
259 return !cls.getPackage().getName().startsWith("java")
260 && !cls.getPackage().getName().startsWith("sun.nio.fs");
261 }
262
263 private boolean isQualifiedForInterpolation(Field field, Class<?> fieldType) {
264 if (!PRIMITIVE_BY_CLASS.containsKey(fieldType)) {
265 PRIMITIVE_BY_CLASS.put(fieldType, fieldType.isPrimitive());
266 }
267
268 if (PRIMITIVE_BY_CLASS.get(fieldType)) {
269 return false;
270 }
271
272
273
274
275
276
277 return !"parent".equals(field.getName());
278 }
279
280 private void evaluateArray(Object target) throws ModelInterpolationException {
281 int len = Array.getLength(target);
282 for (int i = 0; i < len; i++) {
283 Object value = Array.get(target, i);
284 if (value != null) {
285 if (String.class == value.getClass()) {
286 String interpolated = modelInterpolator.interpolateInternal(
287 (String) value, valueSources, postProcessors, debugEnabled);
288
289 if (!interpolated.equals(value)) {
290 Array.set(target, i, interpolated);
291 }
292 } else {
293 interpolationTargets.add(value);
294 }
295 }
296 }
297 }
298 }
299 }