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 }