View Javadoc
1   package org.apache.maven.model.interpolation;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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      // Empirical data from 3.x, actual =40
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                 //noinspection SimplifiableIfStatement
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 ) ); // todo: Not entirely the same message
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 }