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<>( 80, 0.75f, 2 );
54      // Empirical data from 3.x, actual =40
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                 //noinspection SimplifiableIfStatement
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 ) ); // todo: Not entirely the same message
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                                 // nop
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 }