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