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