Coverage Report - org.codehaus.plexus.interpolation.RegexBasedInterpolator
 
Classes in this File Line Coverage Branch Coverage Complexity
RegexBasedInterpolator
67%
80/118
69%
36/52
2.455
 
 1  
 package org.codehaus.plexus.interpolation;
 2  
 
 3  
 /*
 4  
  * Copyright 2001-2008 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 java.util.ArrayList;
 20  
 import java.util.HashMap;
 21  
 import java.util.List;
 22  
 import java.util.Map;
 23  
 import java.util.WeakHashMap;
 24  
 import java.util.regex.Matcher;
 25  
 import java.util.regex.Pattern;
 26  
 
 27  
 import org.codehaus.plexus.interpolation.util.StringUtils;
 28  
 
 29  
 /**
 30  
  * Expansion of the original RegexBasedInterpolator, found in plexus-utils, this
 31  
  * interpolator provides options for setting custom prefix/suffix regex parts,
 32  
  * and includes a {@link RecursionInterceptor} parameter in its interpolate(..)
 33  
  * call, to allow the detection of cyclical expression references.
 34  
  *
 35  
  */
 36  
 public class RegexBasedInterpolator
 37  
     implements Interpolator
 38  
 {
 39  
 
 40  
     private String startRegex;
 41  
 
 42  
     private String endRegex;
 43  
     
 44  9
     private Map existingAnswers = new HashMap();
 45  
 
 46  9
     private List<ValueSource> valueSources = new ArrayList<ValueSource>();
 47  
     
 48  9
     private List<InterpolationPostProcessor> postProcessors = new ArrayList<InterpolationPostProcessor>();
 49  
     
 50  9
     private boolean reusePatterns = false;
 51  
     
 52  9
     private boolean cacheAnswers = false;
 53  
     
 54  
     public static final String DEFAULT_REGEXP = "\\$\\{(.+?)\\}";
 55  
     
 56  
     /**
 57  
      * the key is the regex the value is the Pattern 
 58  
      * At the class construction time the Map will contains the default Pattern
 59  
      */
 60  9
     private Map<String,Pattern> compiledPatterns = new WeakHashMap<String,Pattern>();
 61  
     
 62  
     /**
 63  
      * Setup a basic interpolator.
 64  
      * <p><b>NOTE:</b> You will have to call</p>
 65  
      * {@link RegexBasedInterpolator#addValueSource(ValueSource)} at least once
 66  
      * if you use this constructor!
 67  
      */
 68  
     public RegexBasedInterpolator()
 69  9
     {
 70  9
         compiledPatterns.put( DEFAULT_REGEXP, Pattern.compile( DEFAULT_REGEXP ) );
 71  9
     }
 72  
     
 73  
     /**
 74  
 
 75  
      * @param reusePatterns already compiled patterns will be reused
 76  
      */
 77  
     public RegexBasedInterpolator( boolean reusePatterns )
 78  
     {
 79  1
         this();
 80  1
         this.reusePatterns = reusePatterns;
 81  1
     }    
 82  
 
 83  
     /**
 84  
      * Setup an interpolator with no value sources, and the specified regex pattern
 85  
      * prefix and suffix in place of the default one.
 86  
      * <p><b>NOTE:</b> You will have to call
 87  
      * {@link RegexBasedInterpolator#addValueSource(ValueSource)} at least once
 88  
      * if you use this constructor!</p>
 89  
      *
 90  
      * @param startRegex start of the regular expression to use
 91  
      * @param endRegex end of the regular expression to use
 92  
      */
 93  
     public RegexBasedInterpolator (String startRegex, String endRegex)
 94  
     {
 95  2
         this();
 96  2
         this.startRegex = startRegex;
 97  2
         this.endRegex = endRegex;
 98  2
     }
 99  
 
 100  
     /**
 101  
      * Setup a basic interpolator with the specified list of value sources.
 102  
      *
 103  
      * @param valueSources The list of value sources to use
 104  
      */
 105  
     public RegexBasedInterpolator( List valueSources )
 106  
     {
 107  0
         this();
 108  0
         this.valueSources.addAll( valueSources );
 109  0
     }
 110  
 
 111  
     /**
 112  
      * Setup an interpolator with the specified value sources, and the specified
 113  
      * regex pattern prefix and suffix in place of the default one.
 114  
      *
 115  
      * @param startRegex start of the regular expression to use
 116  
      * @param endRegex end of the regular expression to use
 117  
      * @param valueSources The list of value sources to use
 118  
      */
 119  
     public RegexBasedInterpolator (String startRegex, String endRegex, List valueSources )
 120  
     {
 121  0
         this();
 122  0
         this.startRegex = startRegex;
 123  0
         this.endRegex = endRegex;
 124  0
         this.valueSources.addAll( valueSources );
 125  0
     }
 126  
 
 127  
     /**
 128  
      * {@inheritDoc}
 129  
      */
 130  
     public void addValueSource( ValueSource valueSource )
 131  
     {
 132  9
         valueSources.add( valueSource );
 133  9
     }
 134  
 
 135  
     /**
 136  
      * {@inheritDoc}
 137  
      */
 138  
     public void removeValuesSource( ValueSource valueSource )
 139  
     {
 140  0
         valueSources.remove( valueSource );
 141  0
     }
 142  
 
 143  
     /**
 144  
      * {@inheritDoc}
 145  
      */
 146  
     public void addPostProcessor( InterpolationPostProcessor postProcessor )
 147  
     {
 148  3
         postProcessors.add( postProcessor );
 149  3
     }
 150  
 
 151  
     /**
 152  
      * {@inheritDoc}
 153  
      */
 154  
     public void removePostProcessor( InterpolationPostProcessor postProcessor )
 155  
     {
 156  0
         postProcessors.remove( postProcessor  );
 157  0
     }
 158  
 
 159  
     /**
 160  
      * Attempt to resolve all expressions in the given input string, using the
 161  
      * given pattern to first trim an optional prefix from each expression. The
 162  
      * supplied recursion interceptor will provide protection from expression
 163  
      * cycles, ensuring that the input can be resolved or an exception is
 164  
      * thrown.
 165  
      *
 166  
      * @param input The input string to interpolate
 167  
      *
 168  
      * @param thisPrefixPattern An optional pattern that should be trimmed from
 169  
      *                          the start of any expressions found in the input.
 170  
      *
 171  
      * @param recursionInterceptor Used to protect the interpolation process
 172  
      *                             from expression cycles, and throw an
 173  
      *                             exception if one is detected.
 174  
      */
 175  
     public String interpolate( String input,
 176  
                                String thisPrefixPattern,
 177  
                                RecursionInterceptor recursionInterceptor )
 178  
         throws InterpolationException
 179  
     {
 180  400007
         if (input == null )
 181  
         {
 182  
             // return empty String to prevent NPE too
 183  1
             return "";
 184  
         }
 185  400006
         if ( recursionInterceptor == null )
 186  
         {
 187  400005
             recursionInterceptor = new SimpleRecursionInterceptor();
 188  
         }
 189  
 
 190  400006
         if ( thisPrefixPattern != null && thisPrefixPattern.length() == 0 )
 191  
         {
 192  400001
             thisPrefixPattern = null;
 193  
         }
 194  
 
 195  400006
         int realExprGroup = 2;
 196  
         Pattern expressionPattern;
 197  400006
         if ( startRegex != null || endRegex != null )
 198  
         {
 199  1
             if ( thisPrefixPattern == null )
 200  
             {
 201  0
                 expressionPattern = getPattern( startRegex + endRegex );
 202  0
                 realExprGroup = 1;
 203  
             }
 204  
             else
 205  
             {
 206  1
                 expressionPattern = getPattern( startRegex + thisPrefixPattern + endRegex );
 207  
             }
 208  
 
 209  
         }
 210  400005
         else if ( thisPrefixPattern != null )
 211  
         {
 212  3
             expressionPattern = getPattern( "\\$\\{(" + thisPrefixPattern + ")?(.+?)\\}" );
 213  
         }
 214  
         else
 215  
         {
 216  400002
             expressionPattern = getPattern( DEFAULT_REGEXP );
 217  400002
             realExprGroup = 1;
 218  
         }
 219  
 
 220  
         try
 221  
         {
 222  800011
             return interpolate( input, recursionInterceptor, expressionPattern, realExprGroup );
 223  
         }
 224  
         finally
 225  
         {
 226  400006
             if ( !cacheAnswers )
 227  
             {
 228  400006
                 clearAnswers();
 229  
             }
 230  
         }
 231  
     }
 232  
     
 233  
     private Pattern getPattern( String regExp )
 234  
     {
 235  400006
         if ( !reusePatterns )
 236  
         {
 237  200006
             return Pattern.compile( regExp );
 238  
         }
 239  
            
 240  
         Pattern pattern;
 241  200000
         synchronized( this )
 242  
         {
 243  200000
             pattern = compiledPatterns.get( regExp );
 244  
             
 245  200000
             if ( pattern != null )
 246  
             {
 247  200000
                 return pattern;
 248  
             }
 249  
 
 250  0
             pattern = Pattern.compile( regExp );
 251  0
             compiledPatterns.put( regExp, pattern );
 252  0
         }
 253  
         
 254  0
         return pattern;
 255  
     }
 256  
 
 257  
     /**
 258  
      * Entry point for recursive resolution of an expression and all of its
 259  
      * nested expressions.
 260  
      *
 261  
      * @todo Ensure unresolvable expressions don't trigger infinite recursion.
 262  
      */
 263  
     private String interpolate( String input,
 264  
                                 RecursionInterceptor recursionInterceptor,
 265  
                                 Pattern expressionPattern,
 266  
                                 int realExprGroup )
 267  
         throws InterpolationException
 268  
     {
 269  800013
         if (input == null )
 270  
         {
 271  
             // return empty String to prevent NPE too
 272  0
             return "";
 273  
         }        
 274  800013
         String result = input;
 275  
         
 276  800013
         Matcher matcher = expressionPattern.matcher( result );
 277  
 
 278  1200018
         while ( matcher.find() )
 279  
         {
 280  400008
             String wholeExpr = matcher.group( 0 );
 281  400008
             String realExpr = matcher.group( realExprGroup );
 282  
 
 283  400008
             if ( realExpr.startsWith( "." ) )
 284  
             {
 285  3
                 realExpr = realExpr.substring( 1 );
 286  
             }
 287  
 
 288  400008
             if ( recursionInterceptor.hasRecursiveExpression( realExpr ) )
 289  
             {
 290  1
                 throw new InterpolationCycleException( recursionInterceptor, realExpr, wholeExpr );
 291  
             }
 292  
 
 293  400007
             recursionInterceptor.expressionResolutionStarted( realExpr );
 294  
             try
 295  
             {
 296  400007
                 Object value = existingAnswers.get( realExpr );
 297  400007
                 for ( ValueSource vs : valueSources )
 298  
                 {
 299  400007
                    if (value != null) break;
 300  
 
 301  400007
                     value = vs.getValue( realExpr );
 302  400007
                 }
 303  
 
 304  400007
                 if ( value != null )
 305  
                 {
 306  400007
                     value =
 307  400007
                         interpolate( String.valueOf( value ), recursionInterceptor, expressionPattern, realExprGroup );
 308  
 
 309  400005
                     if ( postProcessors != null && !postProcessors.isEmpty() )
 310  
                     {
 311  400001
                         for ( InterpolationPostProcessor postProcessor : postProcessors )
 312  
                         {
 313  400001
                             Object newVal = postProcessor.execute( realExpr, value );
 314  400001
                             if ( newVal != null )
 315  
                             {
 316  400000
                                 value = newVal;
 317  400000
                                 break;
 318  
                             }
 319  1
                         }
 320  
                     }
 321  
 
 322  
                     // could use:
 323  
                     // result = matcher.replaceFirst( stringValue );
 324  
                     // but this could result in multiple lookups of stringValue, and replaceAll is not correct behaviour
 325  400005
                     result = StringUtils.replace( result, wholeExpr, String.valueOf( value ) );
 326  
 
 327  400005
                     matcher.reset( result );
 328  
                 }
 329  
             }
 330  
             finally
 331  
             {
 332  400007
                 recursionInterceptor.expressionResolutionFinished( realExpr );
 333  400005
             }
 334  400005
         }
 335  
         
 336  800010
         return result;
 337  
     }
 338  
 
 339  
     /**
 340  
      * Return any feedback messages and errors that were generated - but
 341  
      * suppressed - during the interpolation process. Since unresolvable
 342  
      * expressions will be left in the source string as-is, this feedback is
 343  
      * optional, and will only be useful for debugging interpolation problems.
 344  
      *
 345  
      * @return a {@link List} that may be interspersed with {@link String} and
 346  
      * {@link Throwable} instances.
 347  
      */
 348  
     public List getFeedback()
 349  
     {
 350  0
         List messages = new ArrayList();
 351  0
         for ( Object valueSource : valueSources )
 352  
         {
 353  0
             ValueSource vs = (ValueSource) valueSource;
 354  0
             List feedback = vs.getFeedback();
 355  0
             if ( feedback != null && !feedback.isEmpty() )
 356  
             {
 357  0
                 messages.addAll( feedback );
 358  
             }
 359  0
         }
 360  
 
 361  0
         return messages;
 362  
     }
 363  
 
 364  
     /**
 365  
      * Clear the feedback messages from previous interpolate(..) calls.
 366  
      */
 367  
     public void clearFeedback()
 368  
     {
 369  0
         for ( Object valueSource : valueSources )
 370  
         {
 371  0
             ValueSource vs = (ValueSource) valueSource;
 372  0
             vs.clearFeedback();
 373  0
         }
 374  0
     }
 375  
 
 376  
     /**
 377  
      * See {@link RegexBasedInterpolator#interpolate(String, String, RecursionInterceptor)}.
 378  
      * <p>
 379  
      * This method triggers the use of a {@link SimpleRecursionInterceptor}
 380  
      * instance for protection against expression cycles.</p>
 381  
      *
 382  
      * @param input The input string to interpolate
 383  
      *
 384  
      * @param thisPrefixPattern An optional pattern that should be trimmed from
 385  
      *                          the start of any expressions found in the input.
 386  
      */
 387  
     public String interpolate( String input,
 388  
                                String thisPrefixPattern )
 389  
         throws InterpolationException
 390  
     {
 391  400005
         return interpolate( input, thisPrefixPattern, null );
 392  
     }
 393  
 
 394  
     /**
 395  
      * See {@link RegexBasedInterpolator#interpolate(String, String, RecursionInterceptor)}.
 396  
      * <p>
 397  
      * This method triggers the use of a {@link SimpleRecursionInterceptor}
 398  
      * instance for protection against expression cycles. It also leaves empty the
 399  
      * expression prefix which would otherwise be trimmed from expressions. The
 400  
      * result is that any detected expression will be resolved as-is.</p>
 401  
      *
 402  
      * @param input The input string to interpolate
 403  
      */
 404  
     public String interpolate( String input )
 405  
         throws InterpolationException
 406  
     {
 407  1
         return interpolate( input, null, null );
 408  
     }
 409  
 
 410  
     /**
 411  
      * See {@link RegexBasedInterpolator#interpolate(String, String, RecursionInterceptor)}.
 412  
      * <p>
 413  
      * This method leaves empty the expression prefix which would otherwise be
 414  
      * trimmed from expressions. The result is that any detected expression will
 415  
      * be resolved as-is.</p>
 416  
      *
 417  
      * @param input The input string to interpolate
 418  
      *
 419  
      * @param recursionInterceptor Used to protect the interpolation process
 420  
      *                             from expression cycles, and throw an
 421  
      *                             exception if one is detected.
 422  
      */
 423  
     public String interpolate( String input,
 424  
                                RecursionInterceptor recursionInterceptor )
 425  
         throws InterpolationException
 426  
     {
 427  1
         return interpolate( input, null, recursionInterceptor );
 428  
     }
 429  
 
 430  
     public boolean isReusePatterns()
 431  
     {
 432  0
         return reusePatterns;
 433  
     }
 434  
 
 435  
     public void setReusePatterns( boolean reusePatterns )
 436  
     {
 437  0
         this.reusePatterns = reusePatterns;
 438  0
     }
 439  
 
 440  
     public boolean isCacheAnswers()
 441  
     {
 442  0
         return cacheAnswers;
 443  
     }
 444  
 
 445  
     public void setCacheAnswers( boolean cacheAnswers )
 446  
     {
 447  0
         this.cacheAnswers = cacheAnswers;
 448  0
     }
 449  
     
 450  
     public void clearAnswers()
 451  
     {
 452  400006
         existingAnswers.clear();
 453  400006
     }
 454  
 
 455  
 }