Coverage Report - org.codehaus.plexus.interpolation.multi.MultiDelimiterStringSearchInterpolator
 
Classes in this File Line Coverage Branch Coverage Complexity
MultiDelimiterStringSearchInterpolator
63%
93/146
53%
48/90
3.125
 
 1  
 package org.codehaus.plexus.interpolation.multi;
 2  
 
 3  
 /*
 4  
  * Copyright 2001-2009 Codehaus Foundation.
 5  
  *
 6  
  * Licensed under the Apache License, Version 2.0 (the "License");
 7  
  * you may not use this file except in compliance with the License.
 8  
  * You may obtain a copy of the License at
 9  
  *
 10  
  *      http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing, software
 13  
  * distributed under the License is distributed on an "AS IS" BASIS,
 14  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  
  * See the License for the specific language governing permissions and
 16  
  * limitations under the License.
 17  
  */
 18  
 
 19  
 import org.codehaus.plexus.interpolation.InterpolationCycleException;
 20  
 import org.codehaus.plexus.interpolation.InterpolationException;
 21  
 import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
 22  
 import org.codehaus.plexus.interpolation.Interpolator;
 23  
 import org.codehaus.plexus.interpolation.RecursionInterceptor;
 24  
 import org.codehaus.plexus.interpolation.SimpleRecursionInterceptor;
 25  
 import org.codehaus.plexus.interpolation.ValueSource;
 26  
 
 27  
 import java.util.ArrayList;
 28  
 import java.util.HashMap;
 29  
 import java.util.HashSet;
 30  
 import java.util.LinkedHashSet;
 31  
 import java.util.List;
 32  
 import java.util.Map;
 33  
 import java.util.Set;
 34  
 
 35  
 public class MultiDelimiterStringSearchInterpolator
 36  
     implements Interpolator
 37  
 {
 38  
 
 39  
     private static final int MAX_TRIES = 10;
 40  
     
 41  7
     private Map existingAnswers = new HashMap();
 42  
 
 43  7
     private List<ValueSource> valueSources = new ArrayList<ValueSource>();
 44  
 
 45  7
     private List postProcessors = new ArrayList();
 46  
 
 47  7
     private boolean cacheAnswers = false;
 48  
 
 49  7
     private LinkedHashSet<DelimiterSpecification> delimiters = new LinkedHashSet<DelimiterSpecification>();
 50  
 
 51  
     private String escapeString;
 52  
 
 53  
     public MultiDelimiterStringSearchInterpolator()
 54  7
     {
 55  7
         delimiters.add( DelimiterSpecification.DEFAULT_SPEC );
 56  7
     }
 57  
     
 58  
     public MultiDelimiterStringSearchInterpolator addDelimiterSpec( String delimiterSpec )
 59  
     {
 60  6
         if ( delimiterSpec == null )
 61  
         {
 62  0
             return this;
 63  
         }
 64  6
         delimiters.add( DelimiterSpecification.parse( delimiterSpec ) );
 65  6
         return this;
 66  
     }
 67  
     
 68  
     public boolean removeDelimiterSpec( String delimiterSpec )
 69  
     {
 70  0
         if ( delimiterSpec == null )
 71  
         {
 72  0
             return false;
 73  
         }        
 74  0
         return delimiters.remove( DelimiterSpecification.parse( delimiterSpec ) );
 75  
     }
 76  
     
 77  
     public MultiDelimiterStringSearchInterpolator withValueSource( ValueSource vs )
 78  
     {
 79  5
         addValueSource( vs );
 80  5
         return this;
 81  
     }
 82  
     
 83  
     public MultiDelimiterStringSearchInterpolator withPostProcessor( InterpolationPostProcessor postProcessor )
 84  
     {
 85  0
         addPostProcessor( postProcessor );
 86  0
         return this;
 87  
     }
 88  
 
 89  
     /**
 90  
      * {@inheritDoc}
 91  
      */
 92  
     public void addValueSource( ValueSource valueSource )
 93  
     {
 94  7
         valueSources.add( valueSource );
 95  7
     }
 96  
 
 97  
     /**
 98  
      * {@inheritDoc}
 99  
      */
 100  
     public void removeValuesSource( ValueSource valueSource )
 101  
     {
 102  0
         valueSources.remove( valueSource );
 103  0
     }
 104  
 
 105  
     /**
 106  
      * {@inheritDoc}
 107  
      */
 108  
     public void addPostProcessor( InterpolationPostProcessor postProcessor )
 109  
     {
 110  0
         postProcessors.add( postProcessor );
 111  0
     }
 112  
 
 113  
     /**
 114  
      * {@inheritDoc}
 115  
      */
 116  
     public void removePostProcessor( InterpolationPostProcessor postProcessor )
 117  
     {
 118  0
         postProcessors.remove( postProcessor );
 119  0
     }
 120  
 
 121  
     public String interpolate( String input, String thisPrefixPattern )
 122  
         throws InterpolationException
 123  
     {
 124  0
         return interpolate( input, new SimpleRecursionInterceptor() );
 125  
     }
 126  
 
 127  
     public String interpolate( String input, String thisPrefixPattern, RecursionInterceptor recursionInterceptor )
 128  
         throws InterpolationException
 129  
     {
 130  0
         return interpolate( input, recursionInterceptor );
 131  
     }
 132  
 
 133  
     public String interpolate( String input )
 134  
         throws InterpolationException
 135  
     {
 136  5
         return interpolate( input, new SimpleRecursionInterceptor() );
 137  
     }
 138  
 
 139  
     /**
 140  
      * Entry point for recursive resolution of an expression and all of its nested expressions.
 141  
      * 
 142  
      * TODO: Ensure unresolvable expressions don't trigger infinite recursion.
 143  
      */
 144  
     public String interpolate( String input, RecursionInterceptor recursionInterceptor )
 145  
         throws InterpolationException
 146  
     {
 147  
         try
 148  
         {
 149  14
             return interpolate( input, recursionInterceptor, new HashSet() );
 150  
         }
 151  
         finally
 152  
         {
 153  7
             if ( !cacheAnswers )
 154  
             {
 155  5
                 existingAnswers.clear();
 156  
             }
 157  
         }
 158  
     }
 159  
 
 160  
     private String interpolate( String input, RecursionInterceptor recursionInterceptor, Set<String> unresolvable )
 161  
         throws InterpolationException
 162  
     {
 163  41
         if ( input == null )
 164  
         {
 165  
             // return empty String to prevent NPE too
 166  0
             return "";
 167  
         }
 168  41
         StringBuilder result = new StringBuilder( input.length() * 2 );
 169  
 
 170  41
         String lastResult = input;
 171  41
         int tries = 0;
 172  
         do
 173  
         {
 174  62
             tries++;
 175  62
             if ( result.length() > 0 )
 176  
             {
 177  21
                 lastResult = result.toString();
 178  21
                 result.setLength( 0 );
 179  
             }
 180  
             
 181  62
             int startIdx = -1;
 182  62
             int endIdx = -1;
 183  
             
 184  62
             DelimiterSpecification selectedSpec = null;
 185  120
             while( ( selectedSpec = select( input, endIdx ) ) != null )
 186  
             {
 187  58
                 String startExpr = selectedSpec.getBegin();
 188  58
                 String endExpr = selectedSpec.getEnd();
 189  
                 
 190  58
                 startIdx = selectedSpec.getNextStartIndex();
 191  58
                 result.append( input, endIdx + 1, startIdx );
 192  
 
 193  58
                 endIdx = input.indexOf( endExpr, startIdx + 1 );
 194  58
                 if ( endIdx < 0 )
 195  
                 {
 196  0
                     break;
 197  
                 }
 198  
 
 199  58
                 String wholeExpr = input.substring( startIdx, endIdx + endExpr.length() );
 200  58
                 String realExpr = wholeExpr.substring( startExpr.length(), wholeExpr.length() - endExpr.length() );
 201  
 
 202  58
                 if ( startIdx >= 0 && escapeString != null && escapeString.length() > 0 )
 203  
                 {
 204  34
                     int startEscapeIdx = ( startIdx == 0 ) ? 0 : startIdx - escapeString.length();
 205  34
                     if ( startEscapeIdx >= 0 )
 206  
                     {
 207  34
                         String escape = input.substring( startEscapeIdx, startIdx );
 208  34
                         if ( escape != null && escapeString.equals( escape ) )
 209  
                         {
 210  24
                             result.append( wholeExpr );
 211  24
                             if ( startEscapeIdx > 0 )
 212  
                             {
 213  20
                                 --startEscapeIdx;
 214  
                             }
 215  24
                             result.replace( startEscapeIdx, startEscapeIdx + escapeString.length(), "" );
 216  24
                             continue;
 217  
                         }
 218  
                     }
 219  
                 }
 220  
 
 221  34
                 boolean resolved = false;
 222  34
                 if ( !unresolvable.contains( wholeExpr ) )
 223  
                 {
 224  34
                     if ( realExpr.startsWith( "." ) )
 225  
                     {
 226  0
                         realExpr = realExpr.substring( 1 );
 227  
                     }
 228  
 
 229  34
                     if ( recursionInterceptor.hasRecursiveExpression( realExpr ) )
 230  
                     {
 231  0
                         throw new InterpolationCycleException( recursionInterceptor, realExpr, wholeExpr );
 232  
                     }
 233  
 
 234  34
                     recursionInterceptor.expressionResolutionStarted( realExpr );
 235  
 
 236  34
                     Object value = existingAnswers.get( realExpr );
 237  34
                     Object bestAnswer = null;
 238  34
                     for ( ValueSource vs : valueSources )
 239  
                     {
 240  34
                         if (value != null ) break;
 241  
 
 242  34
                         value = vs.getValue( realExpr );
 243  
 
 244  34
                         if ( value != null && value.toString().contains( wholeExpr ) )
 245  
                         {
 246  0
                             bestAnswer = value;
 247  0
                             value = null;
 248  
                         }
 249  34
                     }
 250  
 
 251  
                     // this is the simplest recursion check to catch exact recursion
 252  
                     // (non synonym), and avoid the extra effort of more string
 253  
                     // searching.
 254  34
                     if ( value == null && bestAnswer != null )
 255  
                     {
 256  0
                         throw new InterpolationCycleException( recursionInterceptor, realExpr, wholeExpr );
 257  
                     }
 258  
 
 259  34
                     if ( value != null )
 260  
                     {
 261  34
                         value = interpolate( String.valueOf( value ), recursionInterceptor, unresolvable );
 262  
 
 263  34
                         if ( postProcessors != null && !postProcessors.isEmpty() )
 264  
                         {
 265  0
                             for ( Object postProcessor1 : postProcessors )
 266  
                             {
 267  0
                                 InterpolationPostProcessor postProcessor = (InterpolationPostProcessor) postProcessor1;
 268  0
                                 Object newVal = postProcessor.execute( realExpr, value );
 269  0
                                 if ( newVal != null )
 270  
                                 {
 271  0
                                     value = newVal;
 272  0
                                     break;
 273  
                                 }
 274  0
                             }
 275  
                         }
 276  
 
 277  
                         // could use:
 278  
                         // result = matcher.replaceFirst( stringValue );
 279  
                         // but this could result in multiple lookups of stringValue, and replaceAll is not correct behaviour
 280  34
                         result.append( String.valueOf( value ) );
 281  34
                         resolved = true;
 282  
                     }
 283  
                     else
 284  
                     {
 285  0
                         unresolvable.add( wholeExpr );
 286  
                     }
 287  
 
 288  34
                     recursionInterceptor.expressionResolutionFinished( realExpr );
 289  
                 }
 290  
 
 291  34
                 if ( !resolved )
 292  
                 {
 293  0
                     result.append( wholeExpr );
 294  
                 }
 295  
 
 296  34
                 if ( endIdx > -1 )
 297  
                 {
 298  34
                     endIdx += endExpr.length() - 1;
 299  
                 }
 300  34
             }
 301  
 
 302  62
             if ( endIdx == -1 && startIdx > -1 )
 303  
             {
 304  0
                 result.append( input, startIdx, input.length() );
 305  
             }
 306  62
             else if ( endIdx < input.length() )
 307  
             {
 308  62
                 result.append( input, endIdx + 1, input.length() );
 309  
             }
 310  
         }
 311  62
         while( !lastResult.equals( result.toString() ) && tries < MAX_TRIES );
 312  
 
 313  41
         return result.toString();
 314  
     }
 315  
 
 316  
     private DelimiterSpecification select( String input, int lastEndIdx )
 317  
     {
 318  120
         DelimiterSpecification selected = null;
 319  
 
 320  120
         for ( DelimiterSpecification spec : delimiters )
 321  
         {
 322  184
             spec.clearNextStart();
 323  
 
 324  184
             if ( selected == null )
 325  
             {
 326  172
                 int idx = input.indexOf( spec.getBegin(), lastEndIdx + 1 );
 327  172
                 if ( idx > -1 )
 328  
                 {
 329  58
                     spec.setNextStartIndex( idx );
 330  58
                     selected = spec;
 331  
                 }
 332  
             }
 333  184
         }
 334  
         
 335  120
         return selected;
 336  
     }
 337  
 
 338  
     /**
 339  
      * Return any feedback messages and errors that were generated - but suppressed - during the interpolation process.
 340  
      * Since unresolvable expressions will be left in the source string as-is, this feedback is optional, and will only
 341  
      * be useful for debugging interpolation problems.
 342  
      * 
 343  
      * @return a {@link List} that may be interspersed with {@link String} and {@link Throwable} instances.
 344  
      */
 345  
     public List getFeedback()
 346  
     {
 347  0
         List messages = new ArrayList();
 348  0
         for ( ValueSource vs : valueSources )
 349  
         {
 350  0
             List feedback = vs.getFeedback();
 351  0
             if ( feedback != null && !feedback.isEmpty() )
 352  
             {
 353  0
                 messages.addAll( feedback );
 354  
             }
 355  0
         }
 356  
 
 357  0
         return messages;
 358  
     }
 359  
 
 360  
     /**
 361  
      * Clear the feedback messages from previous interpolate(..) calls.
 362  
      */
 363  
     public void clearFeedback()
 364  
     {
 365  0
         for ( ValueSource vs : valueSources )
 366  
         {
 367  0
             vs.clearFeedback();
 368  0
         }
 369  0
     }
 370  
 
 371  
     public boolean isCacheAnswers()
 372  
     {
 373  0
         return cacheAnswers;
 374  
     }
 375  
 
 376  
     public void setCacheAnswers( boolean cacheAnswers )
 377  
     {
 378  2
         this.cacheAnswers = cacheAnswers;
 379  2
     }
 380  
 
 381  
     public void clearAnswers()
 382  
     {
 383  0
         existingAnswers.clear();
 384  0
     }
 385  
 
 386  
     public String getEscapeString()
 387  
     {
 388  0
         return escapeString;
 389  
     }
 390  
 
 391  
     public void setEscapeString( String escapeString )
 392  
     {
 393  2
         this.escapeString = escapeString;
 394  2
     }
 395  
 
 396  
     public MultiDelimiterStringSearchInterpolator escapeString( String escapeString )
 397  
     {
 398  1
         this.escapeString = escapeString;
 399  1
         return this;
 400  
     }
 401  
     
 402  
     public MultiDelimiterStringSearchInterpolator setDelimiterSpecs( LinkedHashSet<String> specs )
 403  
     {
 404  0
         delimiters.clear();
 405  0
         for ( String spec : specs )
 406  
         {
 407  0
             if ( spec == null )
 408  
             {
 409  0
                 continue;
 410  
             }
 411  0
             delimiters.add( DelimiterSpecification.parse( spec ) );
 412  0
         }
 413  
         
 414  0
         return this;
 415  
     }
 416  
 
 417  
 }