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.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                                                 // add the null back in...not sure what else to do...
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             //            if ( fieldType.isPrimitive() )
274             //            {
275             //                return false;
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 }