1 package org.apache.maven.model.interpolation;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.model.Model;
23 import org.apache.maven.model.building.ModelBuildingRequest;
24 import org.apache.maven.model.building.ModelProblem.Severity;
25 import org.apache.maven.model.building.ModelProblem.Version;
26 import org.apache.maven.model.building.ModelProblemCollector;
27 import org.apache.maven.model.building.ModelProblemCollectorRequest;
28 import org.codehaus.plexus.component.annotations.Component;
29 import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
30 import org.codehaus.plexus.interpolation.Interpolator;
31 import org.codehaus.plexus.interpolation.StringSearchInterpolator;
32 import org.codehaus.plexus.interpolation.ValueSource;
33
34 import java.io.File;
35 import java.lang.reflect.Array;
36 import java.lang.reflect.Field;
37 import java.lang.reflect.Modifier;
38 import java.security.AccessController;
39 import java.security.PrivilegedAction;
40 import java.util.ArrayList;
41 import java.util.Collection;
42 import java.util.LinkedList;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.concurrent.ConcurrentHashMap;
46
47 @Component( role = ModelInterpolator.class )
48 public class StringSearchModelInterpolator
49 extends AbstractStringBasedModelInterpolator
50 {
51
52 private static final Map<Class<?>, InterpolateObjectAction.CacheItem> CACHED_ENTRIES =
53 new ConcurrentHashMap<Class<?>, InterpolateObjectAction.CacheItem>( 80, 0.75f, 2 );
54
55
56
57 public Model interpolateModel( Model model, File projectDir, ModelBuildingRequest config,
58 ModelProblemCollector problems )
59 {
60 interpolateObject( model, model, projectDir, config, problems );
61
62 return model;
63 }
64
65 protected void interpolateObject( Object obj, Model model, File projectDir, ModelBuildingRequest config,
66 ModelProblemCollector problems )
67 {
68 try
69 {
70 List<? extends ValueSource> valueSources = createValueSources( model, projectDir, config, problems );
71 List<? extends InterpolationPostProcessor> postProcessors =
72 createPostProcessors( model, projectDir, config );
73
74 InterpolateObjectAction action =
75 new InterpolateObjectAction( obj, valueSources, postProcessors, this, problems );
76
77 AccessController.doPrivileged( action );
78 }
79 finally
80 {
81 getInterpolator().clearAnswers();
82 }
83 }
84
85 protected Interpolator createInterpolator()
86 {
87 StringSearchInterpolator interpolator = new StringSearchInterpolator();
88 interpolator.setCacheAnswers( true );
89
90 return interpolator;
91 }
92
93 private static final class InterpolateObjectAction
94 implements PrivilegedAction<Object>
95 {
96
97 private final LinkedList<Object> interpolationTargets;
98
99 private final StringSearchModelInterpolator modelInterpolator;
100
101 private final List<? extends ValueSource> valueSources;
102
103 private final List<? extends InterpolationPostProcessor> postProcessors;
104
105 private final ModelProblemCollector problems;
106
107 public InterpolateObjectAction( Object target, List<? extends ValueSource> valueSources,
108 List<? extends InterpolationPostProcessor> postProcessors,
109 StringSearchModelInterpolator modelInterpolator,
110 ModelProblemCollector problems )
111 {
112 this.valueSources = valueSources;
113 this.postProcessors = postProcessors;
114
115 this.interpolationTargets = new LinkedList<Object>();
116 interpolationTargets.add( target );
117
118 this.modelInterpolator = modelInterpolator;
119
120 this.problems = problems;
121 }
122
123 public Object run()
124 {
125 while ( !interpolationTargets.isEmpty() )
126 {
127 Object obj = interpolationTargets.removeFirst();
128
129 traverseObjectWithParents( obj.getClass(), obj );
130 }
131
132 return null;
133 }
134
135
136 private String interpolate( String value )
137 {
138 return modelInterpolator.interpolateInternal( value, valueSources, postProcessors, problems );
139 }
140
141 private void traverseObjectWithParents( Class<?> cls, Object target )
142 {
143 if ( cls == null )
144 {
145 return;
146 }
147
148 CacheItem cacheEntry = getCacheEntry( cls );
149 if ( cacheEntry.isArray() )
150 {
151 evaluateArray( target, this );
152 }
153 else if ( cacheEntry.isQualifiedForInterpolation )
154 {
155 cacheEntry.interpolate( target, this );
156
157 traverseObjectWithParents( cls.getSuperclass(), target );
158 }
159 }
160
161
162 private CacheItem getCacheEntry( Class<?> cls )
163 {
164 CacheItem cacheItem = CACHED_ENTRIES.get( cls );
165 if ( cacheItem == null )
166 {
167 cacheItem = new CacheItem( cls );
168 CACHED_ENTRIES.put( cls, cacheItem );
169 }
170 return cacheItem;
171 }
172
173 private static void evaluateArray( Object target, InterpolateObjectAction ctx )
174 {
175 int len = Array.getLength( target );
176 for ( int i = 0; i < len; i++ )
177 {
178 Object value = Array.get( target, i );
179 if ( value != null )
180 {
181 if ( String.class == value.getClass() )
182 {
183 String interpolated = ctx.interpolate( (String) value );
184
185 if ( !interpolated.equals( value ) )
186 {
187 Array.set( target, i, interpolated );
188 }
189 }
190 else
191 {
192 ctx.interpolationTargets.add( value );
193 }
194 }
195 }
196 }
197
198 private static class CacheItem
199 {
200 private final boolean isArray;
201
202 private final boolean isQualifiedForInterpolation;
203
204 private final CacheField[] fields;
205
206 private boolean isQualifiedForInterpolation( Class<?> cls )
207 {
208 return !cls.getName().startsWith( "java" );
209 }
210
211 private boolean isQualifiedForInterpolation( Field field, Class<?> fieldType )
212 {
213 if ( Map.class.equals( fieldType ) && "locations".equals( field.getName() ) )
214 {
215 return false;
216 }
217
218
219 if ( fieldType.isPrimitive() )
220 {
221 return false;
222 }
223
224 return !"parent".equals( field.getName() );
225 }
226
227 CacheItem( Class clazz )
228 {
229 this.isQualifiedForInterpolation = isQualifiedForInterpolation( clazz );
230 this.isArray = clazz.isArray();
231 List<CacheField> fields = new ArrayList<CacheField>();
232 for ( Field currentField : clazz.getDeclaredFields() )
233 {
234 Class<?> type = currentField.getType();
235 if ( isQualifiedForInterpolation( currentField, type ) )
236 {
237 if ( String.class == type )
238 {
239 if ( !Modifier.isFinal( currentField.getModifiers() ) )
240 {
241 fields.add( new StringField( currentField ) );
242 }
243 }
244 else if ( List.class.isAssignableFrom( type ) )
245 {
246 fields.add( new ListField( currentField ) );
247 }
248 else if ( Collection.class.isAssignableFrom( type ) )
249 {
250 throw new RuntimeException( "We dont interpolate into collections, use a list instead" );
251 }
252 else if ( Map.class.isAssignableFrom( type ) )
253 {
254 fields.add( new MapField( currentField ) );
255 }
256 else
257 {
258 fields.add( new ObjectField( currentField ) );
259 }
260 }
261
262 }
263 this.fields = fields.toArray( new CacheField[fields.size()] );
264
265 }
266
267 public void interpolate( Object target, InterpolateObjectAction interpolateObjectAction )
268 {
269 for ( CacheField field : fields )
270 {
271 field.interpolate( target, interpolateObjectAction );
272 }
273 }
274
275 public boolean isArray()
276 {
277 return isArray;
278 }
279 }
280
281 abstract static class CacheField
282 {
283 protected final Field field;
284
285 CacheField( Field field )
286 {
287 this.field = field;
288 }
289
290 void interpolate( Object target, InterpolateObjectAction interpolateObjectAction )
291 {
292 synchronized ( field )
293 {
294 boolean isAccessible = field.isAccessible();
295 field.setAccessible( true );
296 try
297 {
298 doInterpolate( target, interpolateObjectAction );
299 }
300 catch ( IllegalArgumentException e )
301 {
302 interpolateObjectAction.problems.add(
303 new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ).setMessage(
304 "Failed to interpolate field3: " + field + " on class: "
305 + field.getType().getName() ).setException(
306 e ) );
307 }
308 catch ( IllegalAccessException e )
309 {
310 interpolateObjectAction.problems.add(
311 new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ).setMessage(
312 "Failed to interpolate field4: " + field + " on class: "
313 + field.getType().getName() ).setException( e ) );
314 }
315 finally
316 {
317 field.setAccessible( isAccessible );
318 }
319 }
320
321
322 }
323
324 abstract void doInterpolate( Object target, InterpolateObjectAction ctx )
325 throws IllegalAccessException;
326 }
327
328 static final class StringField
329 extends CacheField
330 {
331 StringField( Field field )
332 {
333 super( field );
334 }
335
336 @Override
337 void doInterpolate( Object target, InterpolateObjectAction ctx )
338 throws IllegalAccessException
339 {
340 String value = (String) field.get( target );
341 if ( value == null )
342 {
343 return;
344 }
345
346 String interpolated = ctx.interpolate( value );
347
348 if ( !interpolated.equals( value ) )
349 {
350 field.set( target, interpolated );
351 }
352 }
353 }
354
355 static final class ListField
356 extends CacheField
357 {
358 ListField( Field field )
359 {
360 super( field );
361 }
362
363 @Override
364 void doInterpolate( Object target, InterpolateObjectAction ctx )
365 throws IllegalAccessException
366 {
367 @SuppressWarnings( "unchecked" ) List<Object> c = (List<Object>) field.get( target );
368 if ( c == null )
369 {
370 return;
371 }
372
373 int size = c.size();
374 Object value;
375 for ( int i = 0; i < size; i++ )
376 {
377
378 value = c.get( i );
379
380 if ( value != null )
381 {
382 if ( String.class == value.getClass() )
383 {
384 String interpolated = ctx.interpolate( (String) value );
385
386 if ( !interpolated.equals( value ) )
387 {
388 try
389 {
390 c.set( i, interpolated );
391 }
392 catch ( UnsupportedOperationException e )
393 {
394 return;
395 }
396 }
397 }
398 else
399 {
400 if ( value.getClass().isArray() )
401 {
402 evaluateArray( value, ctx );
403 }
404 else
405 {
406 ctx.interpolationTargets.add( value );
407 }
408 }
409 }
410 }
411 }
412 }
413
414 static final class MapField
415 extends CacheField
416 {
417 MapField( Field field )
418 {
419 super( field );
420 }
421
422 @Override
423 void doInterpolate( Object target, InterpolateObjectAction ctx )
424 throws IllegalAccessException
425 {
426 @SuppressWarnings( "unchecked" ) Map<Object, Object> m = (Map<Object, Object>) field.get( target );
427 if ( m == null || m.isEmpty() )
428 {
429 return;
430 }
431
432 for ( Map.Entry<Object, Object> entry : m.entrySet() )
433 {
434 Object value = entry.getValue();
435
436 if ( value == null )
437 {
438 continue;
439 }
440
441 if ( String.class == value.getClass() )
442 {
443 String interpolated = ctx.interpolate( (String) value );
444
445 if ( !interpolated.equals( value ) )
446 {
447 try
448 {
449 entry.setValue( interpolated );
450 }
451 catch ( UnsupportedOperationException ignore )
452 {
453 }
454 }
455 }
456 else if ( value.getClass().isArray() )
457 {
458 evaluateArray( value, ctx );
459 }
460 else
461 {
462 ctx.interpolationTargets.add( value );
463 }
464 }
465 }
466 }
467
468 static final class ObjectField
469 extends CacheField
470 {
471 private final boolean isArray;
472
473 ObjectField( Field field )
474 {
475 super( field );
476 this.isArray = field.getType().isArray();
477 }
478
479 @Override
480 void doInterpolate( Object target, InterpolateObjectAction ctx )
481 throws IllegalAccessException
482 {
483 Object value = field.get( target );
484 if ( value != null )
485 {
486 if ( isArray )
487 {
488 evaluateArray( value, ctx );
489 }
490 else
491 {
492 ctx.interpolationTargets.add( value );
493 }
494 }
495 }
496 }
497
498 }
499
500 }