| package org.apache.maven.model.interpolation; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import org.apache.maven.model.InputLocation; |
| import org.apache.maven.model.Model; |
| import org.apache.maven.model.building.ModelBuildingRequest; |
| import org.apache.maven.model.building.ModelProblem.Severity; |
| import org.apache.maven.model.building.ModelProblem.Version; |
| import org.apache.maven.model.building.ModelProblemCollector; |
| import org.apache.maven.model.building.ModelProblemCollectorRequest; |
| import org.apache.maven.model.path.PathTranslator; |
| import org.apache.maven.model.path.UrlNormalizer; |
| import org.codehaus.plexus.interpolation.InterpolationException; |
| import org.codehaus.plexus.interpolation.InterpolationPostProcessor; |
| import org.codehaus.plexus.interpolation.RecursionInterceptor; |
| import org.codehaus.plexus.interpolation.StringSearchInterpolator; |
| import org.codehaus.plexus.interpolation.ValueSource; |
| |
| import java.io.File; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Modifier; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import javax.inject.Inject; |
| |
| /** |
| * StringSearchModelInterpolator |
| * @deprecated replaced by StringVisitorModelInterpolator (MNG-6697) |
| */ |
| @Deprecated |
| public class StringSearchModelInterpolator |
| extends AbstractStringBasedModelInterpolator |
| { |
| private static final Map<Class<?>, InterpolateObjectAction.CacheItem> CACHED_ENTRIES = |
| new ConcurrentHashMap<>( 80, 0.75f, 2 ); |
| // Empirical data from 3.x, actual =40 |
| |
| private interface InnerInterpolator |
| { |
| String interpolate( String value ); |
| } |
| |
| @Inject |
| public StringSearchModelInterpolator( PathTranslator pathTranslator, UrlNormalizer urlNormalizer, |
| ModelVersionProcessor processor ) |
| { |
| super( pathTranslator, urlNormalizer, processor ); |
| } |
| |
| @Override |
| public Model interpolateModel( Model model, File projectDir, ModelBuildingRequest config, |
| ModelProblemCollector problems ) |
| { |
| interpolateObject( model, model, projectDir, config, problems ); |
| return model; |
| } |
| |
| void interpolateObject( Object obj, Model model, File projectDir, ModelBuildingRequest config, |
| ModelProblemCollector problems ) |
| { |
| List<? extends ValueSource> valueSources = createValueSources( model, projectDir, config ); |
| List<? extends InterpolationPostProcessor> postProcessors = createPostProcessors( model, projectDir, config ); |
| |
| InnerInterpolator innerInterpolator = createInterpolator( valueSources, postProcessors, problems ); |
| |
| PrivilegedAction<Object> action = new InterpolateObjectAction( obj, innerInterpolator, problems ); |
| AccessController.doPrivileged( action ); |
| } |
| |
| private InnerInterpolator createInterpolator( List<? extends ValueSource> valueSources, |
| List<? extends InterpolationPostProcessor> postProcessors, |
| final ModelProblemCollector problems ) |
| { |
| final Map<String, String> cache = new HashMap<>(); |
| final StringSearchInterpolator interpolator = new StringSearchInterpolator(); |
| interpolator.setCacheAnswers( true ); |
| for ( ValueSource vs : valueSources ) |
| { |
| interpolator.addValueSource( vs ); |
| } |
| for ( InterpolationPostProcessor postProcessor : postProcessors ) |
| { |
| interpolator.addPostProcessor( postProcessor ); |
| } |
| final RecursionInterceptor recursionInterceptor = createRecursionInterceptor(); |
| return value -> |
| { |
| if ( value != null && value.contains( "${" ) ) |
| { |
| String c = cache.get( value ); |
| if ( c == null ) |
| { |
| try |
| { |
| c = interpolator.interpolate( value, recursionInterceptor ); |
| } |
| catch ( InterpolationException e ) |
| { |
| problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ) |
| .setMessage( e.getMessage() ).setException( e ) ); |
| } |
| cache.put( value, c ); |
| } |
| return c; |
| } |
| return value; |
| }; |
| } |
| |
| private static final class InterpolateObjectAction |
| implements PrivilegedAction<Object> |
| { |
| private final LinkedList<Object> interpolationTargets; |
| |
| private final InnerInterpolator interpolator; |
| |
| private final ModelProblemCollector problems; |
| |
| InterpolateObjectAction( Object target, InnerInterpolator interpolator, ModelProblemCollector problems ) |
| { |
| this.interpolationTargets = new LinkedList<>(); |
| interpolationTargets.add( target ); |
| this.interpolator = interpolator; |
| this.problems = problems; |
| } |
| |
| @Override |
| public Object run() |
| { |
| while ( !interpolationTargets.isEmpty() ) |
| { |
| Object obj = interpolationTargets.removeFirst(); |
| |
| traverseObjectWithParents( obj.getClass(), obj ); |
| } |
| return null; |
| } |
| |
| private String interpolate( String value ) |
| { |
| return interpolator.interpolate( value ); |
| } |
| |
| private void traverseObjectWithParents( Class<?> cls, Object target ) |
| { |
| if ( cls == null ) |
| { |
| return; |
| } |
| |
| CacheItem cacheEntry = getCacheEntry( cls ); |
| if ( cacheEntry.isArray() ) |
| { |
| evaluateArray( target, this ); |
| } |
| else if ( cacheEntry.isQualifiedForInterpolation ) |
| { |
| cacheEntry.interpolate( target, this ); |
| |
| traverseObjectWithParents( cls.getSuperclass(), target ); |
| } |
| } |
| |
| private CacheItem getCacheEntry( Class<?> cls ) |
| { |
| CacheItem cacheItem = CACHED_ENTRIES.get( cls ); |
| if ( cacheItem == null ) |
| { |
| cacheItem = new CacheItem( cls ); |
| CACHED_ENTRIES.put( cls, cacheItem ); |
| } |
| return cacheItem; |
| } |
| |
| private static void evaluateArray( Object target, InterpolateObjectAction ctx ) |
| { |
| int len = Array.getLength( target ); |
| for ( int i = 0; i < len; i++ ) |
| { |
| Object value = Array.get( target, i ); |
| if ( value != null ) |
| { |
| if ( String.class == value.getClass() ) |
| { |
| String interpolated = ctx.interpolate( (String) value ); |
| |
| if ( !interpolated.equals( value ) ) |
| { |
| Array.set( target, i, interpolated ); |
| } |
| } |
| else |
| { |
| ctx.interpolationTargets.add( value ); |
| } |
| } |
| } |
| } |
| |
| private static class CacheItem |
| { |
| private final boolean isArray; |
| |
| private final boolean isQualifiedForInterpolation; |
| |
| private final CacheField[] fields; |
| |
| private boolean isQualifiedForInterpolation( Class<?> cls ) |
| { |
| Package pkg = cls.getPackage(); |
| if ( pkg == null ) |
| { |
| return true; |
| } |
| String pkgName = pkg.getName(); |
| return !pkgName.startsWith( "java." ) && !pkgName.startsWith( "javax." ); |
| } |
| |
| private boolean isQualifiedForInterpolation( Field field, Class<?> fieldType ) |
| { |
| if ( Map.class.equals( fieldType ) && "locations".equals( field.getName() ) ) |
| { |
| return false; |
| } |
| if ( InputLocation.class.equals( fieldType ) ) |
| { |
| return false; |
| } |
| |
| //noinspection SimplifiableIfStatement |
| if ( fieldType.isPrimitive() ) |
| { |
| return false; |
| } |
| |
| return !"parent".equals( field.getName() ); |
| } |
| |
| CacheItem( Class clazz ) |
| { |
| this.isQualifiedForInterpolation = isQualifiedForInterpolation( clazz ); |
| this.isArray = clazz.isArray(); |
| List<CacheField> fields = new ArrayList<>(); |
| if ( isQualifiedForInterpolation ) |
| { |
| for ( Field currentField : clazz.getDeclaredFields() ) |
| { |
| Class<?> type = currentField.getType(); |
| if ( isQualifiedForInterpolation( currentField, type ) ) |
| { |
| if ( String.class == type ) |
| { |
| if ( !Modifier.isFinal( currentField.getModifiers() ) ) |
| { |
| fields.add( new StringField( currentField ) ); |
| } |
| } |
| else if ( List.class.isAssignableFrom( type ) ) |
| { |
| fields.add( new ListField( currentField ) ); |
| } |
| else if ( Collection.class.isAssignableFrom( type ) ) |
| { |
| throw new RuntimeException( |
| "We dont interpolate into collections, use a list instead" ); |
| } |
| else if ( Map.class.isAssignableFrom( type ) ) |
| { |
| fields.add( new MapField( currentField ) ); |
| } |
| else |
| { |
| fields.add( new ObjectField( currentField ) ); |
| } |
| } |
| } |
| } |
| this.fields = fields.toArray( new CacheField[0] ); |
| } |
| |
| void interpolate( Object target, InterpolateObjectAction interpolateObjectAction ) |
| { |
| for ( CacheField field : fields ) |
| { |
| field.interpolate( target, interpolateObjectAction ); |
| } |
| } |
| |
| boolean isArray() |
| { |
| return isArray; |
| } |
| } |
| |
| abstract static class CacheField |
| { |
| final Field field; |
| |
| CacheField( Field field ) |
| { |
| this.field = field; |
| field.setAccessible( true ); |
| } |
| |
| void interpolate( Object target, InterpolateObjectAction interpolateObjectAction ) |
| { |
| try |
| { |
| doInterpolate( target, interpolateObjectAction ); |
| } |
| catch ( IllegalArgumentException e ) |
| { |
| interpolateObjectAction.problems.add( |
| new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ).setMessage( |
| "Failed to interpolate field3: " + field + " on class: " |
| + field.getType().getName() ).setException( |
| e ) ); // TODO Not entirely the same message |
| } |
| catch ( IllegalAccessException e ) |
| { |
| interpolateObjectAction.problems.add( |
| new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ).setMessage( |
| "Failed to interpolate field4: " + field + " on class: " |
| + field.getType().getName() ).setException( e ) ); |
| } |
| } |
| |
| abstract void doInterpolate( Object target, InterpolateObjectAction ctx ) |
| throws IllegalAccessException; |
| } |
| |
| static final class StringField |
| extends CacheField |
| { |
| StringField( Field field ) |
| { |
| super( field ); |
| } |
| |
| @Override |
| void doInterpolate( Object target, InterpolateObjectAction ctx ) |
| throws IllegalAccessException |
| { |
| String value = (String) field.get( target ); |
| if ( value == null ) |
| { |
| return; |
| } |
| |
| String interpolated = ctx.interpolate( value ); |
| |
| if ( interpolated != null && !interpolated.equals( value ) ) |
| { |
| field.set( target, interpolated ); |
| } |
| } |
| } |
| |
| static final class ListField |
| extends CacheField |
| { |
| ListField( Field field ) |
| { |
| super( field ); |
| } |
| |
| @Override |
| void doInterpolate( Object target, InterpolateObjectAction ctx ) |
| throws IllegalAccessException |
| { |
| @SuppressWarnings( "unchecked" ) List<Object> c = (List<Object>) field.get( target ); |
| if ( c == null ) |
| { |
| return; |
| } |
| |
| for ( int i = 0, size = c.size(); i < size; i++ ) |
| { |
| Object value = c.get( i ); |
| |
| if ( value != null ) |
| { |
| if ( String.class == value.getClass() ) |
| { |
| String interpolated = ctx.interpolate( (String) value ); |
| |
| if ( !interpolated.equals( value ) ) |
| { |
| try |
| { |
| c.set( i, interpolated ); |
| } |
| catch ( UnsupportedOperationException e ) |
| { |
| return; |
| } |
| } |
| } |
| else |
| { |
| if ( value.getClass().isArray() ) |
| { |
| evaluateArray( value, ctx ); |
| } |
| else |
| { |
| ctx.interpolationTargets.add( value ); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| static final class MapField |
| extends CacheField |
| { |
| MapField( Field field ) |
| { |
| super( field ); |
| } |
| |
| @Override |
| void doInterpolate( Object target, InterpolateObjectAction ctx ) |
| throws IllegalAccessException |
| { |
| @SuppressWarnings( "unchecked" ) Map<Object, Object> m = (Map<Object, Object>) field.get( target ); |
| if ( m == null || m.isEmpty() ) |
| { |
| return; |
| } |
| |
| for ( Map.Entry<Object, Object> entry : m.entrySet() ) |
| { |
| Object value = entry.getValue(); |
| |
| if ( value == null ) |
| { |
| continue; |
| } |
| |
| if ( String.class == value.getClass() ) |
| { |
| String interpolated = ctx.interpolate( (String) value ); |
| |
| if ( interpolated != null && !interpolated.equals( value ) ) |
| { |
| try |
| { |
| entry.setValue( interpolated ); |
| } |
| catch ( UnsupportedOperationException ignore ) |
| { |
| // nop |
| } |
| } |
| } |
| else if ( value.getClass().isArray() ) |
| { |
| evaluateArray( value, ctx ); |
| } |
| else |
| { |
| ctx.interpolationTargets.add( value ); |
| } |
| } |
| } |
| } |
| |
| static final class ObjectField |
| extends CacheField |
| { |
| private final boolean isArray; |
| |
| ObjectField( Field field ) |
| { |
| super( field ); |
| this.isArray = field.getType().isArray(); |
| } |
| |
| @Override |
| void doInterpolate( Object target, InterpolateObjectAction ctx ) |
| throws IllegalAccessException |
| { |
| Object value = field.get( target ); |
| if ( value != null ) |
| { |
| if ( isArray ) |
| { |
| evaluateArray( value, ctx ); |
| } |
| else |
| { |
| ctx.interpolationTargets.add( value ); |
| } |
| } |
| } |
| } |
| } |
| } |