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