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.ModelProblem.Version;
026    import org.apache.maven.model.building.ModelProblemCollector;
027    import org.apache.maven.model.building.ModelProblemCollectorRequest;
028    import org.codehaus.plexus.component.annotations.Component;
029    import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
030    import org.codehaus.plexus.interpolation.Interpolator;
031    import org.codehaus.plexus.interpolation.StringSearchInterpolator;
032    import org.codehaus.plexus.interpolation.ValueSource;
033    
034    import java.io.File;
035    import java.lang.reflect.Array;
036    import java.lang.reflect.Field;
037    import java.lang.reflect.Modifier;
038    import java.security.AccessController;
039    import java.security.PrivilegedAction;
040    import java.util.ArrayList;
041    import java.util.Collection;
042    import java.util.LinkedList;
043    import java.util.List;
044    import java.util.Map;
045    import java.util.concurrent.ConcurrentHashMap;
046    
047    @Component( role = ModelInterpolator.class )
048    public 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    }