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 @Override
134 public long skip( long n )
135 throws IOException
136 {
137 if ( n < 0L )
138 {
139 throw new IllegalArgumentException( "skip value is negative" );
140 }
141
142 for ( long i = 0; i < n; i++ )
143 {
144 if ( read() == -1 )
145 {
146 return i;
147 }
148 }
149 return n;
150 }
151
152 /**
153 * Reads characters into a portion of an array. This method will block until some input is available, an I/O error
154 * occurs, or the end of the stream is reached.
155 *
156 * @param cbuf Destination buffer to write characters to. Must not be <code>null</code>.
157 * @param off Offset at which to start storing characters.
158 * @param len Maximum number of characters to read.
159 * @return the number of characters read, or -1 if the end of the stream has been reached
160 * @throws IOException If an I/O error occurs
161 */
162 @Override
163 public int read( char cbuf[], int off, int len )
164 throws IOException
165 {
166 for ( int i = 0; i < len; i++ )
167 {
168 int ch = read();
169 if ( ch == -1 )
170 {
171 if ( i == 0 )
172 {
173 return -1;
174 }
175 else
176 {
177 return i;
178 }
179 }
180 cbuf[off + i] = (char) ch;
181 }
182 return len;
183 }
184
185 /**
186 * Returns the next character in the filtered stream, replacing tokens from the original stream.
187 *
188 * @return the next character in the resulting stream, or -1 if the end of the resulting stream has been reached
189 * @throws IOException if the underlying stream throws an IOException during reading
190 */
191 @Override
192 public int read()
193 throws IOException
194 {
195 if ( replaceIndex > 0 )
196 {
197 return replaceData.charAt( replaceData.length() - ( replaceIndex-- ) );
198 }
199 if ( eof )
200 {
201 return -1;
202 }
203
204 in.mark( markLength );
205
206 int ch = in.read();
207 if ( ch == -1 || ( ch == '\n' && !supportMultiLineFiltering ) )
208 {
209 return ch;
210 }
211
212 boolean inEscape = useEscape && ch == getEscapeString().charAt( 0 );
213
214 StringBuilder key = new StringBuilder();
215
216 // have we found an escape string?
217 if ( inEscape )
218 {
219 for ( int i = 0; i < getEscapeString().length(); i++ )
220 {
221 key.append( (char) ch );
222
223 if ( ch != getEscapeString().charAt( i ) || ch == -1 || ( ch == '\n' && !supportMultiLineFiltering ) )
224 {
225 // mismatch, EOF or EOL, no escape string here
226 in.reset();
227 inEscape = false;
228 key.setLength( 0 );
229 break;
230 }
231
232 ch = in.read();
233 }
234
235 }
236
237 // have we found a delimiter?
238 boolean foundToken = false;
239 for ( int i = 0; i < beginToken.length(); i++ )
240 {
241 if ( ch != beginToken.charAt( i ) || ch == -1 || ( ch == '\n' && !supportMultiLineFiltering ) )
242 {
243 // mismatch, EOF or EOL, no match
244 break;
245 }
246
247 if ( i == beginToken.length() - 1 )
248 {
249
250 foundToken = true;
251
252 }
253
254 ch = in.read();
255
256 }
257
258 in.reset();
259 in.skip( key.length() );
260 ch = in.read();
261
262 // escape means no luck, prevent parsing of the escaped character, and return
263 if ( inEscape )
264 {
265
266 if ( beginToken != null )
267 {
268 if ( !isPreserveEscapeString() )
269 {
270 key.setLength( 0 );
271 }
272 }
273
274 key.append( (char) ch );
275
276 replaceData = key.toString();
277 replaceIndex = key.length();
278
279 return read();
280
281 }
282
283 // no match means no luck, reset and return
284 if ( !foundToken )
285 {
286
287 in.reset();
288 return in.read();
289
290 }
291
292 // we're committed, find the end token, EOL or EOF
293
294 key.append( beginToken );
295 in.reset();
296 in.skip( beginToken.length() );
297 ch = in.read();
298
299 int endTokenSize = endToken.length();
300 int end = endTokenSize;
301 do
302 {
303 if ( ch == -1 )
304 {
305 break;
306 }
307 else if ( ch == '\n' && !supportMultiLineFiltering )
308 {
309 // EOL
310 key.append( (char) ch );
311 break;
312 }
313
314 key.append( (char) ch );
315
316 if ( ch == this.endToken.charAt( endTokenSize - end ) )
317 {
318 end--;
319 if ( end == 0 )
320 {
321 break;
322 }
323 }
324 else
325 {
326 end = endTokenSize;
327 }
328
329 ch = in.read();
330 }
331 while ( true );
332
333 // found endtoken? interpolate our key resolved above
334 String value = null;
335 if ( end == 0 )
336 {
337 try
338 {
339 if ( interpolateWithPrefixPattern )
340 {
341 value = interpolator.interpolate( key.toString(), "", recursionInterceptor );
342 }
343 else
344 {
345 value = interpolator.interpolate( key.toString(), recursionInterceptor );
346 }
347 }
348 catch ( InterpolationException e )
349 {
350 IllegalArgumentException error = new IllegalArgumentException( e.getMessage() );
351 error.initCause( e );
352
353 throw error;
354 }
355 }
356
357 // write away the value if present, otherwise the key unmodified
358 if ( value != null )
359 {
360 replaceData = value;
361 replaceIndex = value.length();
362 }
363 else
364 {
365 replaceData = key.toString();
366 replaceIndex = key.length();
367 }
368
369 if ( ch == -1 )
370 {
371 eof = true;
372 }
373 return read();
374
375 }
376
377 /**
378 * @return current state of interpolate with prefix pattern.
379 */
380 public boolean isInterpolateWithPrefixPattern()
381 {
382 return interpolateWithPrefixPattern;
383 }
384
385 /**
386 * @param interpolateWithPrefixPattern interpolate with prefix pattern.
387 */
388 public void setInterpolateWithPrefixPattern( boolean interpolateWithPrefixPattern )
389 {
390 this.interpolateWithPrefixPattern = interpolateWithPrefixPattern;
391 }
392
393 /**
394 * @return {@link #recursionInterceptor}
395 */
396 public RecursionInterceptor getRecursionInterceptor()
397 {
398 return recursionInterceptor;
399 }
400
401 /**
402 * @param theRecursionInterceptor {@link RecursionInterceptor}
403 * @return {@link InterpolatorFilterReaderLineEnding}
404 */
405 public InterpolatorFilterReaderLineEnding setRecursionInterceptor( RecursionInterceptor theRecursionInterceptor )
406 {
407 this.recursionInterceptor = theRecursionInterceptor;
408 return this;
409 }
410
411 }