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