Coverage Report - org.apache.maven.shared.filtering.MultiDelimiterInterpolatorFilterReaderLineEnding
 
Classes in this File Line Coverage Branch Coverage Complexity
MultiDelimiterInterpolatorFilterReaderLineEnding
81%
123/151
69%
69/100
4.75
 
 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 org.codehaus.plexus.interpolation.InterpolationException;
 23  
 import org.codehaus.plexus.interpolation.Interpolator;
 24  
 import org.codehaus.plexus.interpolation.RecursionInterceptor;
 25  
 import org.codehaus.plexus.interpolation.SimpleRecursionInterceptor;
 26  
 import org.codehaus.plexus.interpolation.multi.DelimiterSpecification;
 27  
 
 28  
 import java.io.BufferedReader;
 29  
 import java.io.FilterReader;
 30  
 import java.io.IOException;
 31  
 import java.io.Reader;
 32  
 import java.util.HashSet;
 33  
 import java.util.Iterator;
 34  
 import java.util.LinkedHashSet;
 35  
 
 36  
 /**
 37  
  * A FilterReader implementation, that works with Interpolator interface instead of it's own interpolation
 38  
  * implementation. This implementation is heavily based on org.codehaus.plexus.util.InterpolationFilterReader.
 39  
  *
 40  
  * @author cstamas
 41  
  * @author Olivier Lamy
 42  
  *
 43  
  * @since 1.0
 44  
  */
 45  
 public class MultiDelimiterInterpolatorFilterReaderLineEnding
 46  
     extends FilterReader
 47  
 {
 48  
 
 49  
     /**
 50  
      * Interpolator used to interpolate
 51  
      */
 52  
     private Interpolator interpolator;
 53  
 
 54  
     private RecursionInterceptor recursionInterceptor;
 55  
 
 56  
     /**
 57  
      * replacement text from a token
 58  
      */
 59  57
     private String replaceData = null;
 60  
 
 61  
     /**
 62  
      * Index into replacement data
 63  
      */
 64  57
     private int replaceIndex = 0;
 65  
 
 66  
     /**
 67  
      * Default begin token.
 68  
      */
 69  
     public static final String DEFAULT_BEGIN_TOKEN = "${";
 70  
 
 71  
     /**
 72  
      * Default end token.
 73  
      */
 74  
     public static final String DEFAULT_END_TOKEN = "}";
 75  
 
 76  
     /**
 77  
      * true by default to preserve backward comp
 78  
      */
 79  57
     private boolean interpolateWithPrefixPattern = true;
 80  
 
 81  
     private String escapeString;
 82  
 
 83  57
     private boolean useEscape = false;
 84  
 
 85  
     /**
 86  
      * if true escapeString will be preserved \{foo} -> \{foo}
 87  
      */
 88  57
     private boolean preserveEscapeString = false;
 89  
 
 90  57
     private LinkedHashSet delimiters = new LinkedHashSet();
 91  
 
 92  
     private String beginToken;
 93  
 
 94  
     private String endToken;
 95  
 
 96  
     private boolean supportMultiLineFiltering;
 97  
 
 98  
     /**
 99  
      * must always be bigger than escape string plus delimiters, but doesn't need to be exact
 100  
      */
 101  57
     private int markLength = 16;
 102  
 
 103  57
     private boolean eof = false;
 104  
 
 105  
     /**
 106  
      * This constructor uses default begin token ${ and default end token }.
 107  
      *
 108  
      * @param in                        reader to use
 109  
      * @param interpolator              interpolator instance to use
 110  
      * @param supportMultiLineFiltering If multi line filtering is allowed
 111  
      */
 112  
     public MultiDelimiterInterpolatorFilterReaderLineEnding( Reader in, Interpolator interpolator,
 113  
                                                              boolean supportMultiLineFiltering )
 114  
     {
 115  57
         this( in, interpolator, new SimpleRecursionInterceptor(), supportMultiLineFiltering );
 116  57
     }
 117  
 
 118  
     /**
 119  
      * @param in                        reader to use
 120  
      * @param interpolator              interpolator instance to use
 121  
      * @param ri                        The {@link RecursionInterceptor} to use to prevent recursive expressions.
 122  
      * @param supportMultiLineFiltering If multi line filtering is allowed
 123  
      */
 124  
     public MultiDelimiterInterpolatorFilterReaderLineEnding( Reader in, Interpolator interpolator,
 125  
                                                              RecursionInterceptor ri,
 126  
                                                              boolean supportMultiLineFiltering )
 127  
     {
 128  
         // wrap our own buffer, so we can use mark/reset safely.
 129  57
         super( new BufferedReader( in ) );
 130  
 
 131  57
         this.interpolator = interpolator;
 132  
 
 133  
         // always cache answers, since we'll be sending in pure expressions, not mixed text.
 134  57
         this.interpolator.setCacheAnswers( true );
 135  
 
 136  57
         recursionInterceptor = ri;
 137  
 
 138  57
         delimiters.add( DelimiterSpecification.DEFAULT_SPEC );
 139  
 
 140  57
         this.supportMultiLineFiltering = supportMultiLineFiltering;
 141  
 
 142  57
         calculateMarkLength();
 143  
 
 144  57
     }
 145  
 
 146  
 
 147  
     public boolean removeDelimiterSpec( String delimiterSpec )
 148  
     {
 149  0
         return delimiters.remove( DelimiterSpecification.parse( delimiterSpec ) );
 150  
     }
 151  
 
 152  
     public MultiDelimiterInterpolatorFilterReaderLineEnding setDelimiterSpecs( HashSet specs )
 153  
     {
 154  57
         delimiters.clear();
 155  57
         for ( Iterator it = specs.iterator(); it.hasNext(); )
 156  
         {
 157  114
             String spec = (String) it.next();
 158  114
             delimiters.add( DelimiterSpecification.parse( spec ) );
 159  114
             markLength += spec.length() * 2;
 160  114
         }
 161  
 
 162  57
         return this;
 163  
     }
 164  
 
 165  
     /**
 166  
      * Skips characters. This method will block until some characters are available, an I/O error occurs, or the end of
 167  
      * the stream is reached.
 168  
      *
 169  
      * @param n The number of characters to skip
 170  
      * @return the number of characters actually skipped
 171  
      * @throws IllegalArgumentException If <code>n</code> is negative.
 172  
      * @throws IOException              If an I/O error occurs
 173  
      */
 174  
     public long skip( long n )
 175  
         throws IOException
 176  
     {
 177  0
         if ( n < 0L )
 178  
         {
 179  0
             throw new IllegalArgumentException( "skip value is negative" );
 180  
         }
 181  
 
 182  0
         for ( long i = 0; i < n; i++ )
 183  
         {
 184  0
             if ( read() == -1 )
 185  
             {
 186  0
                 return i;
 187  
             }
 188  
         }
 189  0
         return n;
 190  
     }
 191  
 
 192  
     /**
 193  
      * Reads characters into a portion of an array. This method will block until some input is available, an I/O error
 194  
      * occurs, or the end of the stream is reached.
 195  
      *
 196  
      * @param cbuf Destination buffer to write characters to. Must not be <code>null</code>.
 197  
      * @param off  Offset at which to start storing characters.
 198  
      * @param len  Maximum number of characters to read.
 199  
      * @return the number of characters read, or -1 if the end of the stream has been reached
 200  
      * @throws IOException If an I/O error occurs
 201  
      */
 202  
     public int read( char cbuf[], int off, int len )
 203  
         throws IOException
 204  
     {
 205  15624
         for ( int i = 0; i < len; i++ )
 206  
         {
 207  15624
             int ch = read();
 208  15624
             if ( ch == -1 )
 209  
             {
 210  94
                 if ( i == 0 )
 211  
                 {
 212  57
                     return -1;
 213  
                 }
 214  
                 else
 215  
                 {
 216  37
                     return i;
 217  
                 }
 218  
             }
 219  15530
             cbuf[off + i] = (char) ch;
 220  
         }
 221  0
         return len;
 222  
     }
 223  
 
 224  
     /**
 225  
      * Returns the next character in the filtered stream, replacing tokens from the original stream.
 226  
      *
 227  
      * @return the next character in the resulting stream, or -1 if the end of the resulting stream has been reached
 228  
      * @throws IOException if the underlying stream throws an IOException during reading
 229  
      */
 230  
     public int read()
 231  
         throws IOException
 232  
     {
 233  15772
         if ( replaceIndex > 0 )
 234  
         {
 235  1891
             return replaceData.charAt( replaceData.length() - ( replaceIndex-- ) );
 236  
         }
 237  13881
         if ( eof )
 238  
         {
 239  0
             return -1;
 240  
         }
 241  
 
 242  13881
         in.mark( markLength );
 243  
 
 244  13881
         int ch = in.read();
 245  13881
         if ( ( ch == -1 ) || ( ch == '\n' && !supportMultiLineFiltering ) )
 246  
         {
 247  486
             return ch;
 248  
         }
 249  
 
 250  13395
         boolean inEscape = ( useEscape && ch == escapeString.charAt( 0 ) );
 251  
 
 252  13395
         StringBuffer key = new StringBuffer();
 253  
 
 254  
         // have we found an escape string?
 255  13395
         if ( inEscape )
 256  
         {
 257  42
             for ( int i = 0; i < escapeString.length(); i++ )
 258  
             {
 259  21
                 key.append( (char) ch );
 260  
 
 261  21
                 if ( ch != escapeString.charAt( i ) || ch == -1 || ( ch == '\n' && !supportMultiLineFiltering ) )
 262  
                 {
 263  
                     // mismatch, EOF or EOL, no escape string here
 264  0
                     in.reset();
 265  0
                     inEscape = false;
 266  0
                     key.setLength( 0 );
 267  0
                     break;
 268  
                 }
 269  
 
 270  21
                 ch = in.read();
 271  
 
 272  
             }
 273  
 
 274  
         }
 275  
 
 276  
         // have we found a delimiter?
 277  13395
         int max = 0;
 278  13395
         for ( Iterator it = delimiters.iterator(); it.hasNext(); )
 279  
         {
 280  26790
             DelimiterSpecification spec = (DelimiterSpecification) it.next();
 281  26790
             String begin = spec.getBegin();
 282  
 
 283  
             // longest match wins
 284  26790
             if ( begin.length() < max )
 285  
             {
 286  0
                 continue;
 287  
             }
 288  
 
 289  27034
             for ( int i = 0; i < begin.length(); i++ )
 290  
             {
 291  26895
                 if ( ch != begin.charAt( i ) || ch == -1 || ( ch == '\n' && !supportMultiLineFiltering ) )
 292  
                 {
 293  
                     // mismatch, EOF or EOL, no match
 294  0
                     break;
 295  
                 }
 296  
 
 297  244
                 if ( i == begin.length() - 1 )
 298  
                 {
 299  
 
 300  139
                     beginToken = spec.getBegin();
 301  139
                     endToken = spec.getEnd();
 302  
 
 303  
                 }
 304  
 
 305  244
                 ch = in.read();
 306  
 
 307  
             }
 308  
 
 309  26790
             in.reset();
 310  26790
             in.skip( key.length() );
 311  26790
             ch = in.read();
 312  
 
 313  26790
         }
 314  
 
 315  
         // escape means no luck, prevent parsing of the escaped character, and return
 316  13395
         if ( inEscape )
 317  
         {
 318  
 
 319  21
             if ( beginToken != null )
 320  
             {
 321  12
                 if ( !preserveEscapeString )
 322  
                 {
 323  12
                     key.setLength( 0 );
 324  
                 }
 325  
             }
 326  
 
 327  21
             beginToken = null;
 328  21
             endToken = null;
 329  
 
 330  21
             key.append( (char) ch );
 331  
 
 332  21
             replaceData = key.toString();
 333  21
             replaceIndex = key.length();
 334  
 
 335  21
             return read();
 336  
 
 337  
         }
 338  
 
 339  
         // no match means no luck, reset and return
 340  13374
         if ( beginToken == null || beginToken.length() == 0 || endToken == null || endToken.length() == 0 )
 341  
         {
 342  
 
 343  13247
             in.reset();
 344  13247
             return in.read();
 345  
 
 346  
         }
 347  
 
 348  
         // we're committed, find the end token, EOL or EOF
 349  
 
 350  127
         key.append( beginToken );
 351  127
         in.reset();
 352  127
         in.skip( beginToken.length() );
 353  127
         ch = in.read();
 354  
 
 355  127
         int end = endToken.length();
 356  
         do
 357  
         {
 358  1078
             if ( ch == -1 )
 359  
             {
 360  0
                 break;
 361  
             }
 362  1078
             else if ( ch == '\n' && !supportMultiLineFiltering )
 363  
             {
 364  
                 // EOL
 365  5
                 key.append( (char) ch );
 366  5
                 break;
 367  
             }
 368  
 
 369  1073
             key.append( (char) ch );
 370  
 
 371  1073
             if ( ch == this.endToken.charAt( end - 1 ) )
 372  
             {
 373  122
                 end--;
 374  122
                 if ( end == 0 )
 375  
                 {
 376  122
                     break;
 377  
                 }
 378  
             }
 379  
             else
 380  
             {
 381  951
                 end = endToken.length();
 382  
             }
 383  
 
 384  951
             ch = in.read();
 385  
         }
 386  951
         while ( true );
 387  
 
 388  
         // reset back to no tokens
 389  127
         beginToken = null;
 390  127
         endToken = null;
 391  
 
 392  
         // found endtoken? interpolate our key resolved above
 393  127
         String value = null;
 394  127
         if ( end == 0 )
 395  
         {
 396  
             try
 397  
             {
 398  122
                 if ( interpolateWithPrefixPattern )
 399  
                 {
 400  0
                     value = interpolator.interpolate( key.toString(), "", recursionInterceptor );
 401  
                 }
 402  
                 else
 403  
                 {
 404  122
                     value = interpolator.interpolate( key.toString(), recursionInterceptor );
 405  
                 }
 406  
             }
 407  0
             catch ( InterpolationException e )
 408  
             {
 409  0
                 IllegalArgumentException error = new IllegalArgumentException( e.getMessage() );
 410  0
                 error.initCause( e );
 411  
 
 412  0
                 throw error;
 413  122
             }
 414  
         }
 415  
 
 416  
         // write away the value if present, otherwise the key unmodified
 417  127
         if ( value != null )
 418  
         {
 419  122
             replaceData = value;
 420  122
             replaceIndex = value.length();
 421  
         }
 422  
         else
 423  
         {
 424  5
             replaceData = key.toString();
 425  5
             replaceIndex = key.length();
 426  
         }
 427  
 
 428  127
         if ( ch == -1 )
 429  
         {
 430  0
             eof = true;
 431  
         }
 432  127
         return read();
 433  
 
 434  
     }
 435  
 
 436  
     public boolean isInterpolateWithPrefixPattern()
 437  
     {
 438  0
         return interpolateWithPrefixPattern;
 439  
     }
 440  
 
 441  
     public void setInterpolateWithPrefixPattern( boolean interpolateWithPrefixPattern )
 442  
     {
 443  57
         this.interpolateWithPrefixPattern = interpolateWithPrefixPattern;
 444  57
     }
 445  
 
 446  
     public String getEscapeString()
 447  
     {
 448  0
         return escapeString;
 449  
     }
 450  
 
 451  
     public void setEscapeString( String escapeString )
 452  
     {
 453  
         // TODO NPE if escapeString is null ?
 454  57
         if ( escapeString != null && escapeString.length() >= 1 )
 455  
         {
 456  17
             this.escapeString = escapeString;
 457  17
             this.useEscape = escapeString != null && escapeString.length() >= 1;
 458  17
             calculateMarkLength();
 459  
         }
 460  57
     }
 461  
 
 462  
     public boolean isPreserveEscapeString()
 463  
     {
 464  0
         return preserveEscapeString;
 465  
     }
 466  
 
 467  
     public void setPreserveEscapeString( boolean preserveEscapeString )
 468  
     {
 469  0
         this.preserveEscapeString = preserveEscapeString;
 470  0
     }
 471  
 
 472  
     public RecursionInterceptor getRecursionInterceptor()
 473  
     {
 474  0
         return recursionInterceptor;
 475  
     }
 476  
 
 477  
     public MultiDelimiterInterpolatorFilterReaderLineEnding setRecursionInterceptor(
 478  
         RecursionInterceptor recursionInterceptor )
 479  
     {
 480  57
         this.recursionInterceptor = recursionInterceptor;
 481  57
         return this;
 482  
     }
 483  
 
 484  
     private void calculateMarkLength()
 485  
     {
 486  74
         markLength = 16;
 487  
 
 488  74
         if ( escapeString != null )
 489  
         {
 490  
 
 491  17
             markLength += escapeString.length();
 492  
 
 493  
         }
 494  74
         for ( Iterator it = delimiters.iterator(); it.hasNext(); )
 495  
         {
 496  
 
 497  91
             DelimiterSpecification spec = (DelimiterSpecification) it.next();
 498  91
             markLength += spec.getBegin().length();
 499  91
             markLength += spec.getEnd().length();
 500  
 
 501  91
         }
 502  74
     }
 503  
 
 504  
 }