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 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 }