View Javadoc
1   package org.codehaus.plexus.interpolation.multi;
2   
3   /*
4    * Copyright 2001-2009 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 org.codehaus.plexus.interpolation.InterpolationCycleException;
20  import org.codehaus.plexus.interpolation.InterpolationException;
21  import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
22  import org.codehaus.plexus.interpolation.Interpolator;
23  import org.codehaus.plexus.interpolation.RecursionInterceptor;
24  import org.codehaus.plexus.interpolation.SimpleRecursionInterceptor;
25  import org.codehaus.plexus.interpolation.ValueSource;
26  
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.LinkedHashSet;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  
35  public class MultiDelimiterStringSearchInterpolator
36      implements Interpolator
37  {
38  
39      private static final int MAX_TRIES = 10;
40      
41      private Map existingAnswers = new HashMap();
42  
43      private List<ValueSource> valueSources = new ArrayList<ValueSource>();
44  
45      private List postProcessors = new ArrayList();
46  
47      private boolean cacheAnswers = false;
48  
49      private LinkedHashSet<DelimiterSpecification> delimiters = new LinkedHashSet<DelimiterSpecification>();
50  
51      private String escapeString;
52  
53      public MultiDelimiterStringSearchInterpolator()
54      {
55          delimiters.add( DelimiterSpecification.DEFAULT_SPEC );
56      }
57      
58      public MultiDelimiterStringSearchInterpolator addDelimiterSpec( String delimiterSpec )
59      {
60          if ( delimiterSpec == null )
61          {
62              return this;
63          }
64          delimiters.add( DelimiterSpecification.parse( delimiterSpec ) );
65          return this;
66      }
67      
68      public boolean removeDelimiterSpec( String delimiterSpec )
69      {
70          if ( delimiterSpec == null )
71          {
72              return false;
73          }        
74          return delimiters.remove( DelimiterSpecification.parse( delimiterSpec ) );
75      }
76      
77      public MultiDelimiterStringSearchInterpolator withValueSource( ValueSource vs )
78      {
79          addValueSource( vs );
80          return this;
81      }
82      
83      public MultiDelimiterStringSearchInterpolator withPostProcessor( InterpolationPostProcessor postProcessor )
84      {
85          addPostProcessor( postProcessor );
86          return this;
87      }
88  
89      /**
90       * {@inheritDoc}
91       */
92      public void addValueSource( ValueSource valueSource )
93      {
94          valueSources.add( valueSource );
95      }
96  
97      /**
98       * {@inheritDoc}
99       */
100     public void removeValuesSource( ValueSource valueSource )
101     {
102         valueSources.remove( valueSource );
103     }
104 
105     /**
106      * {@inheritDoc}
107      */
108     public void addPostProcessor( InterpolationPostProcessor postProcessor )
109     {
110         postProcessors.add( postProcessor );
111     }
112 
113     /**
114      * {@inheritDoc}
115      */
116     public void removePostProcessor( InterpolationPostProcessor postProcessor )
117     {
118         postProcessors.remove( postProcessor );
119     }
120 
121     public String interpolate( String input, String thisPrefixPattern )
122         throws InterpolationException
123     {
124         return interpolate( input, new SimpleRecursionInterceptor() );
125     }
126 
127     public String interpolate( String input, String thisPrefixPattern, RecursionInterceptor recursionInterceptor )
128         throws InterpolationException
129     {
130         return interpolate( input, recursionInterceptor );
131     }
132 
133     public String interpolate( String input )
134         throws InterpolationException
135     {
136         return interpolate( input, new SimpleRecursionInterceptor() );
137     }
138 
139     /**
140      * Entry point for recursive resolution of an expression and all of its nested expressions.
141      * 
142      * TODO: Ensure unresolvable expressions don't trigger infinite recursion.
143      */
144     public String interpolate( String input, RecursionInterceptor recursionInterceptor )
145         throws InterpolationException
146     {
147         try
148         {
149             return interpolate( input, recursionInterceptor, new HashSet() );
150         }
151         finally
152         {
153             if ( !cacheAnswers )
154             {
155                 existingAnswers.clear();
156             }
157         }
158     }
159 
160     private String interpolate( String input, RecursionInterceptor recursionInterceptor, Set<String> unresolvable )
161         throws InterpolationException
162     {
163         if ( input == null )
164         {
165             // return empty String to prevent NPE too
166             return "";
167         }
168         StringBuilder result = new StringBuilder( input.length() * 2 );
169 
170         String lastResult = input;
171         int tries = 0;
172         do
173         {
174             tries++;
175             if ( result.length() > 0 )
176             {
177                 lastResult = result.toString();
178                 result.setLength( 0 );
179             }
180             
181             int startIdx = -1;
182             int endIdx = -1;
183             
184             DelimiterSpecification selectedSpec = null;
185             while( ( selectedSpec = select( input, endIdx ) ) != null )
186             {
187                 String startExpr = selectedSpec.getBegin();
188                 String endExpr = selectedSpec.getEnd();
189                 
190                 startIdx = selectedSpec.getNextStartIndex();
191                 result.append( input, endIdx + 1, startIdx );
192 
193                 endIdx = input.indexOf( endExpr, startIdx + 1 );
194                 if ( endIdx < 0 )
195                 {
196                     break;
197                 }
198 
199                 String wholeExpr = input.substring( startIdx, endIdx + endExpr.length() );
200                 String realExpr = wholeExpr.substring( startExpr.length(), wholeExpr.length() - endExpr.length() );
201 
202                 if ( startIdx >= 0 && escapeString != null && escapeString.length() > 0 )
203                 {
204                     int startEscapeIdx = ( startIdx == 0 ) ? 0 : startIdx - escapeString.length();
205                     if ( startEscapeIdx >= 0 )
206                     {
207                         String escape = input.substring( startEscapeIdx, startIdx );
208                         if ( escape != null && escapeString.equals( escape ) )
209                         {
210                             result.append( wholeExpr );
211                             if ( startEscapeIdx > 0 )
212                             {
213                                 --startEscapeIdx;
214                             }
215                             result.replace( startEscapeIdx, startEscapeIdx + escapeString.length(), "" );
216                             continue;
217                         }
218                     }
219                 }
220 
221                 boolean resolved = false;
222                 if ( !unresolvable.contains( wholeExpr ) )
223                 {
224                     if ( realExpr.startsWith( "." ) )
225                     {
226                         realExpr = realExpr.substring( 1 );
227                     }
228 
229                     if ( recursionInterceptor.hasRecursiveExpression( realExpr ) )
230                     {
231                         throw new InterpolationCycleException( recursionInterceptor, realExpr, wholeExpr );
232                     }
233 
234                     recursionInterceptor.expressionResolutionStarted( realExpr );
235 
236                     Object value = existingAnswers.get( realExpr );
237                     Object bestAnswer = null;
238                     for ( ValueSource vs : valueSources )
239                     {
240                         if (value != null ) break;
241 
242                         value = vs.getValue( realExpr );
243 
244                         if ( value != null && value.toString().contains( wholeExpr ) )
245                         {
246                             bestAnswer = value;
247                             value = null;
248                         }
249                     }
250 
251                     // this is the simplest recursion check to catch exact recursion
252                     // (non synonym), and avoid the extra effort of more string
253                     // searching.
254                     if ( value == null && bestAnswer != null )
255                     {
256                         throw new InterpolationCycleException( recursionInterceptor, realExpr, wholeExpr );
257                     }
258 
259                     if ( value != null )
260                     {
261                         value = interpolate( String.valueOf( value ), recursionInterceptor, unresolvable );
262 
263                         if ( postProcessors != null && !postProcessors.isEmpty() )
264                         {
265                             for ( Object postProcessor1 : postProcessors )
266                             {
267                                 InterpolationPostProcessor postProcessor = (InterpolationPostProcessor) postProcessor1;
268                                 Object newVal = postProcessor.execute( realExpr, value );
269                                 if ( newVal != null )
270                                 {
271                                     value = newVal;
272                                     break;
273                                 }
274                             }
275                         }
276 
277                         // could use:
278                         // result = matcher.replaceFirst( stringValue );
279                         // but this could result in multiple lookups of stringValue, and replaceAll is not correct behaviour
280                         result.append( String.valueOf( value ) );
281                         resolved = true;
282                     }
283                     else
284                     {
285                         unresolvable.add( wholeExpr );
286                     }
287 
288                     recursionInterceptor.expressionResolutionFinished( realExpr );
289                 }
290 
291                 if ( !resolved )
292                 {
293                     result.append( wholeExpr );
294                 }
295 
296                 if ( endIdx > -1 )
297                 {
298                     endIdx += endExpr.length() - 1;
299                 }
300             }
301 
302             if ( endIdx == -1 && startIdx > -1 )
303             {
304                 result.append( input, startIdx, input.length() );
305             }
306             else if ( endIdx < input.length() )
307             {
308                 result.append( input, endIdx + 1, input.length() );
309             }
310         }
311         while( !lastResult.equals( result.toString() ) && tries < MAX_TRIES );
312 
313         return result.toString();
314     }
315 
316     private DelimiterSpecification select( String input, int lastEndIdx )
317     {
318         DelimiterSpecification selected = null;
319 
320         for ( DelimiterSpecification spec : delimiters )
321         {
322             spec.clearNextStart();
323 
324             if ( selected == null )
325             {
326                 int idx = input.indexOf( spec.getBegin(), lastEndIdx + 1 );
327                 if ( idx > -1 )
328                 {
329                     spec.setNextStartIndex( idx );
330                     selected = spec;
331                 }
332             }
333         }
334         
335         return selected;
336     }
337 
338     /**
339      * Return any feedback messages and errors that were generated - but suppressed - during the interpolation process.
340      * Since unresolvable expressions will be left in the source string as-is, this feedback is optional, and will only
341      * be useful for debugging interpolation problems.
342      * 
343      * @return a {@link List} that may be interspersed with {@link String} and {@link Throwable} instances.
344      */
345     public List getFeedback()
346     {
347         List messages = new ArrayList();
348         for ( ValueSource vs : valueSources )
349         {
350             List feedback = vs.getFeedback();
351             if ( feedback != null && !feedback.isEmpty() )
352             {
353                 messages.addAll( feedback );
354             }
355         }
356 
357         return messages;
358     }
359 
360     /**
361      * Clear the feedback messages from previous interpolate(..) calls.
362      */
363     public void clearFeedback()
364     {
365         for ( ValueSource vs : valueSources )
366         {
367             vs.clearFeedback();
368         }
369     }
370 
371     public boolean isCacheAnswers()
372     {
373         return cacheAnswers;
374     }
375 
376     public void setCacheAnswers( boolean cacheAnswers )
377     {
378         this.cacheAnswers = cacheAnswers;
379     }
380 
381     public void clearAnswers()
382     {
383         existingAnswers.clear();
384     }
385 
386     public String getEscapeString()
387     {
388         return escapeString;
389     }
390 
391     public void setEscapeString( String escapeString )
392     {
393         this.escapeString = escapeString;
394     }
395 
396     public MultiDelimiterStringSearchInterpolator escapeString( String escapeString )
397     {
398         this.escapeString = escapeString;
399         return this;
400     }
401     
402     public MultiDelimiterStringSearchInterpolator setDelimiterSpecs( LinkedHashSet<String> specs )
403     {
404         delimiters.clear();
405         for ( String spec : specs )
406         {
407             if ( spec == null )
408             {
409                 continue;
410             }
411             delimiters.add( DelimiterSpecification.parse( spec ) );
412         }
413         
414         return this;
415     }
416 
417 }