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