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