Coverage Report - org.apache.maven.shared.filtering.MultiDelimiterInterpolatorFilterReaderLineEnding
 
Classes in this File Line Coverage Branch Coverage Complexity
MultiDelimiterInterpolatorFilterReaderLineEnding
72%
114/158
59%
74/126
4,375
 
 1  
 package org.apache.maven.shared.filtering;
 2  
 
 3  
 /*
 4  
  * Licensed to the Apache Software Foundation (ASF) under one
 5  
  * or more contributor license agreements.  See the NOTICE file
 6  
  * distributed with this work for additional information
 7  
  * regarding copyright ownership.  The ASF licenses this file
 8  
  * to you under the Apache License, Version 2.0 (the
 9  
  * "License"); you may not use this file except in compliance
 10  
  * with the License.  You may obtain a copy of the License at
 11  
  *
 12  
  *    http://www.apache.org/licenses/LICENSE-2.0
 13  
  *
 14  
  * Unless required by applicable law or agreed to in writing,
 15  
  * software distributed under the License is distributed on an
 16  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 17  
  * KIND, either express or implied.  See the License for the
 18  
  * specific language governing permissions and limitations
 19  
  * under the License.
 20  
  */
 21  
 
 22  
 import java.io.FilterReader;
 23  
 import java.io.IOException;
 24  
 import java.io.Reader;
 25  
 import java.util.HashSet;
 26  
 import java.util.Iterator;
 27  
 import java.util.LinkedHashSet;
 28  
 
 29  
 import org.codehaus.plexus.interpolation.InterpolationException;
 30  
 import org.codehaus.plexus.interpolation.Interpolator;
 31  
 import org.codehaus.plexus.interpolation.RecursionInterceptor;
 32  
 import org.codehaus.plexus.interpolation.SimpleRecursionInterceptor;
 33  
 import org.codehaus.plexus.interpolation.multi.DelimiterSpecification;
 34  
 
 35  
 /**
 36  
  * A FilterReader implementation, that works with Interpolator interface instead of it's own interpolation
 37  
  * implementation. This implementation is heavily based on org.codehaus.plexus.util.InterpolationFilterReader.
 38  
  *
 39  
  * @author cstamas
 40  
  * @author Olivier Lamy
 41  
  * @version $Id: MultiDelimiterInterpolatorFilterReaderLineEnding.java 1067324 2011-02-04 22:56:23Z dennisl $
 42  
  * @since 1.0
 43  
  */
 44  
 public class MultiDelimiterInterpolatorFilterReaderLineEnding
 45  
     extends FilterReader
 46  
 {
 47  
 
 48  
     /** Interpolator used to interpolate */
 49  
     private Interpolator interpolator;
 50  
 
 51  
     private RecursionInterceptor recursionInterceptor;
 52  
 
 53  
     /** replacement text from a token */
 54  56
     private String replaceData = null;
 55  
 
 56  
     /** Index into replacement data */
 57  56
     private int replaceIndex = -1;
 58  
 
 59  
     /** Index into previous data */
 60  56
     private int previousIndex = -1;
 61  
 
 62  
     /** Default begin token. */
 63  
     public static final String DEFAULT_BEGIN_TOKEN = "${";
 64  
 
 65  
     /** Default end token. */
 66  
     public static final String DEFAULT_END_TOKEN = "}";
 67  
     
 68  
     /** true by default to preserve backward comp */
 69  56
     private boolean interpolateWithPrefixPattern = true;
 70  
 
 71  
     private String escapeString;
 72  
     
 73  56
     private boolean useEscape = false;
 74  
     
 75  
     /** if true escapeString will be preserved \{foo} -> \{foo} */
 76  56
     private boolean preserveEscapeString = false;
 77  
     
 78  56
     private LinkedHashSet delimiters = new LinkedHashSet();
 79  
     
 80  
     private DelimiterSpecification currentSpec;
 81  
 
 82  
     private String beginToken;
 83  
 
 84  
     private String originalBeginToken;
 85  
 
 86  
     private String endToken;
 87  
     
 88  
     private boolean supportMultiLineFiltering;
 89  
     
 90  56
     private Character preserveChar = null;
 91  
     
 92  
     /**
 93  
      * This constructor uses default begin token ${ and default end token }.
 94  
      *
 95  
      * @param in reader to use
 96  
      * @param interpolator interpolator instance to use
 97  
      * @param supportMultiLineFiltering If multi line filtering is allowed
 98  
      */
 99  
     public MultiDelimiterInterpolatorFilterReaderLineEnding( Reader in, Interpolator interpolator,
 100  
                                                              boolean supportMultiLineFiltering )
 101  
     {
 102  56
         this( in, interpolator, new SimpleRecursionInterceptor(), supportMultiLineFiltering );
 103  56
     }
 104  
     
 105  
     /**
 106  
      * @param in reader to use
 107  
      * @param interpolator interpolator instance to use
 108  
      * @param ri The {@link RecursionInterceptor} to use to prevent recursive expressions.
 109  
      * @param supportMultiLineFiltering If multi line filtering is allowed
 110  
      */
 111  
     public MultiDelimiterInterpolatorFilterReaderLineEnding( Reader in, Interpolator interpolator,
 112  
                                                              RecursionInterceptor ri,
 113  
                                                              boolean supportMultiLineFiltering )
 114  
     {
 115  56
         super( in );
 116  
 
 117  56
         this.interpolator = interpolator;
 118  
         
 119  
         // always cache answers, since we'll be sending in pure expressions, not mixed text.
 120  56
         this.interpolator.setCacheAnswers( true );
 121  
         
 122  56
         recursionInterceptor = ri;
 123  
         
 124  56
         delimiters.add( DelimiterSpecification.DEFAULT_SPEC );
 125  
         
 126  56
         this.supportMultiLineFiltering = supportMultiLineFiltering;
 127  56
     }    
 128  
 
 129  
     
 130  
     public boolean removeDelimiterSpec( String delimiterSpec )
 131  
     {
 132  0
         return delimiters.remove( DelimiterSpecification.parse( delimiterSpec ) );
 133  
     }
 134  
     
 135  
     public MultiDelimiterInterpolatorFilterReaderLineEnding setDelimiterSpecs( HashSet specs )
 136  
     {
 137  56
         delimiters.clear();
 138  56
         for ( Iterator it = specs.iterator(); it.hasNext(); )
 139  
         {
 140  112
             String spec = (String) it.next();
 141  112
             delimiters.add( DelimiterSpecification.parse( spec ) );
 142  112
         }
 143  
         
 144  56
         return this;
 145  
     }
 146  
     
 147  
     /**
 148  
      * Skips characters. This method will block until some characters are available, an I/O error occurs, or the end of
 149  
      * the stream is reached.
 150  
      *
 151  
      * @param n The number of characters to skip
 152  
      * @return the number of characters actually skipped
 153  
      * @exception IllegalArgumentException If <code>n</code> is negative.
 154  
      * @exception IOException If an I/O error occurs
 155  
      */
 156  
     public long skip( long n )
 157  
         throws IOException
 158  
     {
 159  0
         if ( n < 0L )
 160  
         {
 161  0
             throw new IllegalArgumentException( "skip value is negative" );
 162  
         }
 163  
 
 164  0
         for ( long i = 0; i < n; i++ )
 165  
         {
 166  0
             if ( read() == -1 )
 167  
             {
 168  0
                 return i;
 169  
             }
 170  
         }
 171  0
         return n;
 172  
     }
 173  
 
 174  
     /**
 175  
      * Reads characters into a portion of an array. This method will block until some input is available, an I/O error
 176  
      * occurs, or the end of the stream is reached.
 177  
      *
 178  
      * @param cbuf Destination buffer to write characters to. Must not be <code>null</code>.
 179  
      * @param off Offset at which to start storing characters.
 180  
      * @param len Maximum number of characters to read.
 181  
      * @return the number of characters read, or -1 if the end of the stream has been reached
 182  
      * @exception IOException If an I/O error occurs
 183  
      */
 184  
     public int read( char cbuf[], int off, int len )
 185  
         throws IOException
 186  
     {
 187  15004
         for ( int i = 0; i < len; i++ )
 188  
         {
 189  15004
             int ch = read();
 190  15004
             if ( ch == -1 )
 191  
             {
 192  92
                 if ( i == 0 )
 193  
                 {
 194  56
                     return -1;
 195  
                 }
 196  
                 else
 197  
                 {
 198  36
                     return i;
 199  
                 }
 200  
             }
 201  14912
             cbuf[off + i] = (char) ch;
 202  
         }
 203  0
         return len;
 204  
     }
 205  
 
 206  
     /**
 207  
      * Returns the next character in the filtered stream, replacing tokens from the original stream.
 208  
      *
 209  
      * @return the next character in the resulting stream, or -1 if the end of the resulting stream has been reached
 210  
      * @exception IOException if the underlying stream throws an IOException during reading
 211  
      */
 212  
     public int read()
 213  
         throws IOException
 214  
     {
 215  15126
         if ( replaceIndex != -1 && replaceIndex < replaceData.length() )
 216  
         {
 217  1998
             int ch = replaceData.charAt( replaceIndex++ );
 218  1998
             if ( replaceIndex >= replaceData.length() )
 219  
             {
 220  123
                 replaceIndex = -1;
 221  
             }
 222  1998
             return ch;
 223  
         }
 224  13128
         if ( preserveChar != null )
 225  
         {
 226  6
             char copy = Character.valueOf( preserveChar.charValue() ).charValue();
 227  6
             preserveChar = null;
 228  6
             replaceIndex = -1;
 229  6
             return copy;
 230  
         }
 231  
 
 232  13122
         int ch = -1;
 233  13122
         if ( previousIndex != -1 && previousIndex < this.endToken.length() )
 234  
         {
 235  0
             ch = this.endToken.charAt( previousIndex++ );
 236  
         }
 237  
         else
 238  
         {
 239  13122
             ch = in.read();
 240  
         }
 241  13122
         if ( ch == '\n' && !supportMultiLineFiltering )
 242  
         {
 243  371
             previousIndex = -1;
 244  371
             return ch;
 245  
         }
 246  12751
         boolean inEscape = false;
 247  
         
 248  12751
         if ( ( inEscape = ( useEscape && ch == escapeString.charAt( 0 ) ) ) || reselectDelimiterSpec( ch ) )
 249  
         {
 250  129
             StringBuffer key = new StringBuffer( );
 251  
 
 252  129
             key.append( (char) ch );
 253  
             
 254  
             // this will happen when we're using an escape string, and ONLY then.
 255  129
             boolean atEnd = false;
 256  
 
 257  129
             if ( inEscape )
 258  
             {
 259  13
                 for ( int i = 0; i < escapeString.length() - 1; i++ )
 260  
                 {
 261  0
                     ch = in.read();
 262  0
                     if ( ch == -1 || ( ch == '\n' && !supportMultiLineFiltering ) )
 263  
                     {
 264  0
                         atEnd = true;
 265  0
                         break;
 266  
                     }
 267  
                     
 268  0
                     key.append( (char) ch );
 269  
                 }
 270  
                 
 271  13
                 if ( !atEnd )
 272  
                 {
 273  13
                     ch = in.read();
 274  13
                     if ( !reselectDelimiterSpec( ch ) )
 275  
                     {
 276  
                         // here we are after the escape but didn't found the a startToken
 277  
                         // but we have read this means it will be removed
 278  
                         // so we preserve it
 279  6
                         replaceData = key.toString();
 280  6
                         replaceIndex = 1;
 281  6
                         preserveChar = Character.valueOf( (char) ch );
 282  6
                         return replaceData.charAt( 0 );
 283  
                     }
 284  
                     else
 285  
                     {
 286  7
                         key.append( (char) ch );
 287  
                     }
 288  
                 }
 289  
             }
 290  
 
 291  123
             int beginTokenMatchPos = 1;
 292  
             do
 293  
             {
 294  1180
                 if ( atEnd )
 295  
                 {
 296  
                     // didn't finish reading the escape string.
 297  0
                     break;
 298  
                 }
 299  
                 
 300  1180
                 if ( previousIndex != -1 && previousIndex < this.endToken.length() )
 301  
                 {
 302  0
                     ch = this.endToken.charAt( previousIndex++ );
 303  
                 }
 304  
                 else
 305  
                 {
 306  1180
                     ch = in.read();
 307  
                 }
 308  1180
                 if ( ch == '\n' && !supportMultiLineFiltering )
 309  
                 {
 310  
                     // EOL 
 311  1
                     key.append( (char) ch );
 312  1
                     break;
 313  
                 }                
 314  1179
                 if ( ch != -1 )
 315  
                 {
 316  1179
                     key.append( (char) ch );
 317  1179
                     if ( ( beginTokenMatchPos < this.originalBeginToken.length() )
 318  
                         && ( ch != this.originalBeginToken.charAt( beginTokenMatchPos ) ) )
 319  
                     {
 320  0
                         ch = -1; // not really EOF but to trigger code below
 321  0
                         break;
 322  
                     }
 323  
                 }
 324  
                 else
 325  
                 {
 326  
                     break;
 327  
                 }
 328  
                 
 329  1179
                 beginTokenMatchPos++;
 330  
             }
 331  1179
             while ( ch != this.endToken.charAt( 0 ) );
 332  
 
 333  
             // now test endToken
 334  123
             if ( ch != -1 && ( ch != '\n' && !supportMultiLineFiltering ) && this.endToken.length() > 1 )
 335  
             {
 336  0
                 int endTokenMatchPos = 1;
 337  
 
 338  
                 do
 339  
                 {
 340  0
                     if ( previousIndex != -1 && previousIndex < this.endToken.length() )
 341  
                     {
 342  0
                         ch = this.endToken.charAt( previousIndex++ );
 343  
                     }
 344  
                     else
 345  
                     {
 346  0
                         ch = in.read();
 347  
                     }
 348  
 
 349  0
                     if ( ch != -1 )
 350  
                     {
 351  0
                         key.append( (char) ch );
 352  
 
 353  0
                         if ( ch != this.endToken.charAt( endTokenMatchPos++ )
 354  
                             || ( ch != '\n' && !supportMultiLineFiltering ) )
 355  
                         {
 356  0
                             ch = -1; // not really EOF but to trigger code below
 357  0
                             break;
 358  
                         }
 359  
 
 360  
                     }
 361  
                     else
 362  
                     {
 363  
                         break;
 364  
                     }
 365  
                 }
 366  0
                 while ( endTokenMatchPos < this.endToken.length() );
 367  
             }
 368  
 
 369  
             // There is nothing left to read so we have the situation where the begin/end token
 370  
             // are in fact the same and as there is nothing left to read we have got ourselves
 371  
             // end of a token boundary so let it pass through.
 372  123
             if ( ch == -1 || ( ch == '\n' && !supportMultiLineFiltering ) )
 373  
             {
 374  1
                 replaceData = key.toString();
 375  1
                 replaceIndex = 1;
 376  1
                 return replaceData.charAt( 0 );
 377  
             }
 378  
 
 379  122
             String value = null;
 380  
             try
 381  
             {
 382  122
                 boolean escapeFound = false;
 383  122
                 if ( useEscape )
 384  
                 {
 385  47
                     if ( key.toString().startsWith( beginToken ) )
 386  
                     {
 387  7
                         String keyStr = key.toString();
 388  7
                         if ( !preserveEscapeString )
 389  
                         {
 390  7
                             value = keyStr.substring( escapeString.length(), keyStr.length() );
 391  
                         }
 392  
                         else
 393  
                         {
 394  0
                             value = keyStr;
 395  
                         }
 396  7
                         escapeFound = true;
 397  
                     }
 398  
                 }
 399  122
                 if ( !escapeFound )
 400  
                 {
 401  115
                     if ( interpolateWithPrefixPattern )
 402  
                     {
 403  0
                         value = interpolator.interpolate( key.toString(), "", recursionInterceptor );
 404  
                     }
 405  
                     else
 406  
                     {
 407  115
                         value = interpolator.interpolate( key.toString(), recursionInterceptor );
 408  
                     }
 409  
                 }
 410  
             }
 411  0
             catch ( InterpolationException e )
 412  
             {
 413  0
                 IllegalArgumentException error = new IllegalArgumentException( e.getMessage() );
 414  0
                 error.initCause( e );
 415  
 
 416  0
                 throw error;
 417  122
             }
 418  
 
 419  122
             if ( value != null )
 420  
             {
 421  122
                 if ( value.length() != 0 )
 422  
                 {
 423  122
                     replaceData = value;
 424  122
                     replaceIndex = 0;
 425  
                 }
 426  122
                 return read();
 427  
             }
 428  
             else
 429  
             {
 430  0
                 previousIndex = 0;
 431  0
                 replaceData = key.substring( 0, key.length() - this.endToken.length() );
 432  0
                 replaceIndex = 0;
 433  0
                 return this.beginToken.charAt( 0 );
 434  
             }
 435  
         }
 436  
 
 437  12622
         return ch;
 438  
     }
 439  
 
 440  
     private boolean reselectDelimiterSpec( int ch )
 441  
     {
 442  12751
         for ( Iterator it = delimiters.iterator(); it.hasNext(); )
 443  
         {
 444  25410
             DelimiterSpecification spec = (DelimiterSpecification) it.next();
 445  25410
             if ( ch == spec.getBegin().charAt( 0 ) )
 446  
             {
 447  123
                 currentSpec = spec;
 448  123
                 originalBeginToken = currentSpec.getBegin();
 449  123
                 beginToken = useEscape ? escapeString + originalBeginToken : originalBeginToken;
 450  123
                 endToken = currentSpec.getEnd();
 451  
                 
 452  123
                 return true;
 453  
             }
 454  25287
         }
 455  
         
 456  12628
         return false;
 457  
     }
 458  
 
 459  
     public boolean isInterpolateWithPrefixPattern()
 460  
     {
 461  0
         return interpolateWithPrefixPattern;
 462  
     }
 463  
 
 464  
     public void setInterpolateWithPrefixPattern( boolean interpolateWithPrefixPattern )
 465  
     {
 466  56
         this.interpolateWithPrefixPattern = interpolateWithPrefixPattern;
 467  56
     }
 468  
     public String getEscapeString()
 469  
     {
 470  0
         return escapeString;
 471  
     }
 472  
 
 473  
     public void setEscapeString( String escapeString )
 474  
     {
 475  
         // TODO NPE if escapeString is null ?
 476  56
         if ( escapeString != null && escapeString.length() >= 1 )
 477  
         {
 478  16
             this.escapeString = escapeString;
 479  16
             this.useEscape = escapeString != null && escapeString.length() >= 1;
 480  
         }
 481  56
     }
 482  
 
 483  
     public boolean isPreserveEscapeString()
 484  
     {
 485  0
         return preserveEscapeString;
 486  
     }
 487  
 
 488  
     public void setPreserveEscapeString( boolean preserveEscapeString )
 489  
     {
 490  0
         this.preserveEscapeString = preserveEscapeString;
 491  0
     }
 492  
 
 493  
     public RecursionInterceptor getRecursionInterceptor()
 494  
     {
 495  0
         return recursionInterceptor;
 496  
     }
 497  
 
 498  
     public MultiDelimiterInterpolatorFilterReaderLineEnding setRecursionInterceptor( RecursionInterceptor recursionInterceptor )
 499  
     {
 500  56
         this.recursionInterceptor = recursionInterceptor;
 501  56
         return this;
 502  
     }
 503  
 }