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.FilterReader;
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   * @version $Id: InterpolatorFilterReaderLineEnding.java 1057398 2011-01-10 22:18:08Z dennisl $
38   * @since 1.0
39   */
40  public class InterpolatorFilterReaderLineEnding
41      extends FilterReader
42  {
43  
44      /** Interpolator used to interpolate */
45      private Interpolator interpolator;
46  
47      private RecursionInterceptor recursionInterceptor;
48  
49      /** replacement text from a token */
50      private String replaceData = null;
51  
52      /** Index into replacement data */
53      private int replaceIndex = -1;
54  
55      /** Index into previous data */
56      private int previousIndex = -1;
57  
58      /** Default begin token. */
59      public static final String DEFAULT_BEGIN_TOKEN = "${";
60  
61      /** Default end token. */
62      public static final String DEFAULT_END_TOKEN = "}";
63      
64      private String beginToken;
65      
66      private String orginalBeginToken;
67      
68      private String endToken;
69      
70      /** true by default to preserve backward comp */
71      private boolean interpolateWithPrefixPattern = true;
72  
73      private String escapeString;
74      
75      private boolean useEscape = false;
76      
77      /** if true escapeString will be preserved \{foo} -> \{foo} */
78      private boolean preserveEscapeString = false;
79      
80      private boolean supportMultiLineFiltering;
81          
82      /**
83       * @param in reader to use
84       * @param interpolator interpolator instance to use
85       * @param beginToken start token to use
86       * @param endToken end token to use
87       * @param supportMultiLineFiltering If multi line filtering is allowed
88       */
89      public InterpolatorFilterReaderLineEnding( Reader in, Interpolator interpolator, String beginToken, String endToken,
90                                                 boolean supportMultiLineFiltering )
91      {
92          this( in, interpolator, beginToken, endToken, new SimpleRecursionInterceptor(), supportMultiLineFiltering );
93      }    
94      
95      /**
96       * @param in reader to use
97       * @param interpolator interpolator instance to use
98       * @param beginToken start token to use
99       * @param endToken end token to use
100      * @param ri The {@link RecursionInterceptor} to use to prevent recursive expressions.
101      * @param supportMultiLineFiltering If multi line filtering is allowed
102      */
103     private InterpolatorFilterReaderLineEnding( Reader in, Interpolator interpolator, String beginToken,
104                                                 String endToken, RecursionInterceptor ri,
105                                                 boolean supportMultiLineFiltering )
106     {
107         super( in );
108 
109         this.interpolator = interpolator;
110         
111         this.beginToken = beginToken;
112         
113         this.endToken = endToken;
114         
115         recursionInterceptor = ri;
116         
117         this.orginalBeginToken = this.beginToken;
118         
119         this.supportMultiLineFiltering = supportMultiLineFiltering;
120     }    
121 
122     /**
123      * Skips characters. This method will block until some characters are available, an I/O error occurs, or the end of
124      * the stream is reached.
125      *
126      * @param n The number of characters to skip
127      * @return the number of characters actually skipped
128      * @exception IllegalArgumentException If <code>n</code> is negative.
129      * @exception IOException If an I/O error occurs
130      */
131     public long skip( long n )
132         throws IOException
133     {
134         if ( n < 0L )
135         {
136             throw new IllegalArgumentException( "skip value is negative" );
137         }
138 
139         for ( long i = 0; i < n; i++ )
140         {
141             if ( read() == -1 )
142             {
143                 return i;
144             }
145         }
146         return n;
147     }
148 
149     /**
150      * Reads characters into a portion of an array. This method will block until some input is available, an I/O error
151      * occurs, or the end of the stream is reached.
152      *
153      * @param cbuf Destination buffer to write characters to. Must not be <code>null</code>.
154      * @param off Offset at which to start storing characters.
155      * @param len Maximum number of characters to read.
156      * @return the number of characters read, or -1 if the end of the stream has been reached
157      * @exception IOException If an I/O error occurs
158      */
159     public int read( char cbuf[], int off, int len )
160         throws IOException
161     {
162         for ( int i = 0; i < len; i++ )
163         {
164             int ch = read();
165             if ( ch == -1 )
166             {
167                 if ( i == 0 )
168                 {
169                     return -1;
170                 }
171                 else
172                 {
173                     return i;
174                 }
175             }
176             cbuf[off + i] = (char) ch;
177         }
178         return len;
179     }
180 
181     /**
182      * Returns the next character in the filtered stream, replacing tokens from the original stream.
183      *
184      * @return the next character in the resulting stream, or -1 if the end of the resulting stream has been reached
185      * @exception IOException if the underlying stream throws an IOException during reading
186      */
187     public int read()
188         throws IOException
189     {
190         if ( replaceIndex != -1 && replaceIndex < replaceData.length() )
191         {
192             int ch = replaceData.charAt( replaceIndex++ );
193             if ( replaceIndex >= replaceData.length() )
194             {
195                 replaceIndex = -1;
196             }
197             return ch;
198         }
199 
200         int ch = -1;
201         if ( previousIndex != -1 && previousIndex < this.endToken.length() )
202         {
203             ch = this.endToken.charAt( previousIndex++ );
204         }
205         else
206         {
207             ch = in.read();
208         }
209         
210         if ( ch == '\n' && !supportMultiLineFiltering )
211         {
212             previousIndex = -1;
213             return ch;
214         }        
215         
216         if ( ch == this.beginToken.charAt( 0 ) || ( useEscape && ch == this.orginalBeginToken.charAt( 0 ) ) )
217         {
218             StringBuffer key = new StringBuffer( );
219 
220             key.append( (char) ch );
221 
222             int beginTokenMatchPos = 1;
223 
224             do
225             {
226                 if ( previousIndex != -1 && previousIndex < this.endToken.length() )
227                 {
228                     ch = this.endToken.charAt( previousIndex++ );
229                 }
230                 else
231                 {
232                     ch = in.read();
233                 }
234                 if ( ch != -1 && ( ch != '\n' && !supportMultiLineFiltering ) )
235                 {
236                     key.append( (char) ch );
237                     if ( ( beginTokenMatchPos < this.beginToken.length() )
238                         && ( ch != this.beginToken.charAt( beginTokenMatchPos++ ) )
239                         && ( useEscape && this.orginalBeginToken.length() > ( beginTokenMatchPos - 1 )
240                         && ch != this.orginalBeginToken.charAt( beginTokenMatchPos - 1 ) ) )
241                     {
242                         ch = -1; // not really EOF but to trigger code below
243                         break;
244                     }
245                 }
246                 else
247                 {
248                     break;
249                 }
250                 // MSHARED-81 olamy : we must take care of token with length 1, escaping and same char : \@foo@
251                 // here ch == endToken == beginToken -> not going to next char : bad :-)
252                 if ( useEscape
253                     && this.orginalBeginToken == this.endToken && key.toString().startsWith( this.beginToken ) )
254                 {
255                     ch = in.read();
256                     key.append( (char) ch );
257                 }
258             }
259             while ( ch != this.endToken.charAt( 0 ) );
260 
261             // now test endToken
262             if ( ch != -1 && this.endToken.length() > 1 )
263             {
264                 int endTokenMatchPos = 1;
265 
266                 do
267                 {
268                     if ( previousIndex != -1 && previousIndex < this.endToken.length() )
269                     {
270                         ch = this.endToken.charAt( previousIndex++ );
271                     }
272                     else
273                     {
274                         ch = in.read();
275                     }
276 
277                     if ( ch != -1 )
278                     {
279                         key.append( (char) ch );
280 
281                         if ( ch != this.endToken.charAt( endTokenMatchPos++ ) )
282                         {
283                             ch = -1; // not really EOF but to trigger code below
284                             break;
285                         }
286 
287                     }
288                     else
289                     {
290                         break;
291                     }
292                 }
293                 while ( endTokenMatchPos < this.endToken.length() );
294             }
295 
296             // There is nothing left to read so we have the situation where the begin/end token
297             // are in fact the same and as there is nothing left to read we have got ourselves
298             // end of a token boundary so let it pass through.
299             if ( ch == -1 || ( ch == '\n' && !supportMultiLineFiltering ) )
300             {
301                 replaceData = key.toString();
302                 replaceIndex = 1;
303                 return replaceData.charAt( 0 );
304             }
305 
306             String value = null;
307             try
308             {
309                 boolean escapeFound = false;
310                 if ( useEscape )
311                 {
312                     if ( key.toString().startsWith( escapeString + orginalBeginToken ) )
313                     {
314                         String keyStr = key.toString();
315                         if ( !preserveEscapeString )
316                         {
317                             value = keyStr.substring( escapeString.length(), keyStr.length() );
318                         }
319                         else
320                         {
321                             value = keyStr;
322                         }
323                         escapeFound = true;
324                     }
325                 }
326                 if ( !escapeFound )
327                 {
328                     if ( interpolateWithPrefixPattern )
329                     {
330                         value = interpolator.interpolate( key.toString(), "", recursionInterceptor );
331                     }
332                     else
333                     {
334                         value = interpolator.interpolate( key.toString(), recursionInterceptor );
335                     }
336                 }
337             }
338             catch ( InterpolationException e )
339             {
340                 IllegalArgumentException error = new IllegalArgumentException( e.getMessage() );
341                 error.initCause( e );
342 
343                 throw error;
344             }
345 
346             if ( value != null )
347             {
348                 if ( value.length() != 0 )
349                 {
350                     replaceData = value;
351                     replaceIndex = 0;
352                 }
353                 return read();
354             }
355             else
356             {
357                 previousIndex = 0;
358                 replaceData = key.substring( 0, key.length() - this.endToken.length() );
359                 replaceIndex = 0;
360                 return this.beginToken.charAt( 0 );
361             }
362         }
363 
364         return ch;
365     }
366 
367     public boolean isInterpolateWithPrefixPattern()
368     {
369         return interpolateWithPrefixPattern;
370     }
371 
372     public void setInterpolateWithPrefixPattern( boolean interpolateWithPrefixPattern )
373     {
374         this.interpolateWithPrefixPattern = interpolateWithPrefixPattern;
375     }
376     public String getEscapeString()
377     {
378         return escapeString;
379     }
380 
381     public void setEscapeString( String escapeString )
382     {
383         // TODO NPE if escapeString is null ?
384         if ( escapeString != null && escapeString.length() >= 1 )
385         {
386             this.escapeString = escapeString;
387             this.orginalBeginToken = beginToken;
388             this.beginToken = escapeString + beginToken;
389             this.useEscape = escapeString != null && escapeString.length() >= 1;
390         }
391     }
392 
393     public boolean isPreserveEscapeString()
394     {
395         return preserveEscapeString;
396     }
397 
398     public void setPreserveEscapeString( boolean preserveEscapeString )
399     {
400         this.preserveEscapeString = preserveEscapeString;
401     }
402 
403     public RecursionInterceptor getRecursionInterceptor()
404     {
405         return recursionInterceptor;
406     }
407 
408     public InterpolatorFilterReaderLineEnding setRecursionInterceptor( RecursionInterceptor recursionInterceptor )
409     {
410         this.recursionInterceptor = recursionInterceptor;
411         return this;
412     }
413 }