View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.model.interpolation;
20  
21  import java.io.File;
22  import java.lang.reflect.Array;
23  import java.lang.reflect.Field;
24  import java.lang.reflect.Modifier;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.HashMap;
28  import java.util.LinkedList;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.concurrent.ConcurrentHashMap;
32  
33  import org.apache.maven.model.InputLocation;
34  import org.apache.maven.model.Model;
35  import org.apache.maven.model.building.ModelBuildingRequest;
36  import org.apache.maven.model.building.ModelProblem.Severity;
37  import org.apache.maven.model.building.ModelProblem.Version;
38  import org.apache.maven.model.building.ModelProblemCollector;
39  import org.apache.maven.model.building.ModelProblemCollectorRequest;
40  import org.codehaus.plexus.interpolation.InterpolationException;
41  import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
42  import org.codehaus.plexus.interpolation.RecursionInterceptor;
43  import org.codehaus.plexus.interpolation.StringSearchInterpolator;
44  import org.codehaus.plexus.interpolation.ValueSource;
45  
46  /**
47   * StringSearchModelInterpolator
48   * @deprecated replaced by StringVisitorModelInterpolator (MNG-6697)
49   */
50  @Deprecated
51  public class StringSearchModelInterpolator extends AbstractStringBasedModelInterpolator {
52      private static final Map<Class<?>, InterpolateObjectAction.CacheItem> CACHED_ENTRIES =
53              new ConcurrentHashMap<>(80, 0.75f, 2);
54      // Empirical data from 3.x, actual =40
55  
56      private interface InnerInterpolator {
57          String interpolate(String value);
58      }
59  
60      @Override
61      public Model interpolateModel(
62              Model model, File projectDir, ModelBuildingRequest config, ModelProblemCollector problems) {
63          interpolateObject(model, model, projectDir, config, problems);
64          return model;
65      }
66  
67      void interpolateObject(
68              Object obj, Model model, File projectDir, ModelBuildingRequest config, ModelProblemCollector problems) {
69          List<? extends ValueSource> valueSources = createValueSources(model, projectDir, config, problems);
70          List<? extends InterpolationPostProcessor> postProcessors = createPostProcessors(model, projectDir, config);
71  
72          InnerInterpolator innerInterpolator = createInterpolator(valueSources, postProcessors, problems);
73  
74          new InterpolateObjectAction(obj, innerInterpolator, problems).run();
75      }
76  
77      private InnerInterpolator createInterpolator(
78              List<? extends ValueSource> valueSources,
79              List<? extends InterpolationPostProcessor> postProcessors,
80              final ModelProblemCollector problems) {
81          final Map<String, String> cache = new HashMap<>();
82          final StringSearchInterpolator interpolator = new StringSearchInterpolator();
83          interpolator.setCacheAnswers(true);
84          for (ValueSource vs : valueSources) {
85              interpolator.addValueSource(vs);
86          }
87          for (InterpolationPostProcessor postProcessor : postProcessors) {
88              interpolator.addPostProcessor(postProcessor);
89          }
90          final RecursionInterceptor recursionInterceptor = createRecursionInterceptor();
91          return new InnerInterpolator() {
92              @Override
93              public String interpolate(String value) {
94                  if (value != null && value.contains("${")) {
95                      String c = cache.get(value);
96                      if (c == null) {
97                          try {
98                              c = interpolator.interpolate(value, recursionInterceptor);
99                          } catch (InterpolationException e) {
100                             problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE)
101                                     .setMessage(e.getMessage())
102                                     .setException(e));
103                         }
104                         cache.put(value, c);
105                     }
106                     return c;
107                 }
108                 return value;
109             }
110         };
111     }
112 
113     private static final class InterpolateObjectAction {
114         private final LinkedList<Object> interpolationTargets;
115 
116         private final InnerInterpolator interpolator;
117 
118         private final ModelProblemCollector problems;
119 
120         InterpolateObjectAction(Object target, InnerInterpolator interpolator, ModelProblemCollector problems) {
121             this.interpolationTargets = new LinkedList<>();
122             interpolationTargets.add(target);
123             this.interpolator = interpolator;
124             this.problems = problems;
125         }
126 
127         public Object run() {
128             while (!interpolationTargets.isEmpty()) {
129                 Object obj = interpolationTargets.removeFirst();
130 
131                 traverseObjectWithParents(obj.getClass(), obj);
132             }
133             return null;
134         }
135 
136         private String interpolate(String value) {
137             return interpolator.interpolate(value);
138         }
139 
140         private void traverseObjectWithParents(Class<?> cls, Object target) {
141             if (cls == null) {
142                 return;
143             }
144 
145             CacheItem cacheEntry = getCacheEntry(cls);
146             if (cacheEntry.isArray()) {
147                 evaluateArray(target, this);
148             } else if (cacheEntry.isQualifiedForInterpolation) {
149                 cacheEntry.interpolate(target, this);
150 
151                 traverseObjectWithParents(cls.getSuperclass(), target);
152             }
153         }
154 
155         private CacheItem getCacheEntry(Class<?> cls) {
156             CacheItem cacheItem = CACHED_ENTRIES.get(cls);
157             if (cacheItem == null) {
158                 cacheItem = new CacheItem(cls);
159                 CACHED_ENTRIES.put(cls, cacheItem);
160             }
161             return cacheItem;
162         }
163 
164         private static void evaluateArray(Object target, InterpolateObjectAction ctx) {
165             int len = Array.getLength(target);
166             for (int i = 0; i < len; i++) {
167                 Object value = Array.get(target, i);
168                 if (value != null) {
169                     if (String.class == value.getClass()) {
170                         String interpolated = ctx.interpolate((String) value);
171 
172                         if (!interpolated.equals(value)) {
173                             Array.set(target, i, interpolated);
174                         }
175                     } else {
176                         ctx.interpolationTargets.add(value);
177                     }
178                 }
179             }
180         }
181 
182         private static class CacheItem {
183             private final boolean isArray;
184 
185             private final boolean isQualifiedForInterpolation;
186 
187             private final CacheField[] fields;
188 
189             private boolean isQualifiedForInterpolation(Class<?> cls) {
190                 Package pkg = cls.getPackage();
191                 if (pkg == null) {
192                     return true;
193                 }
194                 String pkgName = pkg.getName();
195                 return !pkgName.startsWith("java.") && !pkgName.startsWith("javax.");
196             }
197 
198             private boolean isQualifiedForInterpolation(Field field, Class<?> fieldType) {
199                 if (Map.class.equals(fieldType) && "locations".equals(field.getName())) {
200                     return false;
201                 }
202                 if (InputLocation.class.equals(fieldType)) {
203                     return false;
204                 }
205 
206                 //noinspection SimplifiableIfStatement
207                 if (fieldType.isPrimitive()) {
208                     return false;
209                 }
210 
211                 return !"parent".equals(field.getName());
212             }
213 
214             CacheItem(Class clazz) {
215                 this.isQualifiedForInterpolation = isQualifiedForInterpolation(clazz);
216                 this.isArray = clazz.isArray();
217                 List<CacheField> fields = new ArrayList<>();
218                 if (isQualifiedForInterpolation) {
219                     for (Field currentField : clazz.getDeclaredFields()) {
220                         Class<?> type = currentField.getType();
221                         if (isQualifiedForInterpolation(currentField, type)) {
222                             if (String.class == type) {
223                                 if (!Modifier.isFinal(currentField.getModifiers())) {
224                                     fields.add(new StringField(currentField));
225                                 }
226                             } else if (List.class.isAssignableFrom(type)) {
227                                 fields.add(new ListField(currentField));
228                             } else if (Collection.class.isAssignableFrom(type)) {
229                                 throw new RuntimeException("We dont interpolate into collections, use a list instead");
230                             } else if (Map.class.isAssignableFrom(type)) {
231                                 fields.add(new MapField(currentField));
232                             } else {
233                                 fields.add(new ObjectField(currentField));
234                             }
235                         }
236                     }
237                 }
238                 this.fields = fields.toArray(new CacheField[0]);
239             }
240 
241             void interpolate(Object target, InterpolateObjectAction interpolateObjectAction) {
242                 for (CacheField field : fields) {
243                     field.interpolate(target, interpolateObjectAction);
244                 }
245             }
246 
247             boolean isArray() {
248                 return isArray;
249             }
250         }
251 
252         abstract static class CacheField {
253             final Field field;
254 
255             CacheField(Field field) {
256                 this.field = field;
257                 field.setAccessible(true);
258             }
259 
260             void interpolate(Object target, InterpolateObjectAction interpolateObjectAction) {
261                 try {
262                     doInterpolate(target, interpolateObjectAction);
263                 } catch (IllegalArgumentException e) {
264                     interpolateObjectAction.problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE)
265                             .setMessage("Failed to interpolate field3: " + field + " on class: "
266                                     + field.getType().getName())
267                             .setException(e)); // TODO Not entirely the same message
268                 } catch (IllegalAccessException e) {
269                     interpolateObjectAction.problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE)
270                             .setMessage("Failed to interpolate field4: " + field + " on class: "
271                                     + field.getType().getName())
272                             .setException(e));
273                 }
274             }
275 
276             abstract void doInterpolate(Object target, InterpolateObjectAction ctx) throws IllegalAccessException;
277         }
278 
279         static final class StringField extends CacheField {
280             StringField(Field field) {
281                 super(field);
282             }
283 
284             @Override
285             void doInterpolate(Object target, InterpolateObjectAction ctx) throws IllegalAccessException {
286                 String value = (String) field.get(target);
287                 if (value == null) {
288                     return;
289                 }
290 
291                 String interpolated = ctx.interpolate(value);
292 
293                 if (interpolated != null && !interpolated.equals(value)) {
294                     field.set(target, interpolated);
295                 }
296             }
297         }
298 
299         static final class ListField extends CacheField {
300             ListField(Field field) {
301                 super(field);
302             }
303 
304             @Override
305             void doInterpolate(Object target, InterpolateObjectAction ctx) throws IllegalAccessException {
306                 @SuppressWarnings("unchecked")
307                 List<Object> c = (List<Object>) field.get(target);
308                 if (c == null) {
309                     return;
310                 }
311 
312                 for (int i = 0, size = c.size(); i < size; i++) {
313                     Object value = c.get(i);
314 
315                     if (value != null) {
316                         if (String.class == value.getClass()) {
317                             String interpolated = ctx.interpolate((String) value);
318 
319                             if (!interpolated.equals(value)) {
320                                 try {
321                                     c.set(i, interpolated);
322                                 } catch (UnsupportedOperationException e) {
323                                     return;
324                                 }
325                             }
326                         } else {
327                             if (value.getClass().isArray()) {
328                                 evaluateArray(value, ctx);
329                             } else {
330                                 ctx.interpolationTargets.add(value);
331                             }
332                         }
333                     }
334                 }
335             }
336         }
337 
338         static final class MapField extends CacheField {
339             MapField(Field field) {
340                 super(field);
341             }
342 
343             @Override
344             void doInterpolate(Object target, InterpolateObjectAction ctx) throws IllegalAccessException {
345                 @SuppressWarnings("unchecked")
346                 Map<Object, Object> m = (Map<Object, Object>) field.get(target);
347                 if (m == null || m.isEmpty()) {
348                     return;
349                 }
350 
351                 for (Map.Entry<Object, Object> entry : m.entrySet()) {
352                     Object value = entry.getValue();
353 
354                     if (value == null) {
355                         continue;
356                     }
357 
358                     if (String.class == value.getClass()) {
359                         String interpolated = ctx.interpolate((String) value);
360 
361                         if (interpolated != null && !interpolated.equals(value)) {
362                             try {
363                                 entry.setValue(interpolated);
364                             } catch (UnsupportedOperationException ignore) {
365                                 // nop
366                             }
367                         }
368                     } else if (value.getClass().isArray()) {
369                         evaluateArray(value, ctx);
370                     } else {
371                         ctx.interpolationTargets.add(value);
372                     }
373                 }
374             }
375         }
376 
377         static final class ObjectField extends CacheField {
378             private final boolean isArray;
379 
380             ObjectField(Field field) {
381                 super(field);
382                 this.isArray = field.getType().isArray();
383             }
384 
385             @Override
386             void doInterpolate(Object target, InterpolateObjectAction ctx) throws IllegalAccessException {
387                 Object value = field.get(target);
388                 if (value != null) {
389                     if (isArray) {
390                         evaluateArray(value, ctx);
391                     } else {
392                         ctx.interpolationTargets.add(value);
393                     }
394                 }
395             }
396         }
397     }
398 }