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