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