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