package org.codehaus.plexus.interpolation.multi;

/*
 * Copyright 2001-2009 Codehaus Foundation.
 *
 * Licensed 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.codehaus.plexus.interpolation.InterpolationCycleException;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
import org.codehaus.plexus.interpolation.Interpolator;
import org.codehaus.plexus.interpolation.RecursionInterceptor;
import org.codehaus.plexus.interpolation.SimpleRecursionInterceptor;
import org.codehaus.plexus.interpolation.ValueSource;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class MultiDelimiterStringSearchInterpolator
    implements Interpolator
{

    private static final int MAX_TRIES = 10;
    
    private Map existingAnswers = new HashMap();

    private List<ValueSource> valueSources = new ArrayList<ValueSource>();

    private List postProcessors = new ArrayList();

    private boolean cacheAnswers = false;

    private LinkedHashSet<DelimiterSpecification> delimiters = new LinkedHashSet<DelimiterSpecification>();

    private String escapeString;

    public MultiDelimiterStringSearchInterpolator()
    {
        delimiters.add( DelimiterSpecification.DEFAULT_SPEC );
    }
    
    public MultiDelimiterStringSearchInterpolator addDelimiterSpec( String delimiterSpec )
    {
        if ( delimiterSpec == null )
        {
            return this;
        }
        delimiters.add( DelimiterSpecification.parse( delimiterSpec ) );
        return this;
    }
    
    public boolean removeDelimiterSpec( String delimiterSpec )
    {
        if ( delimiterSpec == null )
        {
            return false;
        }        
        return delimiters.remove( DelimiterSpecification.parse( delimiterSpec ) );
    }
    
    public MultiDelimiterStringSearchInterpolator withValueSource( ValueSource vs )
    {
        addValueSource( vs );
        return this;
    }
    
    public MultiDelimiterStringSearchInterpolator withPostProcessor( InterpolationPostProcessor postProcessor )
    {
        addPostProcessor( postProcessor );
        return this;
    }

    
{@inheritDoc}
/** * {@inheritDoc} */
public void addValueSource( ValueSource valueSource ) { valueSources.add( valueSource ); }
{@inheritDoc}
/** * {@inheritDoc} */
public void removeValuesSource( ValueSource valueSource ) { valueSources.remove( valueSource ); }
{@inheritDoc}
/** * {@inheritDoc} */
public void addPostProcessor( InterpolationPostProcessor postProcessor ) { postProcessors.add( postProcessor ); }
{@inheritDoc}
/** * {@inheritDoc} */
public void removePostProcessor( InterpolationPostProcessor postProcessor ) { postProcessors.remove( postProcessor ); } public String interpolate( String input, String thisPrefixPattern ) throws InterpolationException { return interpolate( input, new SimpleRecursionInterceptor() ); } public String interpolate( String input, String thisPrefixPattern, RecursionInterceptor recursionInterceptor ) throws InterpolationException { return interpolate( input, recursionInterceptor ); } public String interpolate( String input ) throws InterpolationException { return interpolate( input, new SimpleRecursionInterceptor() ); }
Entry point for recursive resolution of an expression and all of its nested expressions. TODO: Ensure unresolvable expressions don't trigger infinite recursion.
/** * Entry point for recursive resolution of an expression and all of its nested expressions. * * TODO: Ensure unresolvable expressions don't trigger infinite recursion. */
public String interpolate( String input, RecursionInterceptor recursionInterceptor ) throws InterpolationException { try { return interpolate( input, recursionInterceptor, new HashSet() ); } finally { if ( !cacheAnswers ) { existingAnswers.clear(); } } } private String interpolate( String input, RecursionInterceptor recursionInterceptor, Set<String> unresolvable ) throws InterpolationException { if ( input == null ) { // return empty String to prevent NPE too return ""; } StringBuilder result = new StringBuilder( input.length() * 2 ); String lastResult = input; int tries = 0; do { tries++; if ( result.length() > 0 ) { lastResult = result.toString(); result.setLength( 0 ); } int startIdx = -1; int endIdx = -1; DelimiterSpecification selectedSpec = null; while( ( selectedSpec = select( input, endIdx ) ) != null ) { String startExpr = selectedSpec.getBegin(); String endExpr = selectedSpec.getEnd(); startIdx = selectedSpec.getNextStartIndex(); result.append( input, endIdx + 1, startIdx ); endIdx = input.indexOf( endExpr, startIdx + 1 ); if ( endIdx < 0 ) { break; } String wholeExpr = input.substring( startIdx, endIdx + endExpr.length() ); String realExpr = wholeExpr.substring( startExpr.length(), wholeExpr.length() - endExpr.length() ); if ( startIdx >= 0 && escapeString != null && escapeString.length() > 0 ) { int startEscapeIdx = ( startIdx == 0 ) ? 0 : startIdx - escapeString.length(); if ( startEscapeIdx >= 0 ) { String escape = input.substring( startEscapeIdx, startIdx ); if ( escape != null && escapeString.equals( escape ) ) { result.append( wholeExpr ); if ( startEscapeIdx > 0 ) { --startEscapeIdx; } result.replace( startEscapeIdx, startEscapeIdx + escapeString.length(), "" ); continue; } } } boolean resolved = false; if ( !unresolvable.contains( wholeExpr ) ) { if ( realExpr.startsWith( "." ) ) { realExpr = realExpr.substring( 1 ); } if ( recursionInterceptor.hasRecursiveExpression( realExpr ) ) { throw new InterpolationCycleException( recursionInterceptor, realExpr, wholeExpr ); } recursionInterceptor.expressionResolutionStarted( realExpr ); Object value = existingAnswers.get( realExpr ); Object bestAnswer = null; for ( ValueSource vs : valueSources ) { if (value != null ) break; value = vs.getValue( realExpr ); if ( value != null && value.toString().contains( wholeExpr ) ) { bestAnswer = value; value = null; } } // this is the simplest recursion check to catch exact recursion // (non synonym), and avoid the extra effort of more string // searching. if ( value == null && bestAnswer != null ) { throw new InterpolationCycleException( recursionInterceptor, realExpr, wholeExpr ); } if ( value != null ) { value = interpolate( String.valueOf( value ), recursionInterceptor, unresolvable ); if ( postProcessors != null && !postProcessors.isEmpty() ) { for ( Object postProcessor1 : postProcessors ) { InterpolationPostProcessor postProcessor = (InterpolationPostProcessor) postProcessor1; Object newVal = postProcessor.execute( realExpr, value ); if ( newVal != null ) { value = newVal; break; } } } // could use: // result = matcher.replaceFirst( stringValue ); // but this could result in multiple lookups of stringValue, and replaceAll is not correct behaviour result.append( String.valueOf( value ) ); resolved = true; } else { unresolvable.add( wholeExpr ); } recursionInterceptor.expressionResolutionFinished( realExpr ); } if ( !resolved ) { result.append( wholeExpr ); } if ( endIdx > -1 ) { endIdx += endExpr.length() - 1; } } if ( endIdx == -1 && startIdx > -1 ) { result.append( input, startIdx, input.length() ); } else if ( endIdx < input.length() ) { result.append( input, endIdx + 1, input.length() ); } } while( !lastResult.equals( result.toString() ) && tries < MAX_TRIES ); return result.toString(); } private DelimiterSpecification select( String input, int lastEndIdx ) { DelimiterSpecification selected = null; for ( DelimiterSpecification spec : delimiters ) { spec.clearNextStart(); if ( selected == null ) { int idx = input.indexOf( spec.getBegin(), lastEndIdx + 1 ); if ( idx > -1 ) { spec.setNextStartIndex( idx ); selected = spec; } } } return selected; }
Return any feedback messages and errors that were generated - but suppressed - during the interpolation process. Since unresolvable expressions will be left in the source string as-is, this feedback is optional, and will only be useful for debugging interpolation problems.
Returns:a List that may be interspersed with String and Throwable instances.
/** * Return any feedback messages and errors that were generated - but suppressed - during the interpolation process. * Since unresolvable expressions will be left in the source string as-is, this feedback is optional, and will only * be useful for debugging interpolation problems. * * @return a {@link List} that may be interspersed with {@link String} and {@link Throwable} instances. */
public List getFeedback() { List messages = new ArrayList(); for ( ValueSource vs : valueSources ) { List feedback = vs.getFeedback(); if ( feedback != null && !feedback.isEmpty() ) { messages.addAll( feedback ); } } return messages; }
Clear the feedback messages from previous interpolate(..) calls.
/** * Clear the feedback messages from previous interpolate(..) calls. */
public void clearFeedback() { for ( ValueSource vs : valueSources ) { vs.clearFeedback(); } } public boolean isCacheAnswers() { return cacheAnswers; } public void setCacheAnswers( boolean cacheAnswers ) { this.cacheAnswers = cacheAnswers; } public void clearAnswers() { existingAnswers.clear(); } public String getEscapeString() { return escapeString; } public void setEscapeString( String escapeString ) { this.escapeString = escapeString; } public MultiDelimiterStringSearchInterpolator escapeString( String escapeString ) { this.escapeString = escapeString; return this; } public MultiDelimiterStringSearchInterpolator setDelimiterSpecs( LinkedHashSet<String> specs ) { delimiters.clear(); for ( String spec : specs ) { if ( spec == null ) { continue; } delimiters.add( DelimiterSpecification.parse( spec ) ); } return this; } }