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.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   * StringSearchModelInterpolator
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.get(cls);
144                 if (fields == null) {
145                     fields = cls.getDeclaredFields();
146                     FIELDS_BY_CLASS.put(cls, fields);
147                 }
148 
149                 for (Field field : fields) {
150                     Class<?> type = field.getType();
151                     if (isQualifiedForInterpolation(field, type)) {
152                         boolean isAccessible = field.isAccessible();
153                         field.setAccessible(true);
154                         try {
155                             try {
156                                 if (String.class == type) {
157                                     String value = (String) field.get(target);
158                                     if (value != null) {
159                                         String interpolated = modelInterpolator.interpolateInternal(
160                                                 value, valueSources, postProcessors, debugEnabled);
161 
162                                         if (!interpolated.equals(value)) {
163                                             field.set(target, interpolated);
164                                         }
165                                     }
166                                 } else if (Collection.class.isAssignableFrom(type)) {
167                                     Collection<Object> c = (Collection<Object>) field.get(target);
168                                     if (c != null && !c.isEmpty()) {
169                                         List<Object> originalValues = new ArrayList<>(c);
170                                         try {
171                                             c.clear();
172                                         } catch (UnsupportedOperationException e) {
173                                             if (debugEnabled && logger != null) {
174                                                 logger.debug("Skipping interpolation of field: " + field + " in: "
175                                                         + cls.getName()
176                                                         + "; it is an unmodifiable collection.");
177                                             }
178                                             continue;
179                                         }
180 
181                                         for (Object value : originalValues) {
182                                             if (value != null) {
183                                                 if (String.class == value.getClass()) {
184                                                     String interpolated = modelInterpolator.interpolateInternal(
185                                                             (String) value, valueSources, postProcessors, debugEnabled);
186 
187                                                     if (!interpolated.equals(value)) {
188                                                         c.add(interpolated);
189                                                     } else {
190                                                         c.add(value);
191                                                     }
192                                                 } else {
193                                                     c.add(value);
194                                                     if (value.getClass().isArray()) {
195                                                         evaluateArray(value);
196                                                     } else {
197                                                         interpolationTargets.add(value);
198                                                     }
199                                                 }
200                                             } else {
201                                                 // add the null back in...not sure what else to do...
202                                                 c.add(value);
203                                             }
204                                         }
205                                     }
206                                 } else if (Map.class.isAssignableFrom(type)) {
207                                     Map<Object, Object> m = (Map<Object, Object>) field.get(target);
208                                     if (m != null && !m.isEmpty()) {
209                                         for (Map.Entry<Object, Object> entry : m.entrySet()) {
210                                             Object value = entry.getValue();
211 
212                                             if (value != null) {
213                                                 if (String.class == value.getClass()) {
214                                                     String interpolated = modelInterpolator.interpolateInternal(
215                                                             (String) value, valueSources, postProcessors, debugEnabled);
216 
217                                                     if (!interpolated.equals(value)) {
218                                                         try {
219                                                             entry.setValue(interpolated);
220                                                         } catch (UnsupportedOperationException e) {
221                                                             if (debugEnabled && logger != null) {
222                                                                 logger.debug("Skipping interpolation of field: " + field
223                                                                         + " (key: " + entry.getKey() + ") in: "
224                                                                         + cls.getName()
225                                                                         + "; it is an unmodifiable collection.");
226                                                             }
227                                                         }
228                                                     }
229                                                 } else {
230                                                     if (value.getClass().isArray()) {
231                                                         evaluateArray(value);
232                                                     } else {
233                                                         interpolationTargets.add(value);
234                                                     }
235                                                 }
236                                             }
237                                         }
238                                     }
239                                 } else {
240                                     Object value = field.get(target);
241                                     if (value != null) {
242                                         if (field.getType().isArray()) {
243                                             evaluateArray(value);
244                                         } else {
245                                             interpolationTargets.add(value);
246                                         }
247                                     }
248                                 }
249                             } catch (IllegalArgumentException | IllegalAccessException e) {
250                                 throw new ModelInterpolationException(
251                                         "Failed to interpolate field: " + field + " on class: " + cls.getName(), e);
252                             }
253                         } finally {
254                             field.setAccessible(isAccessible);
255                         }
256                     }
257                 }
258 
259                 traverseObjectWithParents(cls.getSuperclass(), target);
260             }
261         }
262 
263         private boolean isQualifiedForInterpolation(Class<?> cls) {
264             return !cls.getPackage().getName().startsWith("java");
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             //            if ( fieldType.isPrimitive() )
277             //            {
278             //                return false;
279             //            }
280 
281             if ("parent".equals(field.getName())) {
282                 return false;
283             }
284 
285             return true;
286         }
287 
288         private void evaluateArray(Object target) throws ModelInterpolationException {
289             int len = Array.getLength(target);
290             for (int i = 0; i < len; i++) {
291                 Object value = Array.get(target, i);
292                 if (value != null) {
293                     if (String.class == value.getClass()) {
294                         String interpolated = modelInterpolator.interpolateInternal(
295                                 (String) value, valueSources, postProcessors, debugEnabled);
296 
297                         if (!interpolated.equals(value)) {
298                             Array.set(target, i, interpolated);
299                         }
300                     } else {
301                         interpolationTargets.add(value);
302                     }
303                 }
304             }
305         }
306     }
307 }