001    package org.apache.maven.model.interpolation;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *  http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import org.apache.maven.model.Model;
023    import org.apache.maven.model.building.ModelBuildingRequest;
024    import org.apache.maven.model.building.ModelProblem.Severity;
025    import org.apache.maven.model.building.ModelProblemCollector;
026    import org.codehaus.plexus.component.annotations.Component;
027    import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
028    import org.codehaus.plexus.interpolation.Interpolator;
029    import org.codehaus.plexus.interpolation.StringSearchInterpolator;
030    import org.codehaus.plexus.interpolation.ValueSource;
031    
032    import java.io.File;
033    import java.lang.reflect.Array;
034    import java.lang.reflect.Field;
035    import java.lang.reflect.Modifier;
036    import java.security.AccessController;
037    import java.security.PrivilegedAction;
038    import java.util.ArrayList;
039    import java.util.Collection;
040    import java.util.LinkedList;
041    import java.util.List;
042    import java.util.Map;
043    import java.util.concurrent.ConcurrentHashMap;
044    
045    @Component( role = ModelInterpolator.class )
046    public class StringSearchModelInterpolator
047        extends AbstractStringBasedModelInterpolator
048    {
049    
050        private static final Map<Class<?>, Field[]> fieldsByClass =
051                new ConcurrentHashMap<Class<?>, Field[]>( 80, 0.75f, 2 );  // Empirical data from 3.x, actual =40
052        private static final Map<Class<?>, Boolean> fieldIsPrimitiveByClass =
053                new ConcurrentHashMap<Class<?>, Boolean>( 62, 0.75f, 2 ); // Empirical data from 3.x, actual 31
054    
055        public Model interpolateModel( Model model, File projectDir, ModelBuildingRequest config,
056                                       ModelProblemCollector problems )
057        {
058            interpolateObject( model, model, projectDir, config, problems );
059    
060            return model;
061        }
062    
063        protected void interpolateObject( Object obj, Model model, File projectDir, ModelBuildingRequest config,
064                                          ModelProblemCollector problems )
065        {
066            try
067            {
068                List<? extends ValueSource> valueSources = createValueSources( model, projectDir, config, problems );
069                List<? extends InterpolationPostProcessor> postProcessors = createPostProcessors( model, projectDir,
070                                                                                                  config );
071    
072                InterpolateObjectAction action =
073                    new InterpolateObjectAction( obj, valueSources, postProcessors, this, problems );
074    
075                AccessController.doPrivileged( action );
076            }
077            finally
078            {
079                getInterpolator().clearAnswers();
080            }
081        }
082    
083        protected Interpolator createInterpolator()
084        {
085            StringSearchInterpolator interpolator = new StringSearchInterpolator();
086            interpolator.setCacheAnswers( true );
087    
088            return interpolator;
089        }
090    
091        private static final class InterpolateObjectAction
092            implements PrivilegedAction<Object>
093        {
094    
095            private final LinkedList<Object> interpolationTargets;
096            private final StringSearchModelInterpolator modelInterpolator;
097            private final List<? extends ValueSource> valueSources;
098            private final List<? extends InterpolationPostProcessor> postProcessors;
099            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    }