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  /**
48   * StringSearchModelInterpolator
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      // Empirical data from 3.x, actual =40
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                 //noinspection SimplifiableIfStatement
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 ) ); // TODO Not entirely the same message
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                                 // nop
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 }