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