View Javadoc
1   package org.codehaus.plexus.interpolation;
2   
3   /*
4    * Copyright 2001-2008 Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * 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, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.WeakHashMap;
24  import java.util.regex.Matcher;
25  import java.util.regex.Pattern;
26  
27  import org.codehaus.plexus.interpolation.util.StringUtils;
28  
29  /**
30   * Expansion of the original RegexBasedInterpolator, found in plexus-utils, this
31   * interpolator provides options for setting custom prefix/suffix regex parts,
32   * and includes a {@link RecursionInterceptor} parameter in its interpolate(..)
33   * call, to allow the detection of cyclical expression references.
34   *
35   * @version $Id$
36   */
37  public class RegexBasedInterpolator
38      implements Interpolator
39  {
40  
41      private String startRegex;
42  
43      private String endRegex;
44      
45      private Map existingAnswers = new HashMap();
46  
47      private List<ValueSource> valueSources = new ArrayList<ValueSource>();
48      
49      private List<InterpolationPostProcessor> postProcessors = new ArrayList<InterpolationPostProcessor>();
50      
51      private boolean reusePatterns = false;
52      
53      private boolean cacheAnswers = false;
54      
55      public static final String DEFAULT_REGEXP = "\\$\\{(.+?)\\}";
56      
57      /**
58       * the key is the regex the value is the Pattern 
59       * At the class construction time the Map will contains the default Pattern
60       */
61      private Map<String,Pattern> compiledPatterns = new WeakHashMap<String,Pattern>();
62      
63      /**
64       * Setup a basic interpolator.
65       * <br/>
66       * <b>NOTE:</b> You will have to call
67       * {@link RegexBasedInterpolator#addValueSource(ValueSource)} at least once
68       * if you use this constructor!
69       */
70      public RegexBasedInterpolator()
71      {
72          compiledPatterns.put( DEFAULT_REGEXP, Pattern.compile( DEFAULT_REGEXP ) );
73      }
74      
75      /**
76  
77       * @param reusePatterns already compiled patterns will be reused
78       */
79      public RegexBasedInterpolator( boolean reusePatterns )
80      {
81          this();
82          this.reusePatterns = reusePatterns;
83      }    
84  
85      /**
86       * Setup an interpolator with no value sources, and the specified regex pattern
87       * prefix and suffix in place of the default one.
88       * <br/>
89       * <b>NOTE:</b> You will have to call
90       * {@link RegexBasedInterpolator#addValueSource(ValueSource)} at least once
91       * if you use this constructor!
92       *
93       * @param startRegex start of the regular expression to use
94       * @param endRegex end of the regular expression to use
95       */
96      public RegexBasedInterpolator (String startRegex, String endRegex)
97      {
98          this();
99          this.startRegex = startRegex;
100         this.endRegex = endRegex;
101     }
102 
103     /**
104      * Setup a basic interpolator with the specified list of value sources.
105      *
106      * @param valueSources The list of value sources to use
107      */
108     public RegexBasedInterpolator( List valueSources )
109     {
110         this();
111         this.valueSources.addAll( valueSources );
112     }
113 
114     /**
115      * Setup an interpolator with the specified value sources, and the specified
116      * regex pattern prefix and suffix in place of the default one.
117      *
118      * @param startRegex start of the regular expression to use
119      * @param endRegex end of the regular expression to use
120      * @param valueSources The list of value sources to use
121      */
122     public RegexBasedInterpolator (String startRegex, String endRegex, List valueSources )
123     {
124         this();
125         this.startRegex = startRegex;
126         this.endRegex = endRegex;
127         this.valueSources.addAll( valueSources );
128     }
129 
130     /**
131      * {@inheritDoc}
132      */
133     public void addValueSource( ValueSource valueSource )
134     {
135         valueSources.add( valueSource );
136     }
137 
138     /**
139      * {@inheritDoc}
140      */
141     public void removeValuesSource( ValueSource valueSource )
142     {
143         valueSources.remove( valueSource );
144     }
145 
146     /**
147      * {@inheritDoc}
148      */
149     public void addPostProcessor( InterpolationPostProcessor postProcessor )
150     {
151         postProcessors.add( postProcessor );
152     }
153 
154     /**
155      * {@inheritDoc}
156      */
157     public void removePostProcessor( InterpolationPostProcessor postProcessor )
158     {
159         postProcessors.remove( postProcessor  );
160     }
161 
162     /**
163      * Attempt to resolve all expressions in the given input string, using the
164      * given pattern to first trim an optional prefix from each expression. The
165      * supplied recursion interceptor will provide protection from expression
166      * cycles, ensuring that the input can be resolved or an exception is
167      * thrown.
168      *
169      * @param input The input string to interpolate
170      *
171      * @param thisPrefixPattern An optional pattern that should be trimmed from
172      *                          the start of any expressions found in the input.
173      *
174      * @param recursionInterceptor Used to protect the interpolation process
175      *                             from expression cycles, and throw an
176      *                             exception if one is detected.
177      */
178     public String interpolate( String input,
179                                String thisPrefixPattern,
180                                RecursionInterceptor recursionInterceptor )
181         throws InterpolationException
182     {
183         if (input == null )
184         {
185             // return empty String to prevent NPE too
186             return "";
187         }
188         if ( recursionInterceptor == null )
189         {
190             recursionInterceptor = new SimpleRecursionInterceptor();
191         }
192 
193         if ( thisPrefixPattern != null && thisPrefixPattern.length() == 0 )
194         {
195             thisPrefixPattern = null;
196         }
197 
198         int realExprGroup = 2;
199         Pattern expressionPattern;
200         if ( startRegex != null || endRegex != null )
201         {
202             if ( thisPrefixPattern == null )
203             {
204                 expressionPattern = getPattern( startRegex + endRegex );
205                 realExprGroup = 1;
206             }
207             else
208             {
209                 expressionPattern = getPattern( startRegex + thisPrefixPattern + endRegex );
210             }
211 
212         }
213         else if ( thisPrefixPattern != null )
214         {
215             expressionPattern = getPattern( "\\$\\{(" + thisPrefixPattern + ")?(.+?)\\}" );
216         }
217         else
218         {
219             expressionPattern = getPattern( DEFAULT_REGEXP );
220             realExprGroup = 1;
221         }
222 
223         try
224         {
225             return interpolate( input, recursionInterceptor, expressionPattern, realExprGroup );
226         }
227         finally
228         {
229             if ( !cacheAnswers )
230             {
231                 clearAnswers();
232             }
233         }
234     }
235     
236     private Pattern getPattern( String regExp )
237     {
238         if ( !reusePatterns )
239         {
240             return Pattern.compile( regExp );
241         }
242            
243         Pattern pattern;
244         synchronized( this )
245         {
246             pattern = compiledPatterns.get( regExp );
247             
248             if ( pattern != null )
249             {
250                 return pattern;
251             }
252 
253             pattern = Pattern.compile( regExp );
254             compiledPatterns.put( regExp, pattern );
255         }
256         
257         return pattern;
258     }
259 
260     /**
261      * Entry point for recursive resolution of an expression and all of its
262      * nested expressions.
263      *
264      * @todo Ensure unresolvable expressions don't trigger infinite recursion.
265      */
266     private String interpolate( String input,
267                                 RecursionInterceptor recursionInterceptor,
268                                 Pattern expressionPattern,
269                                 int realExprGroup )
270         throws InterpolationException
271     {
272         if (input == null )
273         {
274             // return empty String to prevent NPE too
275             return "";
276         }        
277         String result = input;
278         
279         Matcher matcher = expressionPattern.matcher( result );
280 
281         while ( matcher.find() )
282         {
283             String wholeExpr = matcher.group( 0 );
284             String realExpr = matcher.group( realExprGroup );
285 
286             if ( realExpr.startsWith( "." ) )
287             {
288                 realExpr = realExpr.substring( 1 );
289             }
290 
291             if ( recursionInterceptor.hasRecursiveExpression( realExpr ) )
292             {
293                 throw new InterpolationCycleException( recursionInterceptor, realExpr, wholeExpr );
294             }
295 
296             recursionInterceptor.expressionResolutionStarted( realExpr );
297             try
298             {
299                 Object value = existingAnswers.get( realExpr );
300                 for ( ValueSource vs : valueSources )
301                 {
302                    if (value != null) break;
303 
304                     value = vs.getValue( realExpr );
305                 }
306 
307                 if ( value != null )
308                 {
309                     value =
310                         interpolate( String.valueOf( value ), recursionInterceptor, expressionPattern, realExprGroup );
311 
312                     if ( postProcessors != null && !postProcessors.isEmpty() )
313                     {
314                         for ( InterpolationPostProcessor postProcessor : postProcessors )
315                         {
316                             Object newVal = postProcessor.execute( realExpr, value );
317                             if ( newVal != null )
318                             {
319                                 value = newVal;
320                                 break;
321                             }
322                         }
323                     }
324 
325                     // could use:
326                     // result = matcher.replaceFirst( stringValue );
327                     // but this could result in multiple lookups of stringValue, and replaceAll is not correct behaviour
328                     result = StringUtils.replace( result, wholeExpr, String.valueOf( value ) );
329 
330                     matcher.reset( result );
331                 }
332             }
333             finally
334             {
335                 recursionInterceptor.expressionResolutionFinished( realExpr );
336             }
337         }
338         
339         return result;
340     }
341 
342     /**
343      * Return any feedback messages and errors that were generated - but
344      * suppressed - during the interpolation process. Since unresolvable
345      * expressions will be left in the source string as-is, this feedback is
346      * optional, and will only be useful for debugging interpolation problems.
347      *
348      * @return a {@link List} that may be interspersed with {@link String} and
349      * {@link Throwable} instances.
350      */
351     public List getFeedback()
352     {
353         List messages = new ArrayList();
354         for ( Object valueSource : valueSources )
355         {
356             ValueSource vs = (ValueSource) valueSource;
357             List feedback = vs.getFeedback();
358             if ( feedback != null && !feedback.isEmpty() )
359             {
360                 messages.addAll( feedback );
361             }
362         }
363 
364         return messages;
365     }
366 
367     /**
368      * Clear the feedback messages from previous interpolate(..) calls.
369      */
370     public void clearFeedback()
371     {
372         for ( Object valueSource : valueSources )
373         {
374             ValueSource vs = (ValueSource) valueSource;
375             vs.clearFeedback();
376         }
377     }
378 
379     /**
380      * See {@link RegexBasedInterpolator#interpolate(String, String, RecursionInterceptor)}.
381      * <br/>
382      * This method triggers the use of a {@link SimpleRecursionInterceptor}
383      * instance for protection against expression cycles.
384      *
385      * @param input The input string to interpolate
386      *
387      * @param thisPrefixPattern An optional pattern that should be trimmed from
388      *                          the start of any expressions found in the input.
389      */
390     public String interpolate( String input,
391                                String thisPrefixPattern )
392         throws InterpolationException
393     {
394         return interpolate( input, thisPrefixPattern, null );
395     }
396 
397     /**
398      * See {@link RegexBasedInterpolator#interpolate(String, String, RecursionInterceptor)}.
399      * <br/>
400      * This method triggers the use of a {@link SimpleRecursionInterceptor}
401      * instance for protection against expression cycles. It also leaves empty the
402      * expression prefix which would otherwise be trimmed from expressions. The
403      * result is that any detected expression will be resolved as-is.
404      *
405      * @param input The input string to interpolate
406      */
407     public String interpolate( String input )
408         throws InterpolationException
409     {
410         return interpolate( input, null, null );
411     }
412 
413     /**
414      * See {@link RegexBasedInterpolator#interpolate(String, String, RecursionInterceptor)}.
415      * <br/>
416      * This method leaves empty the expression prefix which would otherwise be
417      * trimmed from expressions. The result is that any detected expression will
418      * be resolved as-is.
419      *
420      * @param input The input string to interpolate
421      *
422      * @param recursionInterceptor Used to protect the interpolation process
423      *                             from expression cycles, and throw an
424      *                             exception if one is detected.
425      */
426     public String interpolate( String input,
427                                RecursionInterceptor recursionInterceptor )
428         throws InterpolationException
429     {
430         return interpolate( input, null, recursionInterceptor );
431     }
432 
433     public boolean isReusePatterns()
434     {
435         return reusePatterns;
436     }
437 
438     public void setReusePatterns( boolean reusePatterns )
439     {
440         this.reusePatterns = reusePatterns;
441     }
442 
443     public boolean isCacheAnswers()
444     {
445         return cacheAnswers;
446     }
447 
448     public void setCacheAnswers( boolean cacheAnswers )
449     {
450         this.cacheAnswers = cacheAnswers;
451     }
452     
453     public void clearAnswers()
454     {
455         existingAnswers.clear();
456     }
457 
458 }