Coverage Report - org.codehaus.plexus.interpolation.multi.MultiDelimiterInterpolatorFilterReader
 
Classes in this File Line Coverage Branch Coverage Complexity
MultiDelimiterInterpolatorFilterReader
62 %
93/150
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  
  * @version $Id: InterpolatorFilterReader.java 8351 2009-08-20 22:25:14Z jdcasey $
 73  
  */
 74  
 public class MultiDelimiterInterpolatorFilterReader
 75  
     extends FilterReader
 76  
 {
 77  
 
 78  
     /** Interpolator used to interpolate */
 79  
     private Interpolator interpolator;
 80  
     
 81  
     /**
 82  
      * @since 1.12
 83  
      */
 84  
     private RecursionInterceptor recursionInterceptor;
 85  
 
 86  
     /** replacement text from a token */
 87  16
     private String replaceData = null;
 88  
 
 89  
     /** Index into replacement data */
 90  16
     private int replaceIndex = -1;
 91  
 
 92  
     /** Index into previous data */
 93  16
     private int previousIndex = -1;
 94  
 
 95  
     /** Default begin token. */
 96  
     public static final String DEFAULT_BEGIN_TOKEN = "${";
 97  
 
 98  
     /** Default end token. */
 99  
     public static final String DEFAULT_END_TOKEN = "}";
 100  
     
 101  
     /** true by default to preserve backward comp */
 102  16
     private boolean interpolateWithPrefixPattern = true;
 103  
 
 104  
     private String escapeString;
 105  
     
 106  16
     private boolean useEscape = false;
 107  
     
 108  
     /** if true escapeString will be preserved \{foo} -> \{foo} */
 109  16
     private boolean preserveEscapeString = false;
 110  
     
 111  16
     private LinkedHashSet<DelimiterSpecification> delimiters = new LinkedHashSet<DelimiterSpecification>();
 112  
     
 113  
     private DelimiterSpecification currentSpec;
 114  
 
 115  
     private String beginToken;
 116  
 
 117  
     private String originalBeginToken;
 118  
 
 119  
     private String endToken;
 120  
     
 121  
     /**
 122  
      * this constructor use default begin token ${ and default end token } 
 123  
      * @param in reader to use
 124  
      * @param interpolator interpolator instance to use
 125  
      */
 126  
     public MultiDelimiterInterpolatorFilterReader( Reader in, Interpolator interpolator )
 127  
     {
 128  14
         this( in, interpolator, new SimpleRecursionInterceptor() );
 129  14
     }
 130  
     
 131  
     /**
 132  
      * @param in reader to use
 133  
      * @param interpolator interpolator instance to use
 134  
      * @param ri The {@link RecursionInterceptor} to use to prevent recursive expressions.
 135  
      * @since 1.12
 136  
      */
 137  
     public MultiDelimiterInterpolatorFilterReader( Reader in, Interpolator interpolator, RecursionInterceptor ri )
 138  
     {
 139  16
         super( in );
 140  
 
 141  16
         this.interpolator = interpolator;
 142  
         
 143  
         // always cache answers, since we'll be sending in pure expressions, not mixed text.
 144  16
         this.interpolator.setCacheAnswers( true );
 145  
         
 146  16
         recursionInterceptor = ri;
 147  
         
 148  16
         delimiters.add( DelimiterSpecification.DEFAULT_SPEC );
 149  16
     }    
 150  
 
 151  
     public MultiDelimiterInterpolatorFilterReader addDelimiterSpec( String delimiterSpec )
 152  
     {
 153  8
         if ( delimiterSpec == null )
 154  
         {
 155  0
             return this;
 156  
         }        
 157  8
         delimiters.add( DelimiterSpecification.parse( delimiterSpec ) );
 158  8
         return this;
 159  
     }
 160  
     
 161  
     public boolean removeDelimiterSpec( String delimiterSpec )
 162  
     {
 163  0
         if ( delimiterSpec == null )
 164  
         {
 165  0
             return false;
 166  
         }        
 167  0
         return delimiters.remove( DelimiterSpecification.parse( delimiterSpec ) );
 168  
     }
 169  
     
 170  
     public MultiDelimiterInterpolatorFilterReader setDelimiterSpecs( LinkedHashSet<String> specs )
 171  
     {
 172  0
         delimiters.clear();
 173  0
         for ( String spec : specs )
 174  
         {
 175  0
             if ( spec == null )
 176  
             {
 177  0
                 continue;
 178  
             }
 179  0
             delimiters.add( DelimiterSpecification.parse( spec ) );
 180  0
         }
 181  
         
 182  0
         return this;
 183  
     }
 184  
     
 185  
     /**
 186  
      * Skips characters. This method will block until some characters are available, an I/O error occurs, or the end of
 187  
      * the stream is reached.
 188  
      *
 189  
      * @param n The number of characters to skip
 190  
      * @return the number of characters actually skipped
 191  
      * @exception IllegalArgumentException If <code>n</code> is negative.
 192  
      * @exception IOException If an I/O error occurs
 193  
      */
 194  
     public long skip( long n )
 195  
         throws IOException
 196  
     {
 197  0
         if ( n < 0L )
 198  
         {
 199  0
             throw new IllegalArgumentException( "skip value is negative" );
 200  
         }
 201  
 
 202  0
         for ( long i = 0; i < n; i++ )
 203  
         {
 204  0
             if ( read() == -1 )
 205  
             {
 206  0
                 return i;
 207  
             }
 208  
         }
 209  0
         return n;
 210  
     }
 211  
 
 212  
     /**
 213  
      * Reads characters into a portion of an array. This method will block until some input is available, an I/O error
 214  
      * occurs, or the end of the stream is reached.
 215  
      *
 216  
      * @param cbuf Destination buffer to write characters to. Must not be <code>null</code>.
 217  
      * @param off Offset at which to start storing characters.
 218  
      * @param len Maximum number of characters to read.
 219  
      * @return the number of characters read, or -1 if the end of the stream has been reached
 220  
      * @exception IOException If an I/O error occurs
 221  
      */
 222  
     public int read( char cbuf[], int off, int len )
 223  
         throws IOException
 224  
     {
 225  337
         for ( int i = 0; i < len; i++ )
 226  
         {
 227  337
             int ch = read();
 228  337
             if ( ch == -1 )
 229  
             {
 230  32
                 if ( i == 0 )
 231  
                 {
 232  16
                     return -1;
 233  
                 }
 234  
                 else
 235  
                 {
 236  16
                     return i;
 237  
                 }
 238  
             }
 239  305
             cbuf[off + i] = (char) ch;
 240  
         }
 241  0
         return len;
 242  
     }
 243  
 
 244  
     /**
 245  
      * Returns the next character in the filtered stream, replacing tokens from the original stream.
 246  
      *
 247  
      * @return the next character in the resulting stream, or -1 if the end of the resulting stream has been reached
 248  
      * @exception IOException if the underlying stream throws an IOException during reading
 249  
      */
 250  
     public int read()
 251  
         throws IOException
 252  
     {
 253  359
         if ( replaceIndex != -1 && replaceIndex < replaceData.length() )
 254  
         {
 255  187
             int ch = replaceData.charAt( replaceIndex++ );
 256  187
             if ( replaceIndex >= replaceData.length() )
 257  
             {
 258  25
                 replaceIndex = -1;
 259  
             }
 260  187
             return ch;
 261  
         }
 262  
 
 263  172
         int ch = -1;
 264  172
         if ( previousIndex != -1 && previousIndex < this.endToken.length() )
 265  
         {
 266  0
             ch = this.endToken.charAt( previousIndex++ );
 267  
         }
 268  
         else
 269  
         {
 270  172
             ch = in.read();
 271  
         }
 272  
         
 273  172
         boolean inEscape = false;
 274  
         
 275  172
         if ( ( inEscape = ( useEscape && ch == escapeString.charAt( 0 ) ) ) || reselectDelimiterSpec( ch ) )
 276  
         {
 277  25
             StringBuilder key = new StringBuilder( );
 278  
 
 279  25
             key.append( (char) ch );
 280  
             
 281  
             // this will happen when we're using an escape string, and ONLY then.
 282  25
             boolean atEnd = false;
 283  
 
 284  25
             if ( inEscape )
 285  
             {
 286  5
                 for( int i = 0; i < escapeString.length() - 1; i++ )
 287  
                 {
 288  0
                     ch = in.read();
 289  0
                     if ( ch == -1 )
 290  
                     {
 291  0
                         atEnd = true;
 292  0
                         break;
 293  
                     }
 294  
                     
 295  0
                     key.append( (char) ch );
 296  
                 }
 297  
                 
 298  5
                 if ( !atEnd )
 299  
                 {
 300  5
                     ch = in.read();
 301  5
                     if ( !reselectDelimiterSpec( ch ) )
 302  
                     {
 303  0
                         replaceData = key.toString();
 304  0
                         replaceIndex = 1;
 305  0
                         return replaceData.charAt( 0 );
 306  
                     }
 307  
                     else
 308  
                     {
 309  5
                         key.append( (char) ch );
 310  
                     }
 311  
                 }
 312  
             }
 313  
 
 314  25
             int beginTokenMatchPos = 1;
 315  
             do
 316  
             {
 317  179
                 if ( atEnd )
 318  
                 {
 319  
                     // didn't finish reading the escape string.
 320  0
                     break;
 321  
                 }
 322  
                 
 323  179
                 if ( previousIndex != -1 && previousIndex < this.endToken.length() )
 324  
                 {
 325  0
                     ch = this.endToken.charAt( previousIndex++ );
 326  
                 }
 327  
                 else
 328  
                 {
 329  179
                     ch = in.read();
 330  
                 }
 331  179
                 if ( ch != -1 )
 332  
                 {
 333  177
                     key.append( (char) ch );
 334  177
                     if ( ( beginTokenMatchPos < this.originalBeginToken.length() )
 335  
                         && ( ch != this.originalBeginToken.charAt( beginTokenMatchPos ) )  )
 336  
                     {
 337  1
                         ch = -1; // not really EOF but to trigger code below
 338  1
                         break;
 339  
                     }
 340  
                 }
 341  
                 else
 342  
                 {
 343  
                     break;
 344  
                 }
 345  
                 
 346  176
                 beginTokenMatchPos++;
 347  
             }
 348  176
             while ( ch != this.endToken.charAt( 0 ) );
 349  
 
 350  
             // now test endToken
 351  25
             if ( ch != -1 && this.endToken.length() > 1 )
 352  
             {
 353  0
                 int endTokenMatchPos = 1;
 354  
 
 355  
                 do
 356  
                 {
 357  0
                     if ( previousIndex != -1 && previousIndex < this.endToken.length() )
 358  
                     {
 359  0
                         ch = this.endToken.charAt( previousIndex++ );
 360  
                     }
 361  
                     else
 362  
                     {
 363  0
                         ch = in.read();
 364  
                     }
 365  
 
 366  0
                     if ( ch != -1 )
 367  
                     {
 368  0
                         key.append( (char) ch );
 369  
 
 370  0
                         if ( ch != this.endToken.charAt( endTokenMatchPos++ ) )
 371  
                         {
 372  0
                             ch = -1; // not really EOF but to trigger code below
 373  0
                             break;
 374  
                         }
 375  
 
 376  
                     }
 377  
                     else
 378  
                     {
 379  
                         break;
 380  
                     }
 381  
                 }
 382  0
                 while ( endTokenMatchPos < this.endToken.length() );
 383  
             }
 384  
 
 385  
             // There is nothing left to read so we have the situation where the begin/end token
 386  
             // are in fact the same and as there is nothing left to read we have got ourselves
 387  
             // end of a token boundary so let it pass through.
 388  25
             if ( ch == -1 )
 389  
             {
 390  3
                 replaceData = key.toString();
 391  3
                 replaceIndex = 1;
 392  3
                 return replaceData.charAt( 0 );
 393  
             }
 394  
 
 395  22
             String value = null;
 396  
             try
 397  
             {
 398  22
                 boolean escapeFound = false;
 399  22
                 if ( useEscape )
 400  
                 {
 401  16
                     if ( key.toString().startsWith( beginToken ) )
 402  
                     {
 403  5
                         String keyStr = key.toString();
 404  5
                         if ( !preserveEscapeString )
 405  
                         {
 406  5
                             value = keyStr.substring( escapeString.length(), keyStr.length() );
 407  
                         }
 408  
                         else
 409  
                         {
 410  0
                             value = keyStr;
 411  
                         }
 412  5
                         escapeFound = true;
 413  
                     }
 414  
                 }
 415  22
                 if ( !escapeFound )
 416  
                 {
 417  17
                     if ( interpolateWithPrefixPattern )
 418  
                     {
 419  0
                         value = interpolator.interpolate( key.toString(), "", recursionInterceptor );
 420  
                     }
 421  
                     else
 422  
                     {
 423  17
                         value = interpolator.interpolate( key.toString(), recursionInterceptor );
 424  
                     }
 425  
                 }
 426  
             }
 427  0
             catch ( InterpolationException e )
 428  
             {
 429  0
                 IllegalArgumentException error = new IllegalArgumentException( e.getMessage() );
 430  0
                 error.initCause( e );
 431  
 
 432  0
                 throw error;
 433  22
             }
 434  
 
 435  22
             if ( value != null )
 436  
             {
 437  22
                 if ( value.length() != 0 )
 438  
                 {
 439  22
                     replaceData = value;
 440  22
                     replaceIndex = 0;
 441  
                 }
 442  22
                 return read();
 443  
             }
 444  
             else
 445  
             {
 446  0
                 previousIndex = 0;
 447  0
                 replaceData = key.substring( 0, key.length() - this.endToken.length() );
 448  0
                 replaceIndex = 0;
 449  0
                 return this.beginToken.charAt( 0 );
 450  
             }
 451  
         }
 452  
 
 453  147
         return ch;
 454  
     }
 455  
 
 456  
     private boolean reselectDelimiterSpec( int ch )
 457  
     {
 458  172
         for ( DelimiterSpecification spec : delimiters )
 459  
         {
 460  210
             if ( ch == spec.getBegin().charAt( 0 ) )
 461  
             {
 462  25
                 currentSpec = spec;
 463  25
                 originalBeginToken = currentSpec.getBegin();
 464  25
                 beginToken = useEscape ? escapeString + originalBeginToken : originalBeginToken;
 465  25
                 endToken = currentSpec.getEnd();
 466  
 
 467  25
                 return true;
 468  
             }
 469  185
         }
 470  
         
 471  147
         return false;
 472  
     }
 473  
 
 474  
     public boolean isInterpolateWithPrefixPattern()
 475  
     {
 476  0
         return interpolateWithPrefixPattern;
 477  
     }
 478  
 
 479  
     public void setInterpolateWithPrefixPattern( boolean interpolateWithPrefixPattern )
 480  
     {
 481  16
         this.interpolateWithPrefixPattern = interpolateWithPrefixPattern;
 482  16
     }
 483  
     public String getEscapeString()
 484  
     {
 485  0
         return escapeString;
 486  
     }
 487  
 
 488  
     public void setEscapeString( String escapeString )
 489  
     {
 490  
         // TODO NPE if escapeString is null ?
 491  10
         if ( escapeString != null && escapeString.length() >= 1 )
 492  
         {
 493  10
             this.escapeString = escapeString;
 494  10
             this.useEscape = escapeString != null && escapeString.length() >= 1;
 495  
         }
 496  10
     }
 497  
 
 498  
     public boolean isPreserveEscapeString()
 499  
     {
 500  0
         return preserveEscapeString;
 501  
     }
 502  
 
 503  
     public void setPreserveEscapeString( boolean preserveEscapeString )
 504  
     {
 505  0
         this.preserveEscapeString = preserveEscapeString;
 506  0
     }
 507  
 
 508  
     public RecursionInterceptor getRecursionInterceptor()
 509  
     {
 510  0
         return recursionInterceptor;
 511  
     }
 512  
 
 513  
     public MultiDelimiterInterpolatorFilterReader setRecursionInterceptor( RecursionInterceptor recursionInterceptor )
 514  
     {
 515  0
         this.recursionInterceptor = recursionInterceptor;
 516  0
         return this;
 517  
     }
 518  
 }