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
235 // have we found a delimiter?
236 boolean foundToken = false;
237 for ( int i = 0; i < beginToken.length(); i++ )
238 {
239 if ( ch != beginToken.charAt( i ) || ch == -1 || ( ch == '\n' && !supportMultiLineFiltering ) )
240 {
241 // mismatch, EOF or EOL, no match
242 break;
243 }
244
245 if ( i == beginToken.length() - 1 )
246 {
247
248 foundToken = true;
249
250 }
251
252 ch = in.read();
253
254 }
255
256 in.reset();
257 in.skip( key.length() );
258 ch = in.read();
259
260 // escape means no luck, prevent parsing of the escaped character, and return
261 if ( inEscape )
262 {
263
264 if ( beginToken != null )
265 {
266 if ( !isPreserveEscapeString() )
267 {
268 key.setLength( 0 );
269 }
270 }
271
272 key.append( (char) ch );
273
274 replaceData = key.toString();
275 replaceIndex = key.length();
276
277 return read();
278
279 }
280
281 // no match means no luck, reset and return
282 if ( !foundToken )
283 {
284
285 in.reset();
286 return in.read();
287
288 }
289
290 // we're committed, find the end token, EOL or EOF
291
292 key.append( beginToken );
293 in.reset();
294 in.skip( beginToken.length() );
295 ch = in.read();
296
297 int endTokenSize = endToken.length();
298 int end = endTokenSize;
299 do
300 {
301 if ( ch == -1 )
302 {
303 break;
304 }
305 else if ( ch == '\n' && !supportMultiLineFiltering )
306 {
307 // EOL
308 key.append( (char) ch );
309 break;
310 }
311
312 key.append( (char) ch );
313
314 if ( ch == this.endToken.charAt( endTokenSize - end ) )
315 {
316 end--;
317 if ( end == 0 )
318 {
319 break;
320 }
321 }
322 else
323 {
324 end = endTokenSize;
325 }
326
327 ch = in.read();
328 }
329 while ( true );
330
331 // found endtoken? interpolate our key resolved above
332 String value = null;
333 if ( end == 0 )
334 {
335 try
336 {
337 if ( interpolateWithPrefixPattern )
338 {
339 value = interpolator.interpolate( key.toString(), "", recursionInterceptor );
340 }
341 else
342 {
343 value = interpolator.interpolate( key.toString(), recursionInterceptor );
344 }
345 }
346 catch ( InterpolationException e )
347 {
348 IllegalArgumentException error = new IllegalArgumentException( e.getMessage() );
349 error.initCause( e );
350
351 throw error;
352 }
353 }
354
355 // write away the value if present, otherwise the key unmodified
356 if ( value != null )
357 {
358 replaceData = value;
359 replaceIndex = value.length();
360 }
361 else
362 {
363 replaceData = key.toString();
364 replaceIndex = key.length();
365 }
366
367 if ( ch == -1 )
368 {
369 eof = true;
370 }
371 return read();
372
373 }
374
375 /**
376 * @return current state of interpolate with prefix pattern.
377 */
378 public boolean isInterpolateWithPrefixPattern()
379 {
380 return interpolateWithPrefixPattern;
381 }
382
383 /**
384 * @param interpolateWithPrefixPattern interpolate with prefix pattern.
385 */
386 public void setInterpolateWithPrefixPattern( boolean interpolateWithPrefixPattern )
387 {
388 this.interpolateWithPrefixPattern = interpolateWithPrefixPattern;
389 }
390
391 /**
392 * @return {@link #recursionInterceptor}
393 */
394 public RecursionInterceptor getRecursionInterceptor()
395 {
396 return recursionInterceptor;
397 }
398
399 /**
400 * @param theRecursionInterceptor {@link RecursionInterceptor}
401 * @return {@link InterpolatorFilterReaderLineEnding}
402 */
403 public InterpolatorFilterReaderLineEnding setRecursionInterceptor( RecursionInterceptor theRecursionInterceptor )
404 {
405 this.recursionInterceptor = theRecursionInterceptor;
406 return this;
407 }
408
409 }