View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.shared.filtering;
20  
21  import java.io.BufferedReader;
22  import java.io.IOException;
23  import java.io.Reader;
24  
25  import org.codehaus.plexus.interpolation.InterpolationException;
26  import org.codehaus.plexus.interpolation.Interpolator;
27  import org.codehaus.plexus.interpolation.RecursionInterceptor;
28  import org.codehaus.plexus.interpolation.SimpleRecursionInterceptor;
29  
30  /**
31   * A FilterReader implementation, that works with Interpolator interface instead of it's own interpolation
32   * implementation. This implementation is heavily based on org.codehaus.plexus.util.InterpolationFilterReader.
33   *
34   * @author cstamas
35   * @author Olivier Lamy
36   * @since 1.0
37   */
38  public class InterpolatorFilterReaderLineEnding extends AbstractFilterReaderLineEnding {
39  
40      /**
41       * Interpolator used to interpolate
42       */
43      private Interpolator interpolator;
44  
45      private RecursionInterceptor recursionInterceptor;
46  
47      /**
48       * replacement text from a token
49       */
50      private String replaceData = null;
51  
52      /**
53       * Index into replacement data
54       */
55      private int replaceIndex = 0;
56  
57      /**
58       * Default begin token.
59       */
60      public static final String DEFAULT_BEGIN_TOKEN = "${";
61  
62      /**
63       * Default end token.
64       */
65      public static final String DEFAULT_END_TOKEN = "}";
66  
67      private String beginToken;
68  
69      private String endToken;
70  
71      /**
72       * true by default to preserve backward comp
73       */
74      private boolean interpolateWithPrefixPattern = true;
75  
76      private boolean supportMultiLineFiltering;
77  
78      private boolean eof = false;
79  
80      /**
81       * @param in reader to use
82       * @param interpolator interpolator instance to use
83       * @param beginToken start token to use
84       * @param endToken end token to use
85       * @param supportMultiLineFiltering If multi line filtering is allowed
86       */
87      public InterpolatorFilterReaderLineEnding(
88              Reader in,
89              Interpolator interpolator,
90              String beginToken,
91              String endToken,
92              boolean supportMultiLineFiltering) {
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(
105             Reader in,
106             Interpolator interpolator,
107             String beginToken,
108             String endToken,
109             RecursionInterceptor ri,
110             boolean supportMultiLineFiltering) {
111         // wrap our own buffer, so we can use mark/reset safely.
112         super(new BufferedReader(in));
113 
114         this.interpolator = interpolator;
115 
116         this.beginToken = beginToken;
117 
118         this.endToken = endToken;
119 
120         recursionInterceptor = ri;
121 
122         this.supportMultiLineFiltering = supportMultiLineFiltering;
123 
124         calculateMarkLength();
125     }
126 
127     /**
128      * Skips characters. This method will block until some characters are available, an I/O error occurs, or the end of
129      * the stream is reached.
130      *
131      * @param n The number of characters to skip
132      * @return the number of characters actually skipped
133      * @throws IOException If an I/O error occurs
134      */
135     @Override
136     public long skip(long n) throws IOException {
137         if (n < 0L) {
138             throw new IllegalArgumentException("skip value is negative");
139         }
140 
141         for (long i = 0; i < n; i++) {
142             if (read() == -1) {
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      * @throws IOException If an I/O error occurs
158      */
159     @Override
160     public int read(char[] cbuf, int off, int len) throws IOException {
161         for (int i = 0; i < len; i++) {
162             int ch = read();
163             if (ch == -1) {
164                 if (i == 0) {
165                     return -1;
166                 } else {
167                     return i;
168                 }
169             }
170             cbuf[off + i] = (char) ch;
171         }
172         return len;
173     }
174 
175     /**
176      * Returns the next character in the filtered stream, replacing tokens from the original stream.
177      *
178      * @return the next character in the resulting stream, or -1 if the end of the resulting stream has been reached
179      * @throws IOException if the underlying stream throws an IOException during reading
180      */
181     @Override
182     public int read() throws IOException {
183         if (replaceIndex > 0) {
184             return replaceData.charAt(replaceData.length() - (replaceIndex--));
185         }
186         if (eof) {
187             return -1;
188         }
189 
190         in.mark(markLength);
191 
192         int ch = in.read();
193         if (ch == -1 || (ch == '\n' && !supportMultiLineFiltering)) {
194             return ch;
195         }
196 
197         boolean inEscape = useEscape && ch == getEscapeString().charAt(0);
198 
199         StringBuilder key = new StringBuilder();
200 
201         // have we found an escape string?
202         if (inEscape) {
203             for (int i = 0; i < getEscapeString().length(); i++) {
204                 key.append((char) ch);
205 
206                 if (ch != getEscapeString().charAt(i) || ch == -1 || (ch == '\n' && !supportMultiLineFiltering)) {
207                     // mismatch, EOF or EOL, no escape string here
208                     in.reset();
209                     inEscape = false;
210                     key.setLength(0);
211                     break;
212                 }
213 
214                 ch = in.read();
215             }
216         }
217 
218         // have we found a delimiter?
219         boolean foundToken = false;
220         for (int i = 0; i < beginToken.length(); i++) {
221             if (ch != beginToken.charAt(i) || ch == -1 || (ch == '\n' && !supportMultiLineFiltering)) {
222                 // mismatch, EOF or EOL, no match
223                 break;
224             }
225 
226             if (i == beginToken.length() - 1) {
227 
228                 foundToken = true;
229             }
230 
231             ch = in.read();
232         }
233 
234         in.reset();
235         in.skip(key.length());
236         ch = in.read();
237 
238         // escape means no luck, prevent parsing of the escaped character, and return
239         if (inEscape) {
240 
241             if (beginToken != null) {
242                 if (!isPreserveEscapeString()) {
243                     key.setLength(0);
244                 }
245             }
246 
247             key.append((char) ch);
248 
249             replaceData = key.toString();
250             replaceIndex = key.length();
251 
252             return read();
253         }
254 
255         // no match means no luck, reset and return
256         if (!foundToken) {
257 
258             in.reset();
259             return in.read();
260         }
261 
262         // we're committed, find the end token, EOL or EOF
263 
264         key.append(beginToken);
265         in.reset();
266         in.skip(beginToken.length());
267         ch = in.read();
268 
269         int endTokenSize = endToken.length();
270         int end = endTokenSize;
271         do {
272             if (ch == -1) {
273                 break;
274             } else if (ch == '\n' && !supportMultiLineFiltering) {
275                 // EOL
276                 key.append((char) ch);
277                 break;
278             }
279 
280             key.append((char) ch);
281 
282             if (ch == this.endToken.charAt(endTokenSize - end)) {
283                 end--;
284                 if (end == 0) {
285                     break;
286                 }
287             } else {
288                 end = endTokenSize;
289             }
290 
291             ch = in.read();
292         } while (true);
293 
294         // found endtoken? interpolate our key resolved above
295         String value = null;
296         if (end == 0) {
297             try {
298                 if (interpolateWithPrefixPattern) {
299                     value = interpolator.interpolate(key.toString(), "", recursionInterceptor);
300                 } else {
301                     value = interpolator.interpolate(key.toString(), recursionInterceptor);
302                 }
303             } catch (InterpolationException e) {
304                 throw new IllegalArgumentException(e.getMessage(), e);
305             }
306         }
307 
308         // write away the value if present, otherwise the key unmodified
309         if (value != null) {
310             replaceData = value;
311             replaceIndex = value.length();
312         } else {
313             replaceData = key.toString();
314             replaceIndex = key.length();
315         }
316 
317         if (ch == -1) {
318             eof = true;
319         }
320         return read();
321     }
322 
323     /**
324      * @return current state of interpolate with prefix pattern.
325      */
326     public boolean isInterpolateWithPrefixPattern() {
327         return interpolateWithPrefixPattern;
328     }
329 
330     /**
331      * @param interpolateWithPrefixPattern interpolate with prefix pattern.
332      */
333     public void setInterpolateWithPrefixPattern(boolean interpolateWithPrefixPattern) {
334         this.interpolateWithPrefixPattern = interpolateWithPrefixPattern;
335     }
336 
337     /**
338      * @return {@link #recursionInterceptor}
339      */
340     public RecursionInterceptor getRecursionInterceptor() {
341         return recursionInterceptor;
342     }
343 
344     /**
345      * @param theRecursionInterceptor {@link RecursionInterceptor}
346      * @return {@link InterpolatorFilterReaderLineEnding}
347      */
348     public InterpolatorFilterReaderLineEnding setRecursionInterceptor(RecursionInterceptor theRecursionInterceptor) {
349         this.recursionInterceptor = theRecursionInterceptor;
350         return this;
351     }
352 }