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.HashSet;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Set;
25  
26  public class StringSearchInterpolator
27      implements Interpolator
28  {
29  
30      private Map<String,Object> existingAnswers = new HashMap<String,Object>();
31  
32      private List<ValueSource> valueSources = new ArrayList<ValueSource>();
33  
34      private List<InterpolationPostProcessor> postProcessors = new ArrayList<InterpolationPostProcessor>();
35  
36      private boolean cacheAnswers = false;
37  
38      public static final String DEFAULT_START_EXPR = "${";
39  
40      public static final String DEFAULT_END_EXPR = "}";
41  
42      private String startExpr;
43  
44      private String endExpr;
45  
46      private String escapeString;
47  
48      public StringSearchInterpolator()
49      {
50          this.startExpr = DEFAULT_START_EXPR;
51          this.endExpr = DEFAULT_END_EXPR;
52      }
53  
54      public StringSearchInterpolator( String startExpr, String endExpr )
55      {
56          this.startExpr = startExpr;
57          this.endExpr = endExpr;
58      }
59  
60  
61      /**
62       * {@inheritDoc}
63       */
64      public void addValueSource( ValueSource valueSource )
65      {
66          valueSources.add( valueSource );
67      }
68  
69      /**
70       * {@inheritDoc}
71       */
72      public void removeValuesSource( ValueSource valueSource )
73      {
74          valueSources.remove( valueSource );
75      }
76  
77      /**
78       * {@inheritDoc}
79       */
80      public void addPostProcessor( InterpolationPostProcessor postProcessor )
81      {
82          postProcessors.add( postProcessor );
83      }
84  
85      /**
86       * {@inheritDoc}
87       */
88      public void removePostProcessor( InterpolationPostProcessor postProcessor )
89      {
90          postProcessors.remove( postProcessor );
91      }
92  
93      public String interpolate( String input, String thisPrefixPattern )
94          throws InterpolationException
95      {
96          return interpolate( input, new SimpleRecursionInterceptor() );
97      }
98  
99      public String interpolate( String input, String thisPrefixPattern, RecursionInterceptor recursionInterceptor )
100         throws InterpolationException
101     {
102         return interpolate( input, recursionInterceptor );
103     }
104 
105     public String interpolate( String input )
106         throws InterpolationException
107     {
108         return interpolate( input, new SimpleRecursionInterceptor() );
109     }
110 
111     /**
112      * Entry point for recursive resolution of an expression and all of its
113      * nested expressions.
114      *
115      * @todo Ensure unresolvable expressions don't trigger infinite recursion.
116      */
117     public String interpolate( String input, RecursionInterceptor recursionInterceptor )
118         throws InterpolationException
119     {
120         try
121         {
122             return interpolate( input, recursionInterceptor, new HashSet<String>() );
123         }
124         finally
125         {
126             if ( !cacheAnswers )
127             {
128                 existingAnswers.clear();
129             }
130         }
131     }
132 
133     private String interpolate( String input, RecursionInterceptor recursionInterceptor, Set<String> unresolvable )
134         throws InterpolationException
135     {
136         if ( input == null )
137         {
138             // return empty String to prevent NPE too
139             return "";
140         }
141         StringBuilder result = new StringBuilder( input.length() * 2 );
142 
143         int startIdx;
144         int endIdx = -1;
145         while ( ( startIdx = input.indexOf( startExpr, endIdx + 1 ) ) > -1 )
146         {
147             result.append( input, endIdx + 1, startIdx );
148 
149             endIdx = input.indexOf( endExpr, startIdx + 1 );
150             if ( endIdx < 0 )
151             {
152                 break;
153             }
154 
155             final String wholeExpr = input.substring( startIdx, endIdx + endExpr.length() );
156             String realExpr = wholeExpr.substring( startExpr.length(), wholeExpr.length() - endExpr.length() );
157 
158             if ( startIdx >= 0 && escapeString != null && escapeString.length() > 0 )
159             {
160                 int startEscapeIdx = startIdx == 0 ? 0 : startIdx - escapeString.length();
161                 if ( startEscapeIdx >= 0 )
162                 {
163                     String escape = input.substring( startEscapeIdx, startIdx );
164                     if ( escapeString.equals( escape ) )
165                     {
166                         result.append( wholeExpr );
167                         result.replace( startEscapeIdx, startEscapeIdx + escapeString.length(), "" );
168                         continue;
169                     }
170                 }
171             }
172 
173             boolean resolved = false;
174             if ( !unresolvable.contains( wholeExpr ) )
175             {
176                 if ( realExpr.startsWith( "." ) )
177                 {
178                     realExpr = realExpr.substring( 1 );
179                 }
180 
181                 if ( recursionInterceptor.hasRecursiveExpression( realExpr ) )
182                 {
183                     throw new InterpolationCycleException( recursionInterceptor, realExpr, wholeExpr );
184                 }
185 
186                 recursionInterceptor.expressionResolutionStarted( realExpr );
187                 try
188                 {
189                     Object value = existingAnswers.get( realExpr );
190                     Object bestAnswer = null;
191 
192                     for ( ValueSource valueSource : valueSources )
193                     {
194                         if ( value != null )
195                         {
196                             break;
197                         }
198                         value = valueSource.getValue( realExpr );
199 
200                         if ( value != null && value.toString().contains( wholeExpr ) )
201                         {
202                             bestAnswer = value;
203                             value = null;
204                         }
205                     }
206 
207                     // this is the simplest recursion check to catch exact recursion
208                     // (non synonym), and avoid the extra effort of more string
209                     // searching.
210                     if ( value == null && bestAnswer != null )
211                     {
212                         throw new InterpolationCycleException( recursionInterceptor, realExpr, wholeExpr );
213                     }
214 
215                     if ( value != null )
216                     {
217                         value = interpolate( String.valueOf( value ), recursionInterceptor, unresolvable );
218 
219                         if ( postProcessors != null && !postProcessors.isEmpty() )
220                         {
221                             for ( InterpolationPostProcessor postProcessor : postProcessors )
222                             {
223                                 Object newVal = postProcessor.execute( realExpr, value );
224                                 if ( newVal != null )
225                                 {
226                                     value = newVal;
227                                     break;
228                                 }
229                             }
230                         }
231 
232                         // could use:
233                         // result = matcher.replaceFirst( stringValue );
234                         // but this could result in multiple lookups of stringValue, and replaceAll is not correct
235                         // behaviour
236                         result.append( String.valueOf( value ) );
237                         resolved = true;
238                     }
239                     else
240                     {
241                         unresolvable.add( wholeExpr );
242                     }
243                 }
244                 finally
245                 {
246                     recursionInterceptor.expressionResolutionFinished( realExpr );
247                 }
248             }
249 
250             if ( !resolved )
251             {
252                 result.append( wholeExpr );
253             }
254 
255             if ( endIdx > -1 )
256             {
257                 endIdx += endExpr.length() - 1;
258             }
259         }
260 
261         if ( endIdx == -1 && startIdx > -1 )
262         {
263             result.append( input, startIdx, input.length());
264         }
265         else if ( endIdx < input.length() )
266         {
267             result.append( input, endIdx + 1, input.length() );
268         }
269 
270         return result.toString();
271     }
272 
273     /**
274      * Return any feedback messages and errors that were generated - but
275      * suppressed - during the interpolation process. Since unresolvable
276      * expressions will be left in the source string as-is, this feedback is
277      * optional, and will only be useful for debugging interpolation problems.
278      *
279      * @return a {@link List} that may be interspersed with {@link String} and
280      *         {@link Throwable} instances.
281      */
282     public List getFeedback()
283     {
284         List<?> messages = new ArrayList();
285         for ( ValueSource vs : valueSources )
286         {
287             List feedback = vs.getFeedback();
288             if ( feedback != null && !feedback.isEmpty() )
289             {
290                 messages.addAll( feedback );
291             }
292         }
293 
294         return messages;
295     }
296 
297     /**
298      * Clear the feedback messages from previous interpolate(..) calls.
299      */
300     public void clearFeedback()
301     {
302         for ( ValueSource vs : valueSources )
303         {
304             vs.clearFeedback();
305         }
306     }
307 
308     public boolean isCacheAnswers()
309     {
310         return cacheAnswers;
311     }
312 
313     public void setCacheAnswers( boolean cacheAnswers )
314     {
315         this.cacheAnswers = cacheAnswers;
316     }
317 
318     public void clearAnswers()
319     {
320         existingAnswers.clear();
321     }
322 
323     public String getEscapeString()
324     {
325         return escapeString;
326     }
327 
328     public void setEscapeString( String escapeString )
329     {
330         this.escapeString = escapeString;
331     }
332 
333 }