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