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                             result.replace( startEscapeIdx, startEscapeIdx + escapeString.length(), "" );
212                             continue;
213                         }
214                     }
215                 }
216 
217                 boolean resolved = false;
218                 if ( !unresolvable.contains( wholeExpr ) )
219                 {
220                     if ( realExpr.startsWith( "." ) )
221                     {
222                         realExpr = realExpr.substring( 1 );
223                     }
224 
225                     if ( recursionInterceptor.hasRecursiveExpression( realExpr ) )
226                     {
227                         throw new InterpolationCycleException( recursionInterceptor, realExpr, wholeExpr );
228                     }
229 
230                     recursionInterceptor.expressionResolutionStarted( realExpr );
231 
232                     Object value = existingAnswers.get( realExpr );
233                     Object bestAnswer = null;
234                     for ( ValueSource vs : valueSources )
235                     {
236                         if (value != null ) break;
237 
238                         value = vs.getValue( realExpr );
239 
240                         if ( value != null && value.toString().contains( wholeExpr ) )
241                         {
242                             bestAnswer = value;
243                             value = null;
244                         }
245                     }
246 
247                     // this is the simplest recursion check to catch exact recursion
248                     // (non synonym), and avoid the extra effort of more string
249                     // searching.
250                     if ( value == null && bestAnswer != null )
251                     {
252                         throw new InterpolationCycleException( recursionInterceptor, realExpr, wholeExpr );
253                     }
254 
255                     if ( value != null )
256                     {
257                         value = interpolate( String.valueOf( value ), recursionInterceptor, unresolvable );
258 
259                         if ( postProcessors != null && !postProcessors.isEmpty() )
260                         {
261                             for ( Object postProcessor1 : postProcessors )
262                             {
263                                 InterpolationPostProcessor postProcessor = (InterpolationPostProcessor) postProcessor1;
264                                 Object newVal = postProcessor.execute( realExpr, value );
265                                 if ( newVal != null )
266                                 {
267                                     value = newVal;
268                                     break;
269                                 }
270                             }
271                         }
272 
273                         // could use:
274                         // result = matcher.replaceFirst( stringValue );
275                         // but this could result in multiple lookups of stringValue, and replaceAll is not correct behaviour
276                         result.append( String.valueOf( value ) );
277                         resolved = true;
278                     }
279                     else
280                     {
281                         unresolvable.add( wholeExpr );
282                     }
283 
284                     recursionInterceptor.expressionResolutionFinished( realExpr );
285                 }
286 
287                 if ( !resolved )
288                 {
289                     result.append( wholeExpr );
290                 }
291 
292                 if ( endIdx > -1 )
293                 {
294                     endIdx += endExpr.length() - 1;
295                 }
296             }
297 
298             if ( endIdx == -1 && startIdx > -1 )
299             {
300                 result.append( input, startIdx, input.length() );
301             }
302             else if ( endIdx < input.length() )
303             {
304                 result.append( input, endIdx + 1, input.length() );
305             }
306         }
307         while( !lastResult.equals( result.toString() ) && tries < MAX_TRIES );
308 
309         return result.toString();
310     }
311 
312     private DelimiterSpecification select( String input, int lastEndIdx )
313     {
314         DelimiterSpecification selected = null;
315 
316         for ( DelimiterSpecification spec : delimiters )
317         {
318             spec.clearNextStart();
319 
320             if ( selected == null )
321             {
322                 int idx = input.indexOf( spec.getBegin(), lastEndIdx + 1 );
323                 if ( idx > -1 )
324                 {
325                     spec.setNextStartIndex( idx );
326                     selected = spec;
327                 }
328             }
329         }
330         
331         return selected;
332     }
333 
334     /**
335      * Return any feedback messages and errors that were generated - but suppressed - during the interpolation process.
336      * Since unresolvable expressions will be left in the source string as-is, this feedback is optional, and will only
337      * be useful for debugging interpolation problems.
338      * 
339      * @return a {@link List} that may be interspersed with {@link String} and {@link Throwable} instances.
340      */
341     public List getFeedback()
342     {
343         List messages = new ArrayList();
344         for ( ValueSource vs : valueSources )
345         {
346             List feedback = vs.getFeedback();
347             if ( feedback != null && !feedback.isEmpty() )
348             {
349                 messages.addAll( feedback );
350             }
351         }
352 
353         return messages;
354     }
355 
356     /**
357      * Clear the feedback messages from previous interpolate(..) calls.
358      */
359     public void clearFeedback()
360     {
361         for ( ValueSource vs : valueSources )
362         {
363             vs.clearFeedback();
364         }
365     }
366 
367     public boolean isCacheAnswers()
368     {
369         return cacheAnswers;
370     }
371 
372     public void setCacheAnswers( boolean cacheAnswers )
373     {
374         this.cacheAnswers = cacheAnswers;
375     }
376 
377     public void clearAnswers()
378     {
379         existingAnswers.clear();
380     }
381 
382     public String getEscapeString()
383     {
384         return escapeString;
385     }
386 
387     public void setEscapeString( String escapeString )
388     {
389         this.escapeString = escapeString;
390     }
391     
392     public MultiDelimiterStringSearchInterpolator setDelimiterSpecs( LinkedHashSet<String> specs )
393     {
394         delimiters.clear();
395         for ( String spec : specs )
396         {
397             if ( spec == null )
398             {
399                 continue;
400             }
401             delimiters.add( DelimiterSpecification.parse( spec ) );
402         }
403         
404         return this;
405     }
406 
407 }