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                 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 ) ); // TODO Not entirely the same message
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                                 // nop
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 }