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.BufferedReader;
23 import java.io.IOException;
24 import java.io.Reader;
25
26 import org.codehaus.plexus.interpolation.InterpolationException;
27 import org.codehaus.plexus.interpolation.Interpolator;
28 import org.codehaus.plexus.interpolation.RecursionInterceptor;
29 import org.codehaus.plexus.interpolation.SimpleRecursionInterceptor;
30
31 /**
32 * A FilterReader implementation, that works with Interpolator interface instead of it's own interpolation
33 * implementation. This implementation is heavily based on org.codehaus.plexus.util.InterpolationFilterReader.
34 *
35 * @author cstamas
36 * @author Olivier Lamy
37 * @since 1.0
38 */
39 public class InterpolatorFilterReaderLineEnding
40 extends AbstractFilterReaderLineEnding
41 {
42
43 /**
44 * Interpolator used to interpolate
45 */
46 private Interpolator interpolator;
47
48 private RecursionInterceptor recursionInterceptor;
49
50 /**
51 * replacement text from a token
52 */
53 private String replaceData = null;
54
55 /**
56 * Index into replacement data
57 */
58 private int replaceIndex = 0;
59
60 /**
61 * Default begin token.
62 */
63 public static final String DEFAULT_BEGIN_TOKEN = "${";
64
65 /**
66 * Default end token.
67 */
68 public static final String DEFAULT_END_TOKEN = "}";
69
70 private String beginToken;
71
72 private String endToken;
73
74 /**
75 * true by default to preserve backward comp
76 */
77 private boolean interpolateWithPrefixPattern = true;
78
79 private boolean supportMultiLineFiltering;
80
81 private boolean eof = false;
82
83 /**
84 * @param in reader to use
85 * @param interpolator interpolator instance to use
86 * @param beginToken start token to use
87 * @param endToken end token to use
88 * @param supportMultiLineFiltering If multi line filtering is allowed
89 */
90 public InterpolatorFilterReaderLineEnding( Reader in, Interpolator interpolator, String beginToken, String endToken,
91 boolean supportMultiLineFiltering )
92 {
93 this( in, interpolator, beginToken, endToken, new SimpleRecursionInterceptor(), supportMultiLineFiltering );
94 }
95
96 /**
97 * @param in reader to use
98 * @param interpolator interpolator instance to use
99 * @param beginToken start token to use
100 * @param endToken end token to use
101 * @param ri The {@link RecursionInterceptor} to use to prevent recursive expressions.
102 * @param supportMultiLineFiltering If multi line filtering is allowed
103 */
104 private InterpolatorFilterReaderLineEnding( Reader in, Interpolator interpolator, String beginToken,
105 String endToken, RecursionInterceptor ri,
106 boolean supportMultiLineFiltering )
107 {
108 // wrap our own buffer, so we can use mark/reset safely.
109 super( new BufferedReader( in ) );
110
111 this.interpolator = interpolator;
112
113 this.beginToken = beginToken;
114
115 this.endToken = endToken;
116
117 recursionInterceptor = ri;
118
119 this.supportMultiLineFiltering = supportMultiLineFiltering;
120
121 calculateMarkLength();
122
123 }
124
125 /**
126 * Skips characters. This method will block until some characters are available, an I/O error occurs, or the end of
127 * the stream is reached.
128 *
129 * @param n The number of characters to skip
130 * @return the number of characters actually skipped
131 * @throws IOException If an I/O error occurs
132 */
133 public long skip( long n )
134 throws IOException
135 {
136 if ( n < 0L )
137 {
138 throw new IllegalArgumentException( "skip value is negative" );
139 }
140
141 for ( long i = 0; i < n; i++ )
142 {
143 if ( read() == -1 )
144 {
145 return i;
146 }
147 }
148 return n;
149 }
150
151 /**
152 * Reads characters into a portion of an array. This method will block until some input is available, an I/O error
153 * occurs, or the end of the stream is reached.
154 *
155 * @param cbuf Destination buffer to write characters to. Must not be <code>null</code>.
156 * @param off Offset at which to start storing characters.
157 * @param len Maximum number of characters to read.
158 * @return the number of characters read, or -1 if the end of the stream has been reached
159 * @throws IOException If an I/O error occurs
160 */
161 public int read( char cbuf[], int off, int len )
162 throws IOException
163 {
164 for ( int i = 0; i < len; i++ )
165 {
166 int ch = read();
167 if ( ch == -1 )
168 {
169 if ( i == 0 )
170 {
171 return -1;
172 }
173 else
174 {
175 return i;
176 }
177 }
178 cbuf[off + i] = (char) ch;
179 }
180 return len;
181 }
182
183 /**
184 * Returns the next character in the filtered stream, replacing tokens from the original stream.
185 *
186 * @return the next character in the resulting stream, or -1 if the end of the resulting stream has been reached
187 * @throws IOException if the underlying stream throws an IOException during reading
188 */
189 public int read()
190 throws IOException
191 {
192 if ( replaceIndex > 0 )
193 {
194 return replaceData.charAt( replaceData.length() - ( replaceIndex-- ) );
195 }
196 if ( eof )
197 {
198 return -1;
199 }
200
201 in.mark( markLength );
202
203 int ch = in.read();
204 if ( ch == -1 || ( ch == '\n' && !supportMultiLineFiltering ) )
205 {
206 return ch;
207 }
208
209 boolean inEscape = useEscape && ch == getEscapeString().charAt( 0 );
210
211 StringBuilder key = new StringBuilder();
212
213 // have we found an escape string?
214 if ( inEscape )
215 {
216 for ( int i = 0; i < getEscapeString().length(); i++ )
217 {
218 key.append( (char) ch );
219
220 if ( ch != getEscapeString().charAt( i ) || ch == -1 || ( ch == '\n' && !supportMultiLineFiltering ) )
221 {
222 // mismatch, EOF or EOL, no escape string here
223 in.reset();
224 inEscape = false;
225 key.setLength( 0 );
226 break;
227 }
228
229 ch = in.read();
230 }
231
232 }
233
234 // have we found a delimiter?
235 boolean foundToken = false;
236 for ( int i = 0; i < beginToken.length(); i++ )
237 {
238 if ( ch != beginToken.charAt( i ) || ch == -1 || ( ch == '\n' && !supportMultiLineFiltering ) )
239 {
240 // mismatch, EOF or EOL, no match
241 break;
242 }
243
244 if ( i == beginToken.length() - 1 )
245 {
246
247 foundToken = true;
248
249 }
250
251 ch = in.read();
252
253 }
254
255 in.reset();
256 in.skip( key.length() );
257 ch = in.read();
258
259 // escape means no luck, prevent parsing of the escaped character, and return
260 if ( inEscape )
261 {
262
263 if ( beginToken != null )
264 {
265 if ( !isPreserveEscapeString() )
266 {
267 key.setLength( 0 );
268 }
269 }
270
271 key.append( (char) ch );
272
273 replaceData = key.toString();
274 replaceIndex = key.length();
275
276 return read();
277
278 }
279
280 // no match means no luck, reset and return
281 if ( !foundToken )
282 {
283
284 in.reset();
285 return in.read();
286
287 }
288
289 // we're committed, find the end token, EOL or EOF
290
291 key.append( beginToken );
292 in.reset();
293 in.skip( beginToken.length() );
294 ch = in.read();
295
296 int endTokenSize = endToken.length();
297 int end = endTokenSize;
298 do
299 {
300 if ( ch == -1 )
301 {
302 break;
303 }
304 else if ( ch == '\n' && !supportMultiLineFiltering )
305 {
306 // EOL
307 key.append( (char) ch );
308 break;
309 }
310
311 key.append( (char) ch );
312
313 if ( ch == this.endToken.charAt( endTokenSize - end ) )
314 {
315 end--;
316 if ( end == 0 )
317 {
318 break;
319 }
320 }
321 else
322 {
323 end = endTokenSize;
324 }
325
326 ch = in.read();
327 }
328 while ( true );
329
330 // found endtoken? interpolate our key resolved above
331 String value = null;
332 if ( end == 0 )
333 {
334 try
335 {
336 if ( interpolateWithPrefixPattern )
337 {
338 value = interpolator.interpolate( key.toString(), "", recursionInterceptor );
339 }
340 else
341 {
342 value = interpolator.interpolate( key.toString(), recursionInterceptor );
343 }
344 }
345 catch ( InterpolationException e )
346 {
347 IllegalArgumentException error = new IllegalArgumentException( e.getMessage() );
348 error.initCause( e );
349
350 throw error;
351 }
352 }
353
354 // write away the value if present, otherwise the key unmodified
355 if ( value != null )
356 {
357 replaceData = value;
358 replaceIndex = value.length();
359 }
360 else
361 {
362 replaceData = key.toString();
363 replaceIndex = key.length();
364 }
365
366 if ( ch == -1 )
367 {
368 eof = true;
369 }
370 return read();
371
372 }
373
374 /**
375 * @return current state of interpolate with prefix pattern.
376 */
377 public boolean isInterpolateWithPrefixPattern()
378 {
379 return interpolateWithPrefixPattern;
380 }
381
382 /**
383 * @param interpolateWithPrefixPattern interpolate with prefix pattern.
384 */
385 public void setInterpolateWithPrefixPattern( boolean interpolateWithPrefixPattern )
386 {
387 this.interpolateWithPrefixPattern = interpolateWithPrefixPattern;
388 }
389
390 /**
391 * @return {@link #recursionInterceptor}
392 */
393 public RecursionInterceptor getRecursionInterceptor()
394 {
395 return recursionInterceptor;
396 }
397
398 /**
399 * @param theRecursionInterceptor {@link RecursionInterceptor}
400 * @return {@link InterpolatorFilterReaderLineEnding}
401 */
402 public InterpolatorFilterReaderLineEnding setRecursionInterceptor( RecursionInterceptor theRecursionInterceptor )
403 {
404 this.recursionInterceptor = theRecursionInterceptor;
405 return this;
406 }
407
408 }