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 }
268 this.fields = fields.toArray( new CacheField[fields.size()] );
269
270 }
271
272 public void interpolate( Object target, InterpolateObjectAction interpolateObjectAction )
273 {
274 for ( CacheField field : fields )
275 {
276 field.interpolate( target, interpolateObjectAction );
277 }
278 }
279
280 public boolean isArray()
281 {
282 return isArray;
283 }
284 }
285
286 abstract static class CacheField
287 {
288 protected final Field field;
289
290 CacheField( Field field )
291 {
292 this.field = field;
293 }
294
295 void interpolate( Object target, InterpolateObjectAction interpolateObjectAction )
296 {
297 synchronized ( field )
298 {
299 boolean isAccessible = field.isAccessible();
300 field.setAccessible( true );
301 try
302 {
303 doInterpolate( target, interpolateObjectAction );
304 }
305 catch ( IllegalArgumentException e )
306 {
307 interpolateObjectAction.problems.add(
308 new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ).setMessage(
309 "Failed to interpolate field3: " + field + " on class: "
310 + field.getType().getName() ).setException(
311 e ) );
312 }
313 catch ( IllegalAccessException e )
314 {
315 interpolateObjectAction.problems.add(
316 new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ).setMessage(
317 "Failed to interpolate field4: " + field + " on class: "
318 + field.getType().getName() ).setException( e ) );
319 }
320 finally
321 {
322 field.setAccessible( isAccessible );
323 }
324 }
325
326
327 }
328
329 abstract void doInterpolate( Object target, InterpolateObjectAction ctx )
330 throws IllegalAccessException;
331 }
332
333 static final class StringField
334 extends CacheField
335 {
336 StringField( Field field )
337 {
338 super( field );
339 }
340
341 @Override
342 void doInterpolate( Object target, InterpolateObjectAction ctx )
343 throws IllegalAccessException
344 {
345 String value = (String) field.get( target );
346 if ( value == null )
347 {
348 return;
349 }
350
351 String interpolated = ctx.interpolate( value );
352
353 if ( !interpolated.equals( value ) )
354 {
355 field.set( target, interpolated );
356 }
357 }
358 }
359
360 static final class ListField
361 extends CacheField
362 {
363 ListField( Field field )
364 {
365 super( field );
366 }
367
368 @Override
369 void doInterpolate( Object target, InterpolateObjectAction ctx )
370 throws IllegalAccessException
371 {
372 @SuppressWarnings( "unchecked" ) List<Object> c = (List<Object>) field.get( target );
373 if ( c == null )
374 {
375 return;
376 }
377
378 int size = c.size();
379 Object value;
380 for ( int i = 0; i < size; i++ )
381 {
382
383 value = c.get( i );
384
385 if ( value != null )
386 {
387 if ( String.class == value.getClass() )
388 {
389 String interpolated = ctx.interpolate( (String) value );
390
391 if ( !interpolated.equals( value ) )
392 {
393 try
394 {
395 c.set( i, interpolated );
396 }
397 catch ( UnsupportedOperationException e )
398 {
399 return;
400 }
401 }
402 }
403 else
404 {
405 if ( value.getClass().isArray() )
406 {
407 evaluateArray( value, ctx );
408 }
409 else
410 {
411 ctx.interpolationTargets.add( value );
412 }
413 }
414 }
415 }
416 }
417 }
418
419 static final class MapField
420 extends CacheField
421 {
422 MapField( Field field )
423 {
424 super( field );
425 }
426
427 @Override
428 void doInterpolate( Object target, InterpolateObjectAction ctx )
429 throws IllegalAccessException
430 {
431 @SuppressWarnings( "unchecked" ) Map<Object, Object> m = (Map<Object, Object>) field.get( target );
432 if ( m == null || m.isEmpty() )
433 {
434 return;
435 }
436
437 for ( Map.Entry<Object, Object> entry : m.entrySet() )
438 {
439 Object value = entry.getValue();
440
441 if ( value == null )
442 {
443 continue;
444 }
445
446 if ( String.class == value.getClass() )
447 {
448 String interpolated = ctx.interpolate( (String) value );
449
450 if ( !interpolated.equals( value ) )
451 {
452 try
453 {
454 entry.setValue( interpolated );
455 }
456 catch ( UnsupportedOperationException ignore )
457 {
458
459 }
460 }
461 }
462 else if ( value.getClass().isArray() )
463 {
464 evaluateArray( value, ctx );
465 }
466 else
467 {
468 ctx.interpolationTargets.add( value );
469 }
470 }
471 }
472 }
473
474 static final class ObjectField
475 extends CacheField
476 {
477 private final boolean isArray;
478
479 ObjectField( Field field )
480 {
481 super( field );
482 this.isArray = field.getType().isArray();
483 }
484
485 @Override
486 void doInterpolate( Object target, InterpolateObjectAction ctx )
487 throws IllegalAccessException
488 {
489 Object value = field.get( target );
490 if ( value != null )
491 {
492 if ( isArray )
493 {
494 evaluateArray( value, ctx );
495 }
496 else
497 {
498 ctx.interpolationTargets.add( value );
499 }
500 }
501 }
502 }
503
504 }
505
506 }