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