Coverage Report - org.codehaus.plexus.interpolation.multi.MultiDelimiterStringSearchInterpolator
 
Classes in this File Line Coverage Branch Coverage Complexity
MultiDelimiterStringSearchInterpolator
56 %
80/142
43 %
38/88
3,174
 
 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  4
     private Map existingAnswers = new HashMap();
 42  
 
 43  4
     private List<ValueSource> valueSources = new ArrayList<ValueSource>();
 44  
 
 45  4
     private List postProcessors = new ArrayList();
 46  
 
 47  4
     private boolean cacheAnswers = false;
 48  
 
 49  4
     private LinkedHashSet<DelimiterSpecification> delimiters = new LinkedHashSet<DelimiterSpecification>();
 50  
 
 51  
     private String escapeString;
 52  
 
 53  
     public MultiDelimiterStringSearchInterpolator()
 54  4
     {
 55  4
         delimiters.add( DelimiterSpecification.DEFAULT_SPEC );
 56  4
     }
 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  2
         addValueSource( vs );
 80  2
         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  4
         valueSources.add( valueSource );
 95  4
     }
 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  2
         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  4
             return interpolate( input, recursionInterceptor, new HashSet() );
 150  
         }
 151  
         finally
 152  
         {
 153  4
             if ( !cacheAnswers )
 154  
             {
 155  2
                 existingAnswers.clear();
 156  
             }
 157  
         }
 158  
     }
 159  
 
 160  
     private String interpolate( String input, RecursionInterceptor recursionInterceptor, Set<String> unresolvable )
 161  
         throws InterpolationException
 162  
     {
 163  28
         if ( input == null )
 164  
         {
 165  
             // return empty String to prevent NPE too
 166  0
             return "";
 167  
         }
 168  28
         StringBuilder result = new StringBuilder( input.length() * 2 );
 169  
 
 170  28
         String lastResult = input;
 171  28
         int tries = 0;
 172  
         do
 173  
         {
 174  40
             tries++;
 175  40
             if ( result.length() > 0 )
 176  
             {
 177  12
                 lastResult = result.toString();
 178  12
                 result.setLength( 0 );
 179  
             }
 180  
             
 181  40
             int startIdx = -1;
 182  40
             int endIdx = -1;
 183  
             
 184  40
             DelimiterSpecification selectedSpec = null;
 185  64
             while( ( selectedSpec = select( input, endIdx ) ) != null )
 186  
             {
 187  24
                 String startExpr = selectedSpec.getBegin();
 188  24
                 String endExpr = selectedSpec.getEnd();
 189  
                 
 190  24
                 startIdx = selectedSpec.getNextStartIndex();
 191  24
                 result.append( input, endIdx + 1, startIdx );
 192  
 
 193  24
                 endIdx = input.indexOf( endExpr, startIdx + 1 );
 194  24
                 if ( endIdx < 0 )
 195  
                 {
 196  0
                     break;
 197  
                 }
 198  
 
 199  24
                 String wholeExpr = input.substring( startIdx, endIdx + endExpr.length() );
 200  24
                 String realExpr = wholeExpr.substring( startExpr.length(), wholeExpr.length() - endExpr.length() );
 201  
 
 202  24
                 if ( startIdx >= 0 && escapeString != null && escapeString.length() > 0 )
 203  
                 {
 204  0
                     int startEscapeIdx = startIdx == 0 ? 0 : startIdx - escapeString.length();
 205  0
                     if ( startEscapeIdx >= 0 )
 206  
                     {
 207  0
                         String escape = input.substring( startEscapeIdx, startIdx );
 208  0
                         if ( escape != null && escapeString.equals( escape ) )
 209  
                         {
 210  0
                             result.append( wholeExpr );
 211  0
                             result.replace( startEscapeIdx, startEscapeIdx + escapeString.length(), "" );
 212  0
                             continue;
 213  
                         }
 214  
                     }
 215  
                 }
 216  
 
 217  24
                 boolean resolved = false;
 218  24
                 if ( !unresolvable.contains( wholeExpr ) )
 219  
                 {
 220  24
                     if ( realExpr.startsWith( "." ) )
 221  
                     {
 222  0
                         realExpr = realExpr.substring( 1 );
 223  
                     }
 224  
 
 225  24
                     if ( recursionInterceptor.hasRecursiveExpression( realExpr ) )
 226  
                     {
 227  0
                         throw new InterpolationCycleException( recursionInterceptor, realExpr, wholeExpr );
 228  
                     }
 229  
 
 230  24
                     recursionInterceptor.expressionResolutionStarted( realExpr );
 231  
 
 232  24
                     Object value = existingAnswers.get( realExpr );
 233  24
                     Object bestAnswer = null;
 234  24
                     for ( ValueSource vs : valueSources )
 235  
                     {
 236  24
                         if (value != null ) break;
 237  
 
 238  24
                         value = vs.getValue( realExpr );
 239  
 
 240  24
                         if ( value != null && value.toString().contains( wholeExpr ) )
 241  
                         {
 242  0
                             bestAnswer = value;
 243  0
                             value = null;
 244  
                         }
 245  24
                     }
 246  
 
 247  
                     // this is the simplest recursion check to catch exact recursion
 248  
                     // (non synonym), and avoid the extra effort of more string
 249  
                     // searching.
 250  24
                     if ( value == null && bestAnswer != null )
 251  
                     {
 252  0
                         throw new InterpolationCycleException( recursionInterceptor, realExpr, wholeExpr );
 253  
                     }
 254  
 
 255  24
                     if ( value != null )
 256  
                     {
 257  24
                         value = interpolate( String.valueOf( value ), recursionInterceptor, unresolvable );
 258  
 
 259  24
                         if ( postProcessors != null && !postProcessors.isEmpty() )
 260  
                         {
 261  0
                             for ( Object postProcessor1 : postProcessors )
 262  
                             {
 263  0
                                 InterpolationPostProcessor postProcessor = (InterpolationPostProcessor) postProcessor1;
 264  0
                                 Object newVal = postProcessor.execute( realExpr, value );
 265  0
                                 if ( newVal != null )
 266  
                                 {
 267  0
                                     value = newVal;
 268  0
                                     break;
 269  
                                 }
 270  0
                             }
 271  
                         }
 272  
 
 273  
                         // could use:
 274  
                         // result = matcher.replaceFirst( stringValue );
 275  
                         // but this could result in multiple lookups of stringValue, and replaceAll is not correct behaviour
 276  24
                         result.append( String.valueOf( value ) );
 277  24
                         resolved = true;
 278  
                     }
 279  
                     else
 280  
                     {
 281  0
                         unresolvable.add( wholeExpr );
 282  
                     }
 283  
 
 284  24
                     recursionInterceptor.expressionResolutionFinished( realExpr );
 285  
                 }
 286  
 
 287  24
                 if ( !resolved )
 288  
                 {
 289  0
                     result.append( wholeExpr );
 290  
                 }
 291  
 
 292  24
                 if ( endIdx > -1 )
 293  
                 {
 294  24
                     endIdx += endExpr.length() - 1;
 295  
                 }
 296  24
             }
 297  
 
 298  40
             if ( endIdx == -1 && startIdx > -1 )
 299  
             {
 300  0
                 result.append( input, startIdx, input.length() );
 301  
             }
 302  40
             else if ( endIdx < input.length() )
 303  
             {
 304  40
                 result.append( input, endIdx + 1, input.length() );
 305  
             }
 306  
         }
 307  40
         while( !lastResult.equals( result.toString() ) && tries < MAX_TRIES );
 308  
 
 309  28
         return result.toString();
 310  
     }
 311  
 
 312  
     private DelimiterSpecification select( String input, int lastEndIdx )
 313  
     {
 314  64
         DelimiterSpecification selected = null;
 315  
 
 316  64
         for ( DelimiterSpecification spec : delimiters )
 317  
         {
 318  128
             spec.clearNextStart();
 319  
 
 320  128
             if ( selected == null )
 321  
             {
 322  116
                 int idx = input.indexOf( spec.getBegin(), lastEndIdx + 1 );
 323  116
                 if ( idx > -1 )
 324  
                 {
 325  24
                     spec.setNextStartIndex( idx );
 326  24
                     selected = spec;
 327  
                 }
 328  
             }
 329  128
         }
 330  
         
 331  64
         return selected;
 332  
     }
 333  
 
 334  
     /**
 335  
      * Return any feedback messages and errors that were generated - but suppressed - during the interpolation process.
 336  
      * Since unresolvable expressions will be left in the source string as-is, this feedback is optional, and will only
 337  
      * be useful for debugging interpolation problems.
 338  
      * 
 339  
      * @return a {@link List} that may be interspersed with {@link String} and {@link Throwable} instances.
 340  
      */
 341  
     public List getFeedback()
 342  
     {
 343  0
         List messages = new ArrayList();
 344  0
         for ( ValueSource vs : valueSources )
 345  
         {
 346  0
             List feedback = vs.getFeedback();
 347  0
             if ( feedback != null && !feedback.isEmpty() )
 348  
             {
 349  0
                 messages.addAll( feedback );
 350  
             }
 351  0
         }
 352  
 
 353  0
         return messages;
 354  
     }
 355  
 
 356  
     /**
 357  
      * Clear the feedback messages from previous interpolate(..) calls.
 358  
      */
 359  
     public void clearFeedback()
 360  
     {
 361  0
         for ( ValueSource vs : valueSources )
 362  
         {
 363  0
             vs.clearFeedback();
 364  0
         }
 365  0
     }
 366  
 
 367  
     public boolean isCacheAnswers()
 368  
     {
 369  0
         return cacheAnswers;
 370  
     }
 371  
 
 372  
     public void setCacheAnswers( boolean cacheAnswers )
 373  
     {
 374  2
         this.cacheAnswers = cacheAnswers;
 375  2
     }
 376  
 
 377  
     public void clearAnswers()
 378  
     {
 379  0
         existingAnswers.clear();
 380  0
     }
 381  
 
 382  
     public String getEscapeString()
 383  
     {
 384  0
         return escapeString;
 385  
     }
 386  
 
 387  
     public void setEscapeString( String escapeString )
 388  
     {
 389  0
         this.escapeString = escapeString;
 390  0
     }
 391  
     
 392  
     public MultiDelimiterStringSearchInterpolator setDelimiterSpecs( LinkedHashSet<String> specs )
 393  
     {
 394  0
         delimiters.clear();
 395  0
         for ( String spec : specs )
 396  
         {
 397  0
             if ( spec == null )
 398  
             {
 399  0
                 continue;
 400  
             }
 401  0
             delimiters.add( DelimiterSpecification.parse( spec ) );
 402  0
         }
 403  
         
 404  0
         return this;
 405  
     }
 406  
 
 407  
 }