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.ModelProblemCollector;
26  import org.codehaus.plexus.component.annotations.Component;
27  import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
28  import org.codehaus.plexus.interpolation.Interpolator;
29  import org.codehaus.plexus.interpolation.StringSearchInterpolator;
30  import org.codehaus.plexus.interpolation.ValueSource;
31  
32  import java.io.File;
33  import java.lang.reflect.Array;
34  import java.lang.reflect.Field;
35  import java.lang.reflect.Modifier;
36  import java.security.AccessController;
37  import java.security.PrivilegedAction;
38  import java.util.ArrayList;
39  import java.util.Collection;
40  import java.util.LinkedList;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.concurrent.ConcurrentHashMap;
44  
45  @Component( role = ModelInterpolator.class )
46  public class StringSearchModelInterpolator
47      extends AbstractStringBasedModelInterpolator
48  {
49  
50      private static final Map<Class<?>, Field[]> fieldsByClass =
51              new ConcurrentHashMap<Class<?>, Field[]>( 80, 0.75f, 2 );  // Empirical data from 3.x, actual =40
52      private static final Map<Class<?>, Boolean> fieldIsPrimitiveByClass =
53              new ConcurrentHashMap<Class<?>, Boolean>( 62, 0.75f, 2 ); // Empirical data from 3.x, actual 31
54  
55      public Model interpolateModel( Model model, File projectDir, ModelBuildingRequest config,
56                                     ModelProblemCollector problems )
57      {
58          interpolateObject( model, model, projectDir, config, problems );
59  
60          return model;
61      }
62  
63      protected void interpolateObject( Object obj, Model model, File projectDir, ModelBuildingRequest config,
64                                        ModelProblemCollector problems )
65      {
66          try
67          {
68              List<? extends ValueSource> valueSources = createValueSources( model, projectDir, config, problems );
69              List<? extends InterpolationPostProcessor> postProcessors = createPostProcessors( model, projectDir,
70                                                                                                config );
71  
72              InterpolateObjectAction action =
73                  new InterpolateObjectAction( obj, valueSources, postProcessors, this, problems );
74  
75              AccessController.doPrivileged( action );
76          }
77          finally
78          {
79              getInterpolator().clearAnswers();
80          }
81      }
82  
83      protected Interpolator createInterpolator()
84      {
85          StringSearchInterpolator interpolator = new StringSearchInterpolator();
86          interpolator.setCacheAnswers( true );
87  
88          return interpolator;
89      }
90  
91      private static final class InterpolateObjectAction
92          implements PrivilegedAction<Object>
93      {
94  
95          private final LinkedList<Object> interpolationTargets;
96          private final StringSearchModelInterpolator modelInterpolator;
97          private final List<? extends ValueSource> valueSources;
98          private final List<? extends InterpolationPostProcessor> postProcessors;
99          private final ModelProblemCollector problems;
100 
101         public InterpolateObjectAction( Object target, List<? extends ValueSource> valueSources,
102                                         List<? extends InterpolationPostProcessor> postProcessors,
103                                         StringSearchModelInterpolator modelInterpolator,
104                                         ModelProblemCollector problems )
105         {
106             this.valueSources = valueSources;
107             this.postProcessors = postProcessors;
108 
109             this.interpolationTargets = new LinkedList<Object>();
110             interpolationTargets.add( target );
111 
112             this.modelInterpolator = modelInterpolator;
113 
114             this.problems = problems;
115         }
116 
117         public Object run()
118         {
119             while ( !interpolationTargets.isEmpty() )
120             {
121                 Object obj = interpolationTargets.removeFirst();
122 
123                 traverseObjectWithParents( obj.getClass(), obj );
124             }
125 
126             return null;
127         }
128 
129         private void traverseObjectWithParents( Class<?> cls, Object target )
130         {
131             if ( cls == null )
132             {
133                 return;
134             }
135 
136             if ( cls.isArray() )
137             {
138                 evaluateArray( target );
139             }
140             else if ( isQualifiedForInterpolation( cls ) )
141             {
142                 for ( Field currentField : getFields( cls ) )
143                 {
144                     Class<?> type = currentField.getType();
145                     if ( isQualifiedForInterpolation( currentField, type ) )
146                     {
147                         synchronized ( currentField )
148                         {
149                             interpolateField( cls, target, currentField, type );
150                         }
151                     }
152                 }
153 
154                 traverseObjectWithParents( cls.getSuperclass(), target );
155             }
156         }
157 
158         private void interpolateField( Class<?> cls, Object target, Field field, Class<?> type )
159         {
160             boolean isAccessible = field.isAccessible();
161             field.setAccessible( true );
162             try
163             {
164                 if ( String.class == type )
165                 {
166                     interpolateStringField( target, field );
167                 }
168                 else if ( Collection.class.isAssignableFrom( type ) )
169                 {
170                     interpolateCollectionField( target, field );
171                 }
172                 else if ( Map.class.isAssignableFrom( type ) )
173                 {
174                     interpolateMapField( target, field );
175                 }
176                 else
177                 {
178                     Object value = field.get( target );
179                     if ( value != null )
180                     {
181                         if ( field.getType().isArray() )
182                         {
183                             evaluateArray( value );
184                         }
185                         else
186                         {
187                             interpolationTargets.add( value );
188                         }
189                     }
190                 }
191             }
192             catch ( IllegalArgumentException e )
193             {
194                 problems.add( Severity.ERROR, "Failed to interpolate field3: " + field + " on class: " + cls.getName(),
195                               null, e );
196             }
197             catch ( IllegalAccessException e )
198             {
199                 problems.add( Severity.ERROR, "Failed to interpolate field4: " + field + " on class: " + cls.getName(),
200                               null, e );
201             }
202             finally
203             {
204                 field.setAccessible( isAccessible );
205             }
206         }
207 
208         private void interpolateStringField( Object target, Field field )
209             throws IllegalAccessException
210         {
211             String value = (String) field.get( target );
212             if ( value == null || Modifier.isFinal( field.getModifiers() ) )
213             {
214                 return;
215             }
216 
217             String interpolated =
218                 modelInterpolator.interpolateInternal( value, valueSources, postProcessors, problems );
219 
220             if ( !interpolated.equals( value ) )
221             {
222                 field.set( target, interpolated );
223             }
224         }
225 
226         private void interpolateCollectionField( Object target, Field field )
227             throws IllegalAccessException
228         {
229             @SuppressWarnings( "unchecked" )
230             Collection<Object> c = (Collection<Object>) field.get( target );
231             if ( c == null || c.isEmpty() )
232             {
233                 return;
234             }
235 
236             List<Object> originalValues = new ArrayList<Object>( c );
237             try
238             {
239                 c.clear();
240             }
241             catch ( UnsupportedOperationException e )
242             {
243                 return;
244             }
245 
246             for ( Object value : originalValues )
247             {
248                 if ( value == null )
249                 {
250                     // add the null back in...not sure what else to do...
251                     c.add( value );
252                 }
253                 else if ( String.class == value.getClass() )
254                 {
255                     String interpolated =
256                         modelInterpolator.interpolateInternal( (String) value, valueSources, postProcessors, problems );
257 
258                     if ( !interpolated.equals( value ) )
259                     {
260                         c.add( interpolated );
261                     }
262                     else
263                     {
264                         c.add( value );
265                     }
266                 }
267                 else
268                 {
269                     c.add( value );
270                     if ( value.getClass().isArray() )
271                     {
272                         evaluateArray( value );
273                     }
274                     else
275                     {
276                         interpolationTargets.add( value );
277                     }
278                 }
279             }
280         }
281 
282         private void interpolateMapField( Object target, Field field )
283             throws IllegalAccessException
284         {
285             @SuppressWarnings( "unchecked" )
286             Map<Object, Object> m = (Map<Object, Object>) field.get( target );
287             if ( m == null || m.isEmpty() )
288             {
289                 return;
290             }
291 
292             for ( Map.Entry<Object, Object> entry : m.entrySet() )
293             {
294                 Object value = entry.getValue();
295 
296                 if ( value == null )
297                 {
298                     continue;
299                 }
300 
301                 if ( String.class == value.getClass() )
302                 {
303                     String interpolated =
304                         modelInterpolator.interpolateInternal( (String) value, valueSources, postProcessors, problems );
305 
306                     if ( !interpolated.equals( value ) )
307                     {
308                         try
309                         {
310                             entry.setValue( interpolated );
311                         }
312                         catch ( UnsupportedOperationException e )
313                         {
314                             continue;
315                         }
316                     }
317                 }
318                 else if ( value.getClass().isArray() )
319                 {
320                     evaluateArray( value );
321                 }
322                 else
323                 {
324                     interpolationTargets.add( value );
325                 }
326             }
327         }
328 
329         private Field[] getFields( Class<?> cls )
330         {
331             Field[] fields = fieldsByClass.get( cls );
332             if ( fields == null )
333             {
334                 fields = cls.getDeclaredFields();
335                 fieldsByClass.put( cls, fields );
336             }
337             return fields;
338         }
339 
340         private boolean isQualifiedForInterpolation( Class<?> cls )
341         {
342             return !cls.getName().startsWith( "java" );
343         }
344 
345         private boolean isQualifiedForInterpolation( Field field, Class<?> fieldType )
346         {
347             if ( Map.class.equals( fieldType ) && "locations".equals( field.getName() ) )
348             {
349                 return false;
350             }
351 
352             Boolean primitive = fieldIsPrimitiveByClass.get( fieldType );
353             if ( primitive == null )
354             {
355                 primitive = fieldType.isPrimitive();
356                 fieldIsPrimitiveByClass.put( fieldType, primitive );
357             }
358 
359             if ( primitive )
360             {
361                 return false;
362             }
363 
364             return !"parent".equals( field.getName() );
365         }
366 
367         private void evaluateArray( Object target )
368         {
369             int len = Array.getLength( target );
370             for ( int i = 0; i < len; i++ )
371             {
372                 Object value = Array.get( target, i );
373                 if ( value != null )
374                 {
375                     if ( String.class == value.getClass() )
376                     {
377                         String interpolated =
378                             modelInterpolator.interpolateInternal( (String) value, valueSources, postProcessors,
379                                                                    problems );
380 
381                         if ( !interpolated.equals( value ) )
382                         {
383                             Array.set( target, i, interpolated );
384                         }
385                     }
386                     else
387                     {
388                         interpolationTargets.add( value );
389                     }
390                 }
391             }
392         }
393     }
394 
395 }