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