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 private String replaceData = null; 86 87 /** Index into replacement data */ 88 private int replaceIndex = -1; 89 90 /** Index into previous data */ 91 private int previousIndex = -1; 92 93 /** Hashtable to hold the replacee-replacer pairs (String to String). */ 94 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 super( in ); 125 126 this.variables = variables; 127 this.beginToken = beginToken; 128 this.endToken = endToken; 129 130 beginTokenLength = beginToken.length(); 131 endTokenLength = endToken.length(); 132 } 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 this( in, variables, DEFAULT_BEGIN_TOKEN, DEFAULT_END_TOKEN ); 143 } 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 @Override 155 public long skip( long n ) 156 throws IOException 157 { 158 if ( n < 0L ) 159 { 160 throw new IllegalArgumentException( "skip value is negative" ); 161 } 162 163 for ( long i = 0; i < n; i++ ) 164 { 165 if ( read() == -1 ) 166 { 167 return i; 168 } 169 } 170 return n; 171 } 172 173 /** 174 * Reads characters into a portion of an array. This method will block until some input is available, an I/O error 175 * occurs, or the end of the stream is reached. 176 * 177 * @param cbuf Destination buffer to write characters to. Must not be <code>null</code>. 178 * @param off Offset at which to start storing characters. 179 * @param len Maximum number of characters to read. 180 * @return the number of characters read, or -1 if the end of the stream has been reached 181 * @exception IOException If an I/O error occurs 182 */ 183 @Override 184 public int read( char cbuf[], int off, int len ) 185 throws IOException 186 { 187 for ( int i = 0; i < len; i++ ) 188 { 189 int ch = read(); 190 if ( ch == -1 ) 191 { 192 if ( i == 0 ) 193 { 194 return -1; 195 } 196 else 197 { 198 return i; 199 } 200 } 201 cbuf[off + i] = (char) ch; 202 } 203 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 @Override 213 public int read() 214 throws IOException 215 { 216 if ( replaceIndex != -1 && replaceIndex < replaceData.length() ) 217 { 218 int ch = replaceData.charAt( replaceIndex++ ); 219 if ( replaceIndex >= replaceData.length() ) 220 { 221 replaceIndex = -1; 222 } 223 return ch; 224 } 225 226 int ch; 227 if ( previousIndex != -1 && previousIndex < endTokenLength ) 228 { 229 ch = endToken.charAt( previousIndex++ ); 230 } 231 else 232 { 233 ch = in.read(); 234 } 235 236 if ( ch == beginToken.charAt( 0 ) ) 237 { 238 StringBuilder key = new StringBuilder(); 239 240 int beginTokenMatchPos = 1; 241 242 do 243 { 244 if ( previousIndex != -1 && previousIndex < endTokenLength ) 245 { 246 ch = endToken.charAt( previousIndex++ ); 247 } 248 else 249 { 250 ch = in.read(); 251 } 252 if ( ch != -1 ) 253 { 254 key.append( (char) ch ); 255 256 if ( ( beginTokenMatchPos < beginTokenLength ) 257 && ( ch != beginToken.charAt( beginTokenMatchPos++ ) ) ) 258 { 259 ch = -1; // not really EOF but to trigger code below 260 break; 261 } 262 } 263 else 264 { 265 break; 266 } 267 } 268 while ( ch != endToken.charAt( 0 ) ); 269 270 // now test endToken 271 if ( ch != -1 && endTokenLength > 1 ) 272 { 273 int endTokenMatchPos = 1; 274 275 do 276 { 277 if ( previousIndex != -1 && previousIndex < endTokenLength ) 278 { 279 ch = endToken.charAt( previousIndex++ ); 280 } 281 else 282 { 283 ch = in.read(); 284 } 285 286 if ( ch != -1 ) 287 { 288 key.append( (char) ch ); 289 290 if ( ch != endToken.charAt( endTokenMatchPos++ ) ) 291 { 292 ch = -1; // not really EOF but to trigger code below 293 break; 294 } 295 296 } 297 else 298 { 299 break; 300 } 301 } 302 while ( endTokenMatchPos < endTokenLength ); 303 } 304 305 // There is nothing left to read so we have the situation where the begin/end token 306 // are in fact the same and as there is nothing left to read we have got ourselves 307 // end of a token boundary so let it pass through. 308 if ( ch == -1 ) 309 { 310 replaceData = key.toString(); 311 replaceIndex = 0; 312 return beginToken.charAt( 0 ); 313 } 314 315 String variableKey = key.substring( beginTokenLength - 1, key.length() - endTokenLength ); 316 317 Object o = variables.get( variableKey ); 318 if ( o != null ) 319 { 320 String value = o.toString(); 321 if ( value.length() != 0 ) 322 { 323 replaceData = value; 324 replaceIndex = 0; 325 } 326 return read(); 327 } 328 else 329 { 330 previousIndex = 0; 331 replaceData = key.substring( 0, key.length() - endTokenLength ); 332 replaceIndex = 0; 333 return beginToken.charAt( 0 ); 334 } 335 } 336 337 return ch; 338 } 339 }