Coverage Report - org.codehaus.plexus.util.InterpolationFilterReader
 
Classes in this File Line Coverage Branch Coverage Complexity
InterpolationFilterReader
85%
64/75
73%
41/56
8.2
 
 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 acknowledgement:
 21  
  *       "This product includes software developed by the
 22  
  *        Apache Software Foundation (http://www.codehaus.org/)."
 23  
  *    Alternately, this acknowledgement may appear in the software itself,
 24  
  *    if and wherever such third-party acknowledgements 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.util;
 56  
 
 57  
 import java.io.FilterReader;
 58  
 import java.io.IOException;
 59  
 import java.io.Reader;
 60  
 import java.util.HashMap;
 61  
 import java.util.Map;
 62  
 
 63  
 /**
 64  
  * A FilterReader which interpolates keyword values into a character stream. Keywords are recognized when enclosed
 65  
  * between starting and ending delimiter strings. The keywords themselves, and their values, are fetched from a Map
 66  
  * supplied to the constructor.
 67  
  * <p>
 68  
  * When a possible keyword token is recognized (by detecting the starting and ending token delimiters):
 69  
  * </p>
 70  
  * <ul>
 71  
  * <li>if the enclosed string is found in the keyword Map, the delimiters and the keyword are effectively replaced by
 72  
  * the keyword's value;</li>
 73  
  * <li>if the enclosed string is found in the keyword Map, but its value has zero length, then the token (delimiters and
 74  
  * keyword) is effectively removed from the character stream;</li>
 75  
  * <li>if the enclosed string is <em>not</em> found in the keyword Map, then no substitution is made; the token text is
 76  
  * passed through unaltered.</li>
 77  
  * </ul>
 78  
  * 
 79  
  * @see LineOrientedInterpolatingReader s
 80  
  */
 81  
 public class InterpolationFilterReader
 82  
     extends FilterReader
 83  
 {
 84  
     /** replacement text from a token */
 85  12
     private String replaceData = null;
 86  
 
 87  
     /** Index into replacement data */
 88  12
     private int replaceIndex = -1;
 89  
 
 90  
     /** Index into previous data */
 91  12
     private int previousIndex = -1;
 92  
 
 93  
     /** Hashtable to hold the replacee-replacer pairs (String to String). */
 94  12
     private Map<?, Object> variables = new HashMap<Object, Object>();
 95  
 
 96  
     /** Character marking the beginning of a token. */
 97  
     private String beginToken;
 98  
 
 99  
     /** Character marking the end of a token. */
 100  
     private String endToken;
 101  
 
 102  
     /** Length of begin token. */
 103  
     private int beginTokenLength;
 104  
 
 105  
     /** Length of end token. */
 106  
     private int endTokenLength;
 107  
 
 108  
     /** Default begin token. */
 109  
     private static final String DEFAULT_BEGIN_TOKEN = "${";
 110  
 
 111  
     /** Default end token. */
 112  
     private static final String DEFAULT_END_TOKEN = "}";
 113  
 
 114  
     /**
 115  
      * Construct a Reader to interpolate values enclosed between the given delimiter tokens.
 116  
      * 
 117  
      * @param in a Reader to be wrapped for interpolation.
 118  
      * @param variables name/value pairs to be interpolated into the character stream.
 119  
      * @param beginToken an interpolation target begins with this.
 120  
      * @param endToken an interpolation target ends with this.
 121  
      */
 122  
     public InterpolationFilterReader( Reader in, Map<?, Object> variables, String beginToken, String endToken )
 123  
     {
 124  12
         super( in );
 125  
 
 126  12
         this.variables = variables;
 127  12
         this.beginToken = beginToken;
 128  12
         this.endToken = endToken;
 129  
 
 130  12
         beginTokenLength = beginToken.length();
 131  12
         endTokenLength = endToken.length();
 132  12
     }
 133  
 
 134  
     /**
 135  
      * Construct a Reader using the default interpolation delimiter tokens "${" and "}".
 136  
      * 
 137  
      * @param in a Reader to be wrapped for interpolation.
 138  
      * @param variables name/value pairs to be interpolated into the character stream.
 139  
      */
 140  
     public InterpolationFilterReader( Reader in, Map<String, Object> variables )
 141  
     {
 142  5
         this( in, variables, DEFAULT_BEGIN_TOKEN, DEFAULT_END_TOKEN );
 143  5
     }
 144  
 
 145  
     /**
 146  
      * Skips characters. This method will block until some characters are available, an I/O error occurs, or the end of
 147  
      * the stream is reached.
 148  
      *
 149  
      * @param n The number of characters to skip
 150  
      * @return the number of characters actually skipped
 151  
      * @exception IllegalArgumentException If <code>n</code> is negative.
 152  
      * @exception IOException If an I/O error occurs
 153  
      */
 154  
     public long skip( long n )
 155  
         throws IOException
 156  
     {
 157  0
         if ( n < 0L )
 158  
         {
 159  0
             throw new IllegalArgumentException( "skip value is negative" );
 160  
         }
 161  
 
 162  0
         for ( long i = 0; i < n; i++ )
 163  
         {
 164  0
             if ( read() == -1 )
 165  
             {
 166  0
                 return i;
 167  
             }
 168  
         }
 169  0
         return n;
 170  
     }
 171  
 
 172  
     /**
 173  
      * Reads characters into a portion of an array. This method will block until some input is available, an I/O error
 174  
      * occurs, or the end of the stream is reached.
 175  
      *
 176  
      * @param cbuf Destination buffer to write characters to. Must not be <code>null</code>.
 177  
      * @param off Offset at which to start storing characters.
 178  
      * @param len Maximum number of characters to read.
 179  
      * @return the number of characters read, or -1 if the end of the stream has been reached
 180  
      * @exception IOException If an I/O error occurs
 181  
      */
 182  
     public int read( char cbuf[], int off, int len )
 183  
         throws IOException
 184  
     {
 185  374
         for ( int i = 0; i < len; i++ )
 186  
         {
 187  374
             int ch = read();
 188  374
             if ( ch == -1 )
 189  
             {
 190  24
                 if ( i == 0 )
 191  
                 {
 192  12
                     return -1;
 193  
                 }
 194  
                 else
 195  
                 {
 196  12
                     return i;
 197  
                 }
 198  
             }
 199  350
             cbuf[off + i] = (char) ch;
 200  
         }
 201  0
         return len;
 202  
     }
 203  
 
 204  
     /**
 205  
      * Returns the next character in the filtered stream, replacing tokens from the original stream.
 206  
      *
 207  
      * @return the next character in the resulting stream, or -1 if the end of the resulting stream has been reached
 208  
      * @exception IOException if the underlying stream throws an IOException during reading
 209  
      */
 210  
     public int read()
 211  
         throws IOException
 212  
     {
 213  387
         if ( replaceIndex != -1 && replaceIndex < replaceData.length() )
 214  
         {
 215  190
             int ch = replaceData.charAt( replaceIndex++ );
 216  190
             if ( replaceIndex >= replaceData.length() )
 217  
             {
 218  25
                 replaceIndex = -1;
 219  
             }
 220  190
             return ch;
 221  
         }
 222  
 
 223  
         int ch;
 224  197
         if ( previousIndex != -1 && previousIndex < endTokenLength )
 225  
         {
 226  8
             ch = endToken.charAt( previousIndex++ );
 227  
         }
 228  
         else
 229  
         {
 230  189
             ch = in.read();
 231  
         }
 232  
 
 233  197
         if ( ch == beginToken.charAt( 0 ) )
 234  
         {
 235  27
             StringBuilder key = new StringBuilder();
 236  
 
 237  27
             int beginTokenMatchPos = 1;
 238  
 
 239  
             do
 240  
             {
 241  186
                 if ( previousIndex != -1 && previousIndex < endTokenLength )
 242  
                 {
 243  0
                     ch = endToken.charAt( previousIndex++ );
 244  
                 }
 245  
                 else
 246  
                 {
 247  186
                     ch = in.read();
 248  
                 }
 249  186
                 if ( ch != -1 )
 250  
                 {
 251  181
                     key.append( (char) ch );
 252  
 
 253  181
                     if ( ( beginTokenMatchPos < beginTokenLength )
 254  
                         && ( ch != beginToken.charAt( beginTokenMatchPos++ ) ) )
 255  
                     {
 256  1
                         ch = -1; // not really EOF but to trigger code below
 257  1
                         break;
 258  
                     }
 259  
                 }
 260  
                 else
 261  
                 {
 262  
                     break;
 263  
                 }
 264  
             }
 265  180
             while ( ch != endToken.charAt( 0 ) );
 266  
 
 267  
             // now test endToken
 268  27
             if ( ch != -1 && endTokenLength > 1 )
 269  
             {
 270  1
                 int endTokenMatchPos = 1;
 271  
 
 272  
                 do
 273  
                 {
 274  1
                     if ( previousIndex != -1 && previousIndex < endTokenLength )
 275  
                     {
 276  0
                         ch = endToken.charAt( previousIndex++ );
 277  
                     }
 278  
                     else
 279  
                     {
 280  1
                         ch = in.read();
 281  
                     }
 282  
 
 283  1
                     if ( ch != -1 )
 284  
                     {
 285  1
                         key.append( (char) ch );
 286  
 
 287  1
                         if ( ch != endToken.charAt( endTokenMatchPos++ ) )
 288  
                         {
 289  0
                             ch = -1; // not really EOF but to trigger code below
 290  0
                             break;
 291  
                         }
 292  
 
 293  
                     }
 294  
                     else
 295  
                     {
 296  
                         break;
 297  
                     }
 298  
                 }
 299  1
                 while ( endTokenMatchPos < endTokenLength );
 300  
             }
 301  
 
 302  
             // There is nothing left to read so we have the situation where the begin/end token
 303  
             // are in fact the same and as there is nothing left to read we have got ourselves
 304  
             // end of a token boundary so let it pass through.
 305  27
             if ( ch == -1 )
 306  
             {
 307  6
                 replaceData = key.toString();
 308  6
                 replaceIndex = 0;
 309  6
                 return beginToken.charAt( 0 );
 310  
             }
 311  
 
 312  21
             String variableKey = key.substring( beginTokenLength - 1, key.length() - endTokenLength );
 313  
 
 314  21
             Object o = variables.get( variableKey );
 315  21
             if ( o != null )
 316  
             {
 317  13
                 String value = o.toString();
 318  13
                 if ( value.length() != 0 )
 319  
                 {
 320  13
                     replaceData = value;
 321  13
                     replaceIndex = 0;
 322  
                 }
 323  13
                 return read();
 324  
             }
 325  
             else
 326  
             {
 327  8
                 previousIndex = 0;
 328  8
                 replaceData = key.substring( 0, key.length() - endTokenLength );
 329  8
                 replaceIndex = 0;
 330  8
                 return beginToken.charAt( 0 );
 331  
             }
 332  
         }
 333  
 
 334  170
         return ch;
 335  
     }
 336  
 }