001package 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
022import org.apache.maven.model.Model;
023import org.apache.maven.model.building.ModelBuildingRequest;
024import org.apache.maven.model.building.ModelProblem.Severity;
025import org.apache.maven.model.building.ModelProblem.Version;
026import org.apache.maven.model.building.ModelProblemCollector;
027import org.apache.maven.model.building.ModelProblemCollectorRequest;
028import org.codehaus.plexus.component.annotations.Component;
029import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
030import org.codehaus.plexus.interpolation.Interpolator;
031import org.codehaus.plexus.interpolation.StringSearchInterpolator;
032import org.codehaus.plexus.interpolation.ValueSource;
033
034import java.io.File;
035import java.lang.reflect.Array;
036import java.lang.reflect.Field;
037import java.lang.reflect.Modifier;
038import java.security.AccessController;
039import java.security.PrivilegedAction;
040import java.util.ArrayList;
041import java.util.Collection;
042import java.util.LinkedList;
043import java.util.List;
044import java.util.Map;
045import java.util.concurrent.ConcurrentHashMap;
046
047@Component( role = ModelInterpolator.class )
048public class StringSearchModelInterpolator
049    extends AbstractStringBasedModelInterpolator
050{
051
052    private static final Map<Class<?>, InterpolateObjectAction.CacheItem> CACHED_ENTRIES =
053        new ConcurrentHashMap<Class<?>, InterpolateObjectAction.CacheItem>( 80, 0.75f, 2 );
054    // Empirical data from 3.x, actual =40
055
056
057    public Model interpolateModel( Model model, File projectDir, ModelBuildingRequest config,
058                                   ModelProblemCollector problems )
059    {
060        interpolateObject( model, model, projectDir, config, problems );
061
062        return model;
063    }
064
065    protected void interpolateObject( Object obj, Model model, File projectDir, ModelBuildingRequest config,
066                                      ModelProblemCollector problems )
067    {
068        try
069        {
070            List<? extends ValueSource> valueSources = createValueSources( model, projectDir, config, problems );
071            List<? extends InterpolationPostProcessor> postProcessors =
072                createPostProcessors( model, projectDir, config );
073
074            InterpolateObjectAction action =
075                new InterpolateObjectAction( obj, valueSources, postProcessors, this, problems );
076
077            AccessController.doPrivileged( action );
078        }
079        finally
080        {
081            getInterpolator().clearAnswers();
082        }
083    }
084
085    protected Interpolator createInterpolator()
086    {
087        StringSearchInterpolator interpolator = new StringSearchInterpolator();
088        interpolator.setCacheAnswers( true );
089
090        return interpolator;
091    }
092
093    private static final class InterpolateObjectAction
094        implements PrivilegedAction<Object>
095    {
096
097        private final LinkedList<Object> interpolationTargets;
098
099        private final StringSearchModelInterpolator modelInterpolator;
100
101        private final List<? extends ValueSource> valueSources;
102
103        private final List<? extends InterpolationPostProcessor> postProcessors;
104
105        private final ModelProblemCollector problems;
106
107        public InterpolateObjectAction( Object target, List<? extends ValueSource> valueSources,
108                                        List<? extends InterpolationPostProcessor> postProcessors,
109                                        StringSearchModelInterpolator modelInterpolator,
110                                        ModelProblemCollector problems )
111        {
112            this.valueSources = valueSources;
113            this.postProcessors = postProcessors;
114
115            this.interpolationTargets = new LinkedList<Object>();
116            interpolationTargets.add( target );
117
118            this.modelInterpolator = modelInterpolator;
119
120            this.problems = problems;
121        }
122
123        public Object run()
124        {
125            while ( !interpolationTargets.isEmpty() )
126            {
127                Object obj = interpolationTargets.removeFirst();
128
129                traverseObjectWithParents( obj.getClass(), obj );
130            }
131
132            return null;
133        }
134
135
136        private String interpolate( String value )
137        {
138            return modelInterpolator.interpolateInternal( value, valueSources, postProcessors, problems );
139        }
140
141        private void traverseObjectWithParents( Class<?> cls, Object target )
142        {
143            if ( cls == null )
144            {
145                return;
146            }
147
148            CacheItem cacheEntry = getCacheEntry( cls );
149            if ( cacheEntry.isArray() )
150            {
151                evaluateArray( target, this );
152            }
153            else if ( cacheEntry.isQualifiedForInterpolation )
154            {
155                cacheEntry.interpolate( target, this );
156
157                traverseObjectWithParents( cls.getSuperclass(), target );
158            }
159        }
160
161
162        private CacheItem getCacheEntry( Class<?> cls )
163        {
164            CacheItem cacheItem = CACHED_ENTRIES.get( cls );
165            if ( cacheItem == null )
166            {
167                cacheItem = new CacheItem( cls );
168                CACHED_ENTRIES.put( cls, cacheItem );
169            }
170            return cacheItem;
171        }
172
173        private static void evaluateArray( Object target, InterpolateObjectAction ctx )
174        {
175            int len = Array.getLength( target );
176            for ( int i = 0; i < len; i++ )
177            {
178                Object value = Array.get( target, i );
179                if ( value != null )
180                {
181                    if ( String.class == value.getClass() )
182                    {
183                        String interpolated = ctx.interpolate( (String) value );
184
185                        if ( !interpolated.equals( value ) )
186                        {
187                            Array.set( target, i, interpolated );
188                        }
189                    }
190                    else
191                    {
192                        ctx.interpolationTargets.add( value );
193                    }
194                }
195            }
196        }
197
198        private static class CacheItem
199        {
200            private final boolean isArray;
201
202            private final boolean isQualifiedForInterpolation;
203
204            private final CacheField[] fields;
205
206            private boolean isQualifiedForInterpolation( Class<?> cls )
207            {
208                return !cls.getName().startsWith( "java" );
209            }
210
211            private boolean isQualifiedForInterpolation( Field field, Class<?> fieldType )
212            {
213                if ( Map.class.equals( fieldType ) && "locations".equals( field.getName() ) )
214                {
215                    return false;
216                }
217
218                //noinspection SimplifiableIfStatement
219                if ( fieldType.isPrimitive() )
220                {
221                    return false;
222                }
223
224                return !"parent".equals( field.getName() );
225            }
226
227            CacheItem( Class clazz )
228            {
229                this.isQualifiedForInterpolation = isQualifiedForInterpolation( clazz );
230                this.isArray = clazz.isArray();
231                List<CacheField> fields = new ArrayList<CacheField>();
232                for ( Field currentField : clazz.getDeclaredFields() )
233                {
234                    Class<?> type = currentField.getType();
235                    if ( isQualifiedForInterpolation( currentField, type ) )
236                    {
237                        if ( String.class == type )
238                        {
239                            if ( !Modifier.isFinal( currentField.getModifiers() ) )
240                            {
241                                fields.add( new StringField( currentField ) );
242                            }
243                        }
244                        else if ( List.class.isAssignableFrom( type ) )
245                        {
246                            fields.add( new ListField( currentField ) );
247                        }
248                        else if ( Collection.class.isAssignableFrom( type ) )
249                        {
250                            throw new RuntimeException( "We dont interpolate into collections, use a list instead" );
251                        }
252                        else if ( Map.class.isAssignableFrom( type ) )
253                        {
254                            fields.add( new MapField( currentField ) );
255                        }
256                        else
257                        {
258                            fields.add( new ObjectField( currentField ) );
259                        }
260                    }
261
262                }
263                this.fields = fields.toArray( new CacheField[fields.size()] );
264
265            }
266
267            public void interpolate( Object target, InterpolateObjectAction interpolateObjectAction )
268            {
269                for ( CacheField field : fields )
270                {
271                    field.interpolate( target, interpolateObjectAction );
272                }
273            }
274
275            public boolean isArray()
276            {
277                return isArray;
278            }
279        }
280
281        abstract static class CacheField
282        {
283            protected final Field field;
284
285            CacheField( Field field )
286            {
287                this.field = field;
288            }
289
290            void interpolate( Object target, InterpolateObjectAction interpolateObjectAction )
291            {
292                synchronized ( field )
293                {
294                    boolean isAccessible = field.isAccessible();
295                    field.setAccessible( true );
296                    try
297                    {
298                        doInterpolate( target, interpolateObjectAction );
299                    }
300                    catch ( IllegalArgumentException e )
301                    {
302                        interpolateObjectAction.problems.add(
303                            new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ).setMessage(
304                                "Failed to interpolate field3: " + field + " on class: "
305                                    + field.getType().getName() ).setException(
306                                e ) ); // todo: Not entirely the same message
307                    }
308                    catch ( IllegalAccessException e )
309                    {
310                        interpolateObjectAction.problems.add(
311                            new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ).setMessage(
312                                "Failed to interpolate field4: " + field + " on class: "
313                                    + field.getType().getName() ).setException( e ) );
314                    }
315                    finally
316                    {
317                        field.setAccessible( isAccessible );
318                    }
319                }
320
321
322            }
323
324            abstract void doInterpolate( Object target, InterpolateObjectAction ctx )
325                throws IllegalAccessException;
326        }
327
328        static final class StringField
329            extends CacheField
330        {
331            StringField( Field field )
332            {
333                super( field );
334            }
335
336            @Override
337            void doInterpolate( Object target, InterpolateObjectAction ctx )
338                throws IllegalAccessException
339            {
340                String value = (String) field.get( target );
341                if ( value == null )
342                {
343                    return;
344                }
345
346                String interpolated = ctx.interpolate( value );
347
348                if ( !interpolated.equals( value ) )
349                {
350                    field.set( target, interpolated );
351                }
352            }
353        }
354
355        static final class ListField
356            extends CacheField
357        {
358            ListField( Field field )
359            {
360                super( field );
361            }
362
363            @Override
364            void doInterpolate( Object target, InterpolateObjectAction ctx )
365                throws IllegalAccessException
366            {
367                @SuppressWarnings( "unchecked" ) List<Object> c = (List<Object>) field.get( target );
368                if ( c == null )
369                {
370                    return;
371                }
372
373                int size = c.size();
374                Object value;
375                for ( int i = 0; i < size; i++ )
376                {
377
378                    value = c.get( i );
379
380                    if ( value != null )
381                    {
382                        if ( String.class == value.getClass() )
383                        {
384                            String interpolated = ctx.interpolate( (String) value );
385
386                            if ( !interpolated.equals( value ) )
387                            {
388                                try
389                                {
390                                    c.set( i, interpolated );
391                                }
392                                catch ( UnsupportedOperationException e )
393                                {
394                                    return;
395                                }
396                            }
397                        }
398                        else
399                        {
400                            if ( value.getClass().isArray() )
401                            {
402                                evaluateArray( value, ctx );
403                            }
404                            else
405                            {
406                                ctx.interpolationTargets.add( value );
407                            }
408                        }
409                    }
410                }
411            }
412        }
413
414        static final class MapField
415            extends CacheField
416        {
417            MapField( Field field )
418            {
419                super( field );
420            }
421
422            @Override
423            void doInterpolate( Object target, InterpolateObjectAction ctx )
424                throws IllegalAccessException
425            {
426                @SuppressWarnings( "unchecked" ) Map<Object, Object> m = (Map<Object, Object>) field.get( target );
427                if ( m == null || m.isEmpty() )
428                {
429                    return;
430                }
431
432                for ( Map.Entry<Object, Object> entry : m.entrySet() )
433                {
434                    Object value = entry.getValue();
435
436                    if ( value == null )
437                    {
438                        continue;
439                    }
440
441                    if ( String.class == value.getClass() )
442                    {
443                        String interpolated = ctx.interpolate( (String) value );
444
445                        if ( !interpolated.equals( value ) )
446                        {
447                            try
448                            {
449                                entry.setValue( interpolated );
450                            }
451                            catch ( UnsupportedOperationException ignore )
452                            {
453                            }
454                        }
455                    }
456                    else if ( value.getClass().isArray() )
457                    {
458                        evaluateArray( value, ctx );
459                    }
460                    else
461                    {
462                        ctx.interpolationTargets.add( value );
463                    }
464                }
465            }
466        }
467
468        static final class ObjectField
469            extends CacheField
470        {
471            private final boolean isArray;
472
473            ObjectField( Field field )
474            {
475                super( field );
476                this.isArray = field.getType().isArray();
477            }
478
479            @Override
480            void doInterpolate( Object target, InterpolateObjectAction ctx )
481                throws IllegalAccessException
482            {
483                Object value = field.get( target );
484                if ( value != null )
485                {
486                    if ( isArray )
487                    {
488                        evaluateArray( value, ctx );
489                    }
490                    else
491                    {
492                        ctx.interpolationTargets.add( value );
493                    }
494                }
495            }
496        }
497
498    }
499
500}