Coverage Report - org.codehaus.plexus.interpolation.multi.MultiDelimiterInterpolatorFilterReader
 
Classes in this File Line Coverage Branch Coverage Complexity
MultiDelimiterInterpolatorFilterReader
62%
94/151
56%
60/106
4.824
 
 1  
 /*
 2  
  * The Apache Software License, Version 1.1
 3  
  *
 4  
  * Copyright (c) 2002-2003 The Apache Software Foundation.  All rights
 5  
  * reserved.
 6  
  *
 7  
  * Redistribution and use in source and binary forms, with or without
 8  
  * modification, are permitted provided that the following conditions
 9  
  * are met:
 10  
  *
 11  
  * 1. Redistributions of source code must retain the above copyright
 12  
  *    notice, this list of conditions and the following disclaimer.
 13  
  *
 14  
  * 2. Redistributions in binary form must reproduce the above copyright
 15  
  *    notice, this list of conditions and the following disclaimer in
 16  
  *    the documentation and/or other materials provided with the
 17  
  *    distribution.
 18  
  *
 19  
  * 3. The end-user documentation included with the redistribution, if
 20  
  *    any, must include the following acknowlegement:
 21  
  *       "This product includes software developed by the
 22  
  *        Apache Software Foundation (http://www.codehaus.org/)."
 23  
  *    Alternately, this acknowlegement may appear in the software itself,
 24  
  *    if and wherever such third-party acknowlegements normally appear.
 25  
  *
 26  
  * 4. The names "Ant" and "Apache Software
 27  
  *    Foundation" must not be used to endorse or promote products derived
 28  
  *    from this software without prior written permission. For written
 29  
  *    permission, please contact codehaus@codehaus.org.
 30  
  *
 31  
  * 5. Products derived from this software may not be called "Apache"
 32  
  *    nor may "Apache" appear in their names without prior written
 33  
  *    permission of the Apache Group.
 34  
  *
 35  
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 36  
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 37  
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 38  
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 39  
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 40  
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 41  
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 42  
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 43  
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 44  
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 45  
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 46  
  * SUCH DAMAGE.
 47  
  * ====================================================================
 48  
  *
 49  
  * This software consists of voluntary contributions made by many
 50  
  * individuals on behalf of the Apache Software Foundation.  For more
 51  
  * information on the Apache Software Foundation, please see
 52  
  * <http://www.codehaus.org/>.
 53  
  */
 54  
 
 55  
 package org.codehaus.plexus.interpolation.multi;
 56  
 
 57  
 import org.codehaus.plexus.interpolation.InterpolationException;
 58  
 import org.codehaus.plexus.interpolation.Interpolator;
 59  
 import org.codehaus.plexus.interpolation.RecursionInterceptor;
 60  
 import org.codehaus.plexus.interpolation.SimpleRecursionInterceptor;
 61  
 
 62  
 import java.io.FilterReader;
 63  
 import java.io.IOException;
 64  
 import java.io.Reader;
 65  
 import java.util.LinkedHashSet;
 66  
 
 67  
 /**
 68  
  * A FilterReader implementation, that works with Interpolator interface instead of it's own interpolation
 69  
  * implementation. This implementation is heavily based on org.codehaus.plexus.util.InterpolationFilterReader.
 70  
  *
 71  
  * @author cstamas
 72  
  */
 73  
 public class MultiDelimiterInterpolatorFilterReader
 74  
     extends FilterReader
 75  
 {
 76  
 
 77  
     /** Interpolator used to interpolate */
 78  
     private Interpolator interpolator;
 79  
     
 80  
     /**
 81  
      * @since 1.12
 82  
      */
 83  
     private RecursionInterceptor recursionInterceptor;
 84  
 
 85  
     /** replacement text from a token */
 86  16
     private String replaceData = null;
 87  
 
 88  
     /** Index into replacement data */
 89  16
     private int replaceIndex = -1;
 90  
 
 91  
     /** Index into previous data */
 92  16
     private int previousIndex = -1;
 93  
 
 94  
     /** Default begin token. */
 95  
     public static final String DEFAULT_BEGIN_TOKEN = "${";
 96  
 
 97  
     /** Default end token. */
 98  
     public static final String DEFAULT_END_TOKEN = "}";
 99  
     
 100  
     /** true by default to preserve backward comp */
 101  16
     private boolean interpolateWithPrefixPattern = true;
 102  
 
 103  
     private String escapeString;
 104  
     
 105  16
     private boolean useEscape = false;
 106  
     
 107  
     /** if true escapeString will be preserved \{foo} -> \{foo} */
 108  16
     private boolean preserveEscapeString = false;
 109  
     
 110  16
     private LinkedHashSet<DelimiterSpecification> delimiters = new LinkedHashSet<DelimiterSpecification>();
 111  
     
 112  
     private DelimiterSpecification currentSpec;
 113  
 
 114  
     private String beginToken;
 115  
 
 116  
     private String originalBeginToken;
 117  
 
 118  
     private String endToken;
 119  
     
 120  
     /**
 121  
      * this constructor use default begin token ${ and default end token } 
 122  
      * @param in reader to use
 123  
      * @param interpolator interpolator instance to use
 124  
      */
 125  
     public MultiDelimiterInterpolatorFilterReader( Reader in, Interpolator interpolator )
 126  
     {
 127  14
         this( in, interpolator, new SimpleRecursionInterceptor() );
 128  14
     }
 129  
     
 130  
     /**
 131  
      * @param in reader to use
 132  
      * @param interpolator interpolator instance to use
 133  
      * @param ri The {@link RecursionInterceptor} to use to prevent recursive expressions.
 134  
      * @since 1.12
 135  
      */
 136  
     public MultiDelimiterInterpolatorFilterReader( Reader in, Interpolator interpolator, RecursionInterceptor ri )
 137  
     {
 138  16
         super( in );
 139  
 
 140  16
         this.interpolator = interpolator;
 141  
         
 142  
         // always cache answers, since we'll be sending in pure expressions, not mixed text.
 143  16
         this.interpolator.setCacheAnswers( true );
 144  
         
 145  16
         recursionInterceptor = ri;
 146  
         
 147  16
         delimiters.add( DelimiterSpecification.DEFAULT_SPEC );
 148  16
     }    
 149  
 
 150  
     public MultiDelimiterInterpolatorFilterReader addDelimiterSpec( String delimiterSpec )
 151  
     {
 152  8
         if ( delimiterSpec == null )
 153  
         {
 154  0
             return this;
 155  
         }        
 156  8
         delimiters.add( DelimiterSpecification.parse( delimiterSpec ) );
 157  8
         return this;
 158  
     }
 159  
     
 160  
     public boolean removeDelimiterSpec( String delimiterSpec )
 161  
     {
 162  0
         if ( delimiterSpec == null )
 163  
         {
 164  0
             return false;
 165  
         }        
 166  0
         return delimiters.remove( DelimiterSpecification.parse( delimiterSpec ) );
 167  
     }
 168  
     
 169  
     public MultiDelimiterInterpolatorFilterReader setDelimiterSpecs( LinkedHashSet<String> specs )
 170  
     {
 171  0
         delimiters.clear();
 172  0
         for ( String spec : specs )
 173  
         {
 174  0
             if ( spec == null )
 175  
             {
 176  0
                 continue;
 177  
             }
 178  0
             delimiters.add( DelimiterSpecification.parse( spec ) );
 179  0
         }
 180  
         
 181  0
         return this;
 182  
     }
 183  
     
 184  
     /**
 185  
      * Skips characters. This method will block until some characters are available, an I/O error occurs, or the end of
 186  
      * the stream is reached.
 187  
      *
 188  
      * @param n The number of characters to skip
 189  
      * @return the number of characters actually skipped
 190  
      * @exception IllegalArgumentException If <code>n</code> is negative.
 191  
      * @exception IOException If an I/O error occurs
 192  
      */
 193  
     public long skip( long n )
 194  
         throws IOException
 195  
     {
 196  0
         if ( n < 0L )
 197  
         {
 198  0
             throw new IllegalArgumentException( "skip value is negative" );
 199  
         }
 200  
 
 201  0
         for ( long i = 0; i < n; i++ )
 202  
         {
 203  0
             if ( read() == -1 )
 204  
             {
 205  0
                 return i;
 206  
             }
 207  
         }
 208  0
         return n;
 209  
     }
 210  
 
 211  
     /**
 212  
      * Reads characters into a portion of an array. This method will block until some input is available, an I/O error
 213  
      * occurs, or the end of the stream is reached.
 214  
      *
 215  
      * @param cbuf Destination buffer to write characters to. Must not be <code>null</code>.
 216  
      * @param off Offset at which to start storing characters.
 217  
      * @param len Maximum number of characters to read.
 218  
      * @return the number of characters read, or -1 if the end of the stream has been reached
 219  
      * @exception IOException If an I/O error occurs
 220  
      */
 221  
     public int read( char cbuf[], int off, int len )
 222  
         throws IOException
 223  
     {
 224  337
         for ( int i = 0; i < len; i++ )
 225  
         {
 226  337
             int ch = read();
 227  337
             if ( ch == -1 )
 228  
             {
 229  32
                 if ( i == 0 )
 230  
                 {
 231  16
                     return -1;
 232  
                 }
 233  
                 else
 234  
                 {
 235  16
                     return i;
 236  
                 }
 237  
             }
 238  305
             cbuf[off + i] = (char) ch;
 239  
         }
 240  0
         return len;
 241  
     }
 242  
 
 243  
     /**
 244  
      * Returns the next character in the filtered stream, replacing tokens from the original stream.
 245  
      *
 246  
      * @return the next character in the resulting stream, or -1 if the end of the resulting stream has been reached
 247  
      * @exception IOException if the underlying stream throws an IOException during reading
 248  
      */
 249  
     public int read()
 250  
         throws IOException
 251  
     {
 252  359
         if ( replaceIndex != -1 && replaceIndex < replaceData.length() )
 253  
         {
 254  187
             int ch = replaceData.charAt( replaceIndex++ );
 255  187
             if ( replaceIndex >= replaceData.length() )
 256  
             {
 257  25
                 replaceIndex = -1;
 258  
             }
 259  187
             return ch;
 260  
         }
 261  
 
 262  172
         int ch = -1;
 263  172
         if ( previousIndex != -1 && previousIndex < this.endToken.length() )
 264  
         {
 265  0
             ch = this.endToken.charAt( previousIndex++ );
 266  
         }
 267  
         else
 268  
         {
 269  172
             ch = in.read();
 270  
         }
 271  
         
 272  172
         boolean inEscape = false;
 273  
         
 274  172
         if ( ( inEscape = ( useEscape && ch == escapeString.charAt( 0 ) ) ) || reselectDelimiterSpec( ch ) )
 275  
         {
 276  25
             StringBuilder key = new StringBuilder( );
 277  
 
 278  25
             key.append( (char) ch );
 279  
             
 280  
             // this will happen when we're using an escape string, and ONLY then.
 281  25
             boolean atEnd = false;
 282  
 
 283  25
             if ( inEscape )
 284  
             {
 285  5
                 for( int i = 0; i < escapeString.length() - 1; i++ )
 286  
                 {
 287  0
                     ch = in.read();
 288  0
                     if ( ch == -1 )
 289  
                     {
 290  0
                         atEnd = true;
 291  0
                         break;
 292  
                     }
 293  
                     
 294  0
                     key.append( (char) ch );
 295  
                 }
 296  
                 
 297  5
                 if ( !atEnd )
 298  
                 {
 299  5
                     ch = in.read();
 300  5
                     if ( !reselectDelimiterSpec( ch ) )
 301  
                     {
 302  0
                         replaceData = key.toString();
 303  0
                         replaceIndex = 1;
 304  0
                         return replaceData.charAt( 0 );
 305  
                     }
 306  
                     else
 307  
                     {
 308  5
                         key.append( (char) ch );
 309  
                     }
 310  
                 }
 311  
             }
 312  
 
 313  25
             int beginTokenMatchPos = 1;
 314  
             do
 315  
             {
 316  179
                 if ( atEnd )
 317  
                 {
 318  
                     // didn't finish reading the escape string.
 319  0
                     break;
 320  
                 }
 321  
                 
 322  179
                 if ( previousIndex != -1 && previousIndex < this.endToken.length() )
 323  
                 {
 324  0
                     ch = this.endToken.charAt( previousIndex++ );
 325  
                 }
 326  
                 else
 327  
                 {
 328  179
                     ch = in.read();
 329  
                 }
 330  179
                 if ( ch != -1 )
 331  
                 {
 332  177
                     key.append( (char) ch );
 333  177
                     if ( ( beginTokenMatchPos < this.originalBeginToken.length() )
 334  20
                         && ( ch != this.originalBeginToken.charAt( beginTokenMatchPos ) )  )
 335  
                     {
 336  1
                         ch = -1; // not really EOF but to trigger code below
 337  1
                         break;
 338  
                     }
 339  
                 }
 340  
                 else
 341  
                 {
 342  
                     break;
 343  
                 }
 344  
                 
 345  176
                 beginTokenMatchPos++;
 346  
             }
 347  176
             while ( ch != this.endToken.charAt( 0 ) );
 348  
 
 349  
             // now test endToken
 350  25
             if ( ch != -1 && this.endToken.length() > 1 )
 351  
             {
 352  0
                 int endTokenMatchPos = 1;
 353  
 
 354  
                 do
 355  
                 {
 356  0
                     if ( previousIndex != -1 && previousIndex < this.endToken.length() )
 357  
                     {
 358  0
                         ch = this.endToken.charAt( previousIndex++ );
 359  
                     }
 360  
                     else
 361  
                     {
 362  0
                         ch = in.read();
 363  
                     }
 364  
 
 365  0
                     if ( ch != -1 )
 366  
                     {
 367  0
                         key.append( (char) ch );
 368  
 
 369  0
                         if ( ch != this.endToken.charAt( endTokenMatchPos++ ) )
 370  
                         {
 371  0
                             ch = -1; // not really EOF but to trigger code below
 372  0
                             break;
 373  
                         }
 374  
 
 375  
                     }
 376  
                     else
 377  
                     {
 378  
                         break;
 379  
                     }
 380  
                 }
 381  0
                 while ( endTokenMatchPos < this.endToken.length() );
 382  
             }
 383  
 
 384  
             // There is nothing left to read so we have the situation where the begin/end token
 385  
             // are in fact the same and as there is nothing left to read we have got ourselves
 386  
             // end of a token boundary so let it pass through.
 387  25
             if ( ch == -1 )
 388  
             {
 389  3
                 replaceData = key.toString();
 390  3
                 replaceIndex = 1;
 391  3
                 return replaceData.charAt( 0 );
 392  
             }
 393  
 
 394  22
             String value = null;
 395  
             try
 396  
             {
 397  22
                 boolean escapeFound = false;
 398  22
                 if ( useEscape )
 399  
                 {
 400  16
                     if ( key.toString().startsWith( beginToken ) )
 401  
                     {
 402  5
                         String keyStr = key.toString();
 403  5
                         if ( !preserveEscapeString )
 404  
                         {
 405  5
                             value = keyStr.substring( escapeString.length(), keyStr.length() );
 406  
                         }
 407  
                         else
 408  
                         {
 409  0
                             value = keyStr;
 410  
                         }
 411  5
                         escapeFound = true;
 412  
                     }
 413  
                 }
 414  22
                 if ( !escapeFound )
 415  
                 {
 416  17
                     if ( interpolateWithPrefixPattern )
 417  
                     {
 418  0
                         value = interpolator.interpolate( key.toString(), "", recursionInterceptor );
 419  
                     }
 420  
                     else
 421  
                     {
 422  17
                         value = interpolator.interpolate( key.toString(), recursionInterceptor );
 423  
                     }
 424  
                 }
 425  
             }
 426  0
             catch ( InterpolationException e )
 427  
             {
 428  0
                 IllegalArgumentException error = new IllegalArgumentException( e.getMessage() );
 429  0
                 error.initCause( e );
 430  
 
 431  0
                 throw error;
 432  22
             }
 433  
 
 434  22
             if ( value != null )
 435  
             {
 436  22
                 if ( value.length() != 0 )
 437  
                 {
 438  22
                     replaceData = value;
 439  22
                     replaceIndex = 0;
 440  
                 }
 441  22
                 return read();
 442  
             }
 443  
             else
 444  
             {
 445  0
                 previousIndex = 0;
 446  0
                 replaceData = key.substring( 0, key.length() - this.endToken.length() );
 447  0
                 replaceIndex = 0;
 448  0
                 return this.beginToken.charAt( 0 );
 449  
             }
 450  
         }
 451  
 
 452  147
         return ch;
 453  
     }
 454  
 
 455  
     private boolean reselectDelimiterSpec( int ch )
 456  
     {
 457  172
         for ( DelimiterSpecification spec : delimiters )
 458  
         {
 459  210
             if ( ch == spec.getBegin().charAt( 0 ) )
 460  
             {
 461  25
                 currentSpec = spec;
 462  25
                 originalBeginToken = currentSpec.getBegin();
 463  25
                 beginToken = useEscape ? escapeString + originalBeginToken : originalBeginToken;
 464  25
                 endToken = currentSpec.getEnd();
 465  
 
 466  25
                 return true;
 467  
             }
 468  185
         }
 469  
         
 470  147
         return false;
 471  
     }
 472  
 
 473  
     public boolean isInterpolateWithPrefixPattern()
 474  
     {
 475  0
         return interpolateWithPrefixPattern;
 476  
     }
 477  
 
 478  
     public void setInterpolateWithPrefixPattern( boolean interpolateWithPrefixPattern )
 479  
     {
 480  16
         this.interpolateWithPrefixPattern = interpolateWithPrefixPattern;
 481  16
     }
 482  
     public String getEscapeString()
 483  
     {
 484  0
         return escapeString;
 485  
     }
 486  
 
 487  
     public void setEscapeString( String escapeString )
 488  
     {
 489  
         // TODO NPE if escapeString is null ?
 490  10
         if ( escapeString != null && escapeString.length() >= 1 )
 491  
         {
 492  10
             this.escapeString = escapeString;
 493  10
             this.useEscape = escapeString != null && escapeString.length() >= 1;
 494  
         }
 495  10
     }
 496  
 
 497  
     public boolean isPreserveEscapeString()
 498  
     {
 499  0
         return preserveEscapeString;
 500  
     }
 501  
 
 502  
     public void setPreserveEscapeString( boolean preserveEscapeString )
 503  
     {
 504  0
         this.preserveEscapeString = preserveEscapeString;
 505  0
     }
 506  
 
 507  
     public RecursionInterceptor getRecursionInterceptor()
 508  
     {
 509  0
         return recursionInterceptor;
 510  
     }
 511  
 
 512  
     public MultiDelimiterInterpolatorFilterReader setRecursionInterceptor( RecursionInterceptor recursionInterceptor )
 513  
     {
 514  0
         this.recursionInterceptor = recursionInterceptor;
 515  0
         return this;
 516  
     }
 517  
 }