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 }