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