001package org.apache.maven.project.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.project.ProjectBuilderConfiguration;
024import org.apache.maven.project.path.PathTranslator;
025import org.codehaus.plexus.component.annotations.Component;
026import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
027import org.codehaus.plexus.interpolation.Interpolator;
028import org.codehaus.plexus.interpolation.StringSearchInterpolator;
029import org.codehaus.plexus.interpolation.ValueSource;
030import org.codehaus.plexus.logging.Logger;
031
032import java.io.File;
033import java.lang.reflect.Array;
034import java.lang.reflect.Field;
035import java.security.AccessController;
036import java.security.PrivilegedAction;
037import java.util.ArrayList;
038import java.util.Collection;
039import java.util.LinkedList;
040import java.util.List;
041import java.util.Map;
042import java.util.WeakHashMap;
043
044@Deprecated
045@Component( role = ModelInterpolator.class )
046public class StringSearchModelInterpolator
047    extends AbstractStringBasedModelInterpolator
048{
049
050    private static final Map<Class<?>, Field[]> fieldsByClass = new WeakHashMap<>();
051    private static final Map<Class<?>, Boolean> fieldIsPrimitiveByClass = new WeakHashMap<>();
052
053    public StringSearchModelInterpolator()
054    {
055    }
056
057    public StringSearchModelInterpolator( PathTranslator pathTranslator )
058    {
059        super( pathTranslator );
060    }
061
062    public Model interpolate( Model model, File projectDir, ProjectBuilderConfiguration config, boolean debugEnabled )
063        throws ModelInterpolationException
064    {
065        interpolateObject( model, model, projectDir, config, debugEnabled );
066
067        return model;
068    }
069
070    protected void interpolateObject( Object obj, Model model, File projectDir, ProjectBuilderConfiguration config,
071                                      boolean debugEnabled )
072        throws ModelInterpolationException
073    {
074        try
075        {
076            List<ValueSource> valueSources = createValueSources( model, projectDir, config );
077            List<InterpolationPostProcessor> postProcessors = createPostProcessors( model, projectDir, config );
078
079            InterpolateObjectAction action =
080                new InterpolateObjectAction( obj, valueSources, postProcessors, debugEnabled,
081                                             this, getLogger() );
082
083            ModelInterpolationException error = AccessController.doPrivileged( action );
084
085            if ( error != null )
086            {
087                throw error;
088            }
089        }
090        finally
091        {
092            getInterpolator().clearAnswers();
093        }
094    }
095
096    protected Interpolator createInterpolator()
097    {
098        StringSearchInterpolator interpolator = new StringSearchInterpolator();
099        interpolator.setCacheAnswers( true );
100
101        return interpolator;
102    }
103
104    private static final class InterpolateObjectAction implements PrivilegedAction<ModelInterpolationException>
105    {
106
107        private final boolean debugEnabled;
108        private final LinkedList<Object> interpolationTargets;
109        private final StringSearchModelInterpolator modelInterpolator;
110        private final Logger logger;
111        private final List<ValueSource> valueSources;
112        private final List<InterpolationPostProcessor> postProcessors;
113
114        public InterpolateObjectAction( Object target, List<ValueSource> valueSources,
115                                        List<InterpolationPostProcessor> postProcessors, boolean debugEnabled,
116                                        StringSearchModelInterpolator modelInterpolator, Logger logger )
117        {
118            this.valueSources = valueSources;
119            this.postProcessors = postProcessors;
120            this.debugEnabled = debugEnabled;
121
122            this.interpolationTargets = new LinkedList<>();
123            interpolationTargets.add( target );
124
125            this.modelInterpolator = modelInterpolator;
126            this.logger = logger;
127        }
128
129        public ModelInterpolationException run()
130        {
131            while ( !interpolationTargets.isEmpty() )
132            {
133                Object obj = interpolationTargets.removeFirst();
134
135                try
136                {
137                    traverseObjectWithParents( obj.getClass(), obj );
138                }
139                catch ( ModelInterpolationException e )
140                {
141                    return e;
142                }
143            }
144
145            return null;
146        }
147
148        @SuppressWarnings( "unchecked" )
149        private void traverseObjectWithParents( Class<?> cls, Object target )
150            throws ModelInterpolationException
151        {
152            if ( cls == null )
153            {
154                return;
155            }
156
157
158            if ( cls.isArray() )
159            {
160                evaluateArray( target );
161            }
162            else if ( isQualifiedForInterpolation( cls ) )
163            {
164                Field[] fields = fieldsByClass.get( cls );
165                if ( fields == null )
166                {
167                    fields = cls.getDeclaredFields();
168                    fieldsByClass.put( cls, fields );
169                }
170
171                for ( Field field : fields )
172                {
173                    Class<?> type = field.getType();
174                    if ( isQualifiedForInterpolation( field, type ) )
175                    {
176                        boolean isAccessible = field.isAccessible();
177                        field.setAccessible( true );
178                        try
179                        {
180                            try
181                            {
182                                if ( String.class == type )
183                                {
184                                    String value = (String) field.get( target );
185                                    if ( value != null )
186                                    {
187                                        String interpolated =
188                                            modelInterpolator.interpolateInternal( value, valueSources, postProcessors,
189                                                                                   debugEnabled );
190
191                                        if ( !interpolated.equals( value ) )
192                                        {
193                                            field.set( target, interpolated );
194                                        }
195                                    }
196                                }
197                                else if ( Collection.class.isAssignableFrom( type ) )
198                                {
199                                    Collection<Object> c = (Collection<Object>) field.get( target );
200                                    if ( c != null && !c.isEmpty() )
201                                    {
202                                        List<Object> originalValues = new ArrayList<>( c );
203                                        try
204                                        {
205                                            c.clear();
206                                        }
207                                        catch ( UnsupportedOperationException e )
208                                        {
209                                            if ( debugEnabled && logger != null )
210                                            {
211                                                logger.debug( "Skipping interpolation of field: " + field + " in: "
212                                                                  + cls.getName()
213                                                                  + "; it is an unmodifiable collection." );
214                                            }
215                                            continue;
216                                        }
217
218                                        for ( Object value : originalValues )
219                                        {
220                                            if ( value != null )
221                                            {
222                                                if ( String.class == value.getClass() )
223                                                {
224                                                    String interpolated =
225                                                        modelInterpolator.interpolateInternal( (String) value,
226                                                                                               valueSources,
227                                                                                               postProcessors,
228                                                                                               debugEnabled );
229
230                                                    if ( !interpolated.equals( value ) )
231                                                    {
232                                                        c.add( interpolated );
233                                                    }
234                                                    else
235                                                    {
236                                                        c.add( value );
237                                                    }
238                                                }
239                                                else
240                                                {
241                                                    c.add( value );
242                                                    if ( value.getClass().isArray() )
243                                                    {
244                                                        evaluateArray( value );
245                                                    }
246                                                    else
247                                                    {
248                                                        interpolationTargets.add( value );
249                                                    }
250                                                }
251                                            }
252                                            else
253                                            {
254                                                // add the null back in...not sure what else to do...
255                                                c.add( value );
256                                            }
257                                        }
258                                    }
259                                }
260                                else if ( Map.class.isAssignableFrom( type ) )
261                                {
262                                    Map<Object, Object> m = (Map<Object, Object>) field.get( target );
263                                    if ( m != null && !m.isEmpty() )
264                                    {
265                                        for ( Map.Entry<Object, Object> entry : m.entrySet() )
266                                        {
267                                            Object value = entry.getValue();
268
269                                            if ( value != null )
270                                            {
271                                                if ( String.class == value.getClass() )
272                                                {
273                                                    String interpolated =
274                                                        modelInterpolator.interpolateInternal( (String) value,
275                                                                                               valueSources,
276                                                                                               postProcessors,
277                                                                                               debugEnabled );
278
279                                                    if ( !interpolated.equals( value ) )
280                                                    {
281                                                        try
282                                                        {
283                                                            entry.setValue( interpolated );
284                                                        }
285                                                        catch ( UnsupportedOperationException e )
286                                                        {
287                                                            if ( debugEnabled && logger != null )
288                                                            {
289                                                                logger.debug(
290                                                                    "Skipping interpolation of field: " + field
291                                                                        + " (key: " + entry.getKey() + ") in: "
292                                                                        + cls.getName()
293                                                                        + "; it is an unmodifiable collection." );
294                                                            }
295                                                        }
296                                                    }
297                                                }
298                                                else
299                                                {
300                                                    if ( value.getClass().isArray() )
301                                                    {
302                                                        evaluateArray( value );
303                                                    }
304                                                    else
305                                                    {
306                                                        interpolationTargets.add( value );
307                                                    }
308                                                }
309                                            }
310                                        }
311                                    }
312                                }
313                                else
314                                {
315                                    Object value = field.get( target );
316                                    if ( value != null )
317                                    {
318                                        if ( field.getType().isArray() )
319                                        {
320                                            evaluateArray( value );
321                                        }
322                                        else
323                                        {
324                                            interpolationTargets.add( value );
325                                        }
326                                    }
327                                }
328                            }
329                            catch ( IllegalArgumentException | IllegalAccessException e )
330                            {
331                                throw new ModelInterpolationException(
332                                    "Failed to interpolate field: " + field + " on class: " + cls.getName(), e );
333                            }
334                        }
335                        finally
336                        {
337                            field.setAccessible( isAccessible );
338                        }
339                    }
340                }
341
342                traverseObjectWithParents( cls.getSuperclass(), target );
343            }
344        }
345
346        private boolean isQualifiedForInterpolation( Class<?> cls )
347        {
348            return !cls.getPackage().getName().startsWith( "java" );
349        }
350
351        private boolean isQualifiedForInterpolation( Field field, Class<?> fieldType )
352        {
353            if ( !fieldIsPrimitiveByClass.containsKey( fieldType ) )
354            {
355                fieldIsPrimitiveByClass.put( fieldType, fieldType.isPrimitive() );
356            }
357
358            if ( fieldIsPrimitiveByClass.get( fieldType ) )
359            {
360                return false;
361            }
362
363//            if ( fieldType.isPrimitive() )
364//            {
365//                return false;
366//            }
367
368            if ( "parent".equals( field.getName() ) )
369            {
370                return false;
371            }
372
373            return true;
374        }
375
376        private void evaluateArray( Object target )
377            throws ModelInterpolationException
378        {
379            int len = Array.getLength( target );
380            for ( int i = 0; i < len; i++ )
381            {
382                Object value = Array.get( target, i );
383                if ( value != null )
384                {
385                    if ( String.class == value.getClass() )
386                    {
387                        String interpolated =
388                            modelInterpolator.interpolateInternal( (String) value, valueSources, postProcessors,
389                                                                   debugEnabled );
390
391                        if ( !interpolated.equals( value ) )
392                        {
393                            Array.set( target, i, interpolated );
394                        }
395                    }
396                    else
397                    {
398                        interpolationTargets.add( value );
399                    }
400                }
401            }
402        }
403    }
404
405}