1 package org.apache.maven.project.interpolation;
2
3 import org.apache.maven.model.Model;
4 import org.apache.maven.project.ProjectBuilderConfiguration;
5 import org.apache.maven.project.path.PathTranslator;
6 import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
7 import org.codehaus.plexus.interpolation.Interpolator;
8 import org.codehaus.plexus.interpolation.StringSearchInterpolator;
9 import org.codehaus.plexus.interpolation.ValueSource;
10 import org.codehaus.plexus.logging.Logger;
11
12 import java.io.File;
13 import java.lang.reflect.Array;
14 import java.lang.reflect.Field;
15 import java.security.AccessController;
16 import java.security.PrivilegedAction;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.LinkedList;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.WeakHashMap;
23
24 public class StringSearchModelInterpolator
25 extends AbstractStringBasedModelInterpolator
26 {
27
28 private static final Map<Class<?>, Field[]> fieldsByClass = new WeakHashMap<Class<?>, Field[]>();
29 private static final Map<Class<?>, Boolean> fieldIsPrimitiveByClass = new WeakHashMap<Class<?>, Boolean>();
30
31 public StringSearchModelInterpolator()
32 {
33 }
34
35 public StringSearchModelInterpolator( PathTranslator pathTranslator )
36 {
37 super( pathTranslator );
38 }
39
40 public Model interpolate( Model model, File projectDir, ProjectBuilderConfiguration config, boolean debugEnabled )
41 throws ModelInterpolationException
42 {
43 interpolateObject( model, model, projectDir, config, debugEnabled );
44
45 return model;
46 }
47
48 protected void interpolateObject( Object obj, Model model, File projectDir, ProjectBuilderConfiguration config,
49 boolean debugEnabled )
50 throws ModelInterpolationException
51 {
52 try
53 {
54 List<ValueSource> valueSources = createValueSources( model, projectDir, config );
55 List<InterpolationPostProcessor> postProcessors = createPostProcessors( model, projectDir, config );
56
57 InterpolateObjectAction action =
58 new InterpolateObjectAction( obj, valueSources, postProcessors, debugEnabled,
59 this, getLogger() );
60
61 ModelInterpolationException error =
62 (ModelInterpolationException) AccessController.doPrivileged( action );
63
64 if ( error != null )
65 {
66 throw error;
67 }
68 }
69 finally
70 {
71 getInterpolator().clearAnswers();
72 }
73 }
74
75 protected Interpolator createInterpolator()
76 {
77 StringSearchInterpolator interpolator = new StringSearchInterpolator();
78 interpolator.setCacheAnswers( true );
79
80 return interpolator;
81 }
82
83 private static final class InterpolateObjectAction implements PrivilegedAction<ModelInterpolationException>
84 {
85
86 private final boolean debugEnabled;
87 private final LinkedList<Object> interpolationTargets;
88 private final StringSearchModelInterpolator modelInterpolator;
89 private final Logger logger;
90 private final List<ValueSource> valueSources;
91 private final List<InterpolationPostProcessor> postProcessors;
92
93 public InterpolateObjectAction( Object target, List<ValueSource> valueSources,
94 List<InterpolationPostProcessor> postProcessors, boolean debugEnabled,
95 StringSearchModelInterpolator modelInterpolator, Logger logger )
96 {
97 this.valueSources = valueSources;
98 this.postProcessors = postProcessors;
99 this.debugEnabled = debugEnabled;
100
101 this.interpolationTargets = new LinkedList<Object>();
102 interpolationTargets.add( target );
103
104 this.modelInterpolator = modelInterpolator;
105 this.logger = logger;
106 }
107
108 public ModelInterpolationException run()
109 {
110 while( !interpolationTargets.isEmpty() )
111 {
112 Object obj = interpolationTargets.removeFirst();
113
114 try
115 {
116 traverseObjectWithParents( obj.getClass(), obj );
117 }
118 catch ( ModelInterpolationException e )
119 {
120 return e;
121 }
122 }
123
124 return null;
125 }
126
127 @SuppressWarnings("unchecked")
128 private void traverseObjectWithParents( Class<?> cls, Object target )
129 throws ModelInterpolationException
130 {
131 if ( cls == null )
132 {
133 return;
134 }
135
136
137 if ( cls.isArray() )
138 {
139 evaluateArray( target );
140 }
141 else if ( isQualifiedForInterpolation( cls ) )
142 {
143 Field[] fields = (Field[]) fieldsByClass.get( cls );
144 if ( fields == null )
145 {
146 fields = cls.getDeclaredFields();
147 fieldsByClass.put( cls, fields );
148 }
149
150 for ( int i = 0; i < fields.length; i++ )
151 {
152 Class<?> type = fields[i].getType();
153 if ( isQualifiedForInterpolation( fields[i], type ) )
154 {
155 boolean isAccessible = fields[i].isAccessible();
156 fields[i].setAccessible( true );
157 try
158 {
159 try
160 {
161 if ( String.class == type )
162 {
163 String value = (String) fields[i].get( target );
164 if ( value != null )
165 {
166 String interpolated = modelInterpolator.interpolateInternal( value, valueSources, postProcessors, debugEnabled );
167
168 if ( !interpolated.equals( value ) )
169 {
170 fields[i].set( target, interpolated );
171 }
172 }
173 }
174 else if ( Collection.class.isAssignableFrom( type ) )
175 {
176 Collection<Object> c = (Collection<Object>) fields[i].get( target );
177 if ( c != null && !c.isEmpty() )
178 {
179 List<Object> originalValues = new ArrayList<Object>( c );
180 try
181 {
182 c.clear();
183 }
184 catch( UnsupportedOperationException e )
185 {
186 if ( debugEnabled && logger != null )
187 {
188 logger.debug( "Skipping interpolation of field: " + fields[i] + " in: " + cls.getName() + "; it is an unmodifiable collection." );
189 }
190 continue;
191 }
192
193 for ( Object value : originalValues )
194 {
195 if ( value != null )
196 {
197 if( String.class == value.getClass() )
198 {
199 String interpolated = modelInterpolator.interpolateInternal( (String) value, valueSources, postProcessors, debugEnabled );
200
201 if ( !interpolated.equals( value ) )
202 {
203 c.add( interpolated );
204 }
205 else
206 {
207 c.add( value );
208 }
209 }
210 else
211 {
212 c.add( value );
213 if ( value.getClass().isArray() )
214 {
215 evaluateArray( value );
216 }
217 else
218 {
219 interpolationTargets.add( value );
220 }
221 }
222 }
223 else
224 {
225
226 c.add( value );
227 }
228 }
229 }
230 }
231 else if ( Map.class.isAssignableFrom( type ) )
232 {
233 Map<Object, Object> m = (Map<Object, Object>) fields[i].get( target );
234 if ( m != null && !m.isEmpty() )
235 {
236 for ( Map.Entry<Object, Object> entry : m.entrySet() )
237 {
238 Object value = entry.getValue();
239
240 if ( value != null )
241 {
242 if( String.class == value.getClass() )
243 {
244 String interpolated = modelInterpolator.interpolateInternal( (String) value, valueSources, postProcessors, debugEnabled );
245
246 if ( !interpolated.equals( value ) )
247 {
248 try
249 {
250 entry.setValue( interpolated );
251 }
252 catch( UnsupportedOperationException e )
253 {
254 if ( debugEnabled && logger != null )
255 {
256 logger.debug( "Skipping interpolation of field: " + fields[i] + " (key: " + entry.getKey() + ") in: " + cls.getName() + "; it is an unmodifiable collection." );
257 }
258 continue;
259 }
260 }
261 }
262 else
263 {
264 if ( value.getClass().isArray() )
265 {
266 evaluateArray( value );
267 }
268 else
269 {
270 interpolationTargets.add( value );
271 }
272 }
273 }
274 }
275 }
276 }
277 else
278 {
279 Object value = fields[i].get( target );
280 if ( value != null )
281 {
282 if ( fields[i].getType().isArray() )
283 {
284 evaluateArray( value );
285 }
286 else
287 {
288 interpolationTargets.add( value );
289 }
290 }
291 }
292 }
293 catch ( IllegalArgumentException e )
294 {
295 throw new ModelInterpolationException( "Failed to interpolate field: " + fields[i] + " on class: " + cls.getName(), e );
296 }
297 catch ( IllegalAccessException e )
298 {
299 throw new ModelInterpolationException( "Failed to interpolate field: " + fields[i] + " on class: " + cls.getName(), e );
300 }
301 }
302 finally
303 {
304 fields[i].setAccessible( isAccessible );
305 }
306 }
307 }
308
309 traverseObjectWithParents( cls.getSuperclass(), target );
310 }
311 }
312
313 private boolean isQualifiedForInterpolation( Class<?> cls )
314 {
315 return !cls.getPackage().getName().startsWith( "java" );
316 }
317
318 private boolean isQualifiedForInterpolation( Field field, Class<?> fieldType )
319 {
320 if ( !fieldIsPrimitiveByClass.containsKey( fieldType ) )
321 {
322 fieldIsPrimitiveByClass.put( fieldType, Boolean.valueOf( fieldType.isPrimitive() ) );
323 }
324
325 if ( ((Boolean) fieldIsPrimitiveByClass.get( fieldType )).booleanValue() )
326 {
327 return false;
328 }
329
330
331
332
333
334
335 if ( "parent".equals( field.getName() ) )
336 {
337 return false;
338 }
339
340 return true;
341 }
342
343 private void evaluateArray( Object target )
344 throws ModelInterpolationException
345 {
346 int len = Array.getLength( target );
347 for( int i = 0; i < len; i++ )
348 {
349 Object value = Array.get( target, i );
350 if ( value != null )
351 {
352 if ( String.class == value.getClass() )
353 {
354 String interpolated = modelInterpolator.interpolateInternal( (String) value, valueSources, postProcessors, debugEnabled );
355
356 if ( !interpolated.equals( value ) )
357 {
358 Array.set( target, i, interpolated );
359 }
360 }
361 else
362 {
363 interpolationTargets.add( value );
364 }
365 }
366 }
367 }
368 }
369
370 }