View Javadoc
1   package org.codehaus.plexus.interpolation.fixed;
2   
3   /*
4    * Copyright 2014 The 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.List;
21  
22  import org.codehaus.plexus.interpolation.BasicInterpolator;
23  import org.codehaus.plexus.interpolation.InterpolationException;
24  import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
25  import org.codehaus.plexus.interpolation.RecursionInterceptor;
26  
27  /**
28   * <p>
29   * A fixed string search interpolator is permanently bound to a given set of value sources,
30   * an is totally fixed and stateless over these value sources.</p>
31   * <p>The fixed interpolator is also a #StatelessValueSource and can be used as a source
32   * for a different fixed interpolator, creating a scope chain.</p>
33   * <p>Once constructed, this interpolator will always point to the same set of objects (value sources),
34   * in such a way that if the underlying object is fixed, expressions will always
35   * evaluate to the same result.</p>
36   * <p>The fixed interpolator can be shared among different clients and is thread safe to
37   * the extent the underlying value sources can be accessed safely.
38   * Since interpolation expressions cannot modify the objects, thread safety concerns
39   * this will normally be limited to safe publication and memory model visibility of
40   * underlying objects.</p>
41   * <p>The fixed interpolator can be a valuesource</p>
42   */
43  public class FixedStringSearchInterpolator implements FixedValueSource {
44  
45      private final FixedValueSource[] valueSources;
46  
47      private final InterpolationPostProcessor postProcessor;
48  
49      public static final String DEFAULT_START_EXPR = "${";
50  
51      public static final String DEFAULT_END_EXPR = "}";
52  
53      private final String startExpr;
54  
55      private final String endExpr;
56  
57      private final String escapeString;
58  
59      private FixedStringSearchInterpolator(
60              String startExpr,
61              String endExpr,
62              String escapeString,
63              InterpolationPostProcessor postProcessor,
64              FixedValueSource... valueSources) {
65          this.startExpr = startExpr;
66          this.endExpr = endExpr;
67          this.escapeString = escapeString;
68          if (valueSources == null) {
69              throw new IllegalArgumentException("valueSources cannot be null");
70          }
71          for (int i = 0; i < valueSources.length; i++) {
72              if (valueSources[i] == null) {
73                  throw new IllegalArgumentException("valueSources[" + i + "] is null");
74              }
75          }
76  
77          this.valueSources = valueSources;
78          this.postProcessor = postProcessor;
79      }
80  
81      public static FixedStringSearchInterpolator create(
82              String startExpr, String endExpr, FixedValueSource... valueSources) {
83          return new FixedStringSearchInterpolator(startExpr, endExpr, null, null, valueSources);
84      }
85  
86      public static FixedStringSearchInterpolator create(FixedValueSource... valueSources) {
87          return new FixedStringSearchInterpolator(DEFAULT_START_EXPR, DEFAULT_END_EXPR, null, null, valueSources);
88      }
89  
90      public static FixedStringSearchInterpolator createWithPermittedNulls(FixedValueSource... valueSources) {
91          List<FixedValueSource> nonnulls = new ArrayList<FixedValueSource>();
92          for (FixedValueSource item : valueSources) {
93              if (item != null) nonnulls.add(item);
94          }
95          return new FixedStringSearchInterpolator(
96                  DEFAULT_START_EXPR,
97                  DEFAULT_END_EXPR,
98                  null,
99                  null,
100                 nonnulls.toArray(new FixedValueSource[nonnulls.size()]));
101     }
102 
103     public FixedStringSearchInterpolator withExpressionMarkers(String startExpr, String endExpr) {
104         return new FixedStringSearchInterpolator(startExpr, endExpr, escapeString, postProcessor, valueSources);
105     }
106 
107     public FixedStringSearchInterpolator withPostProcessor(InterpolationPostProcessor postProcessor) {
108         return new FixedStringSearchInterpolator(startExpr, endExpr, escapeString, postProcessor, valueSources);
109     }
110 
111     public FixedStringSearchInterpolator withEscapeString(String escapeString) {
112         return new FixedStringSearchInterpolator(startExpr, endExpr, escapeString, postProcessor, valueSources);
113     }
114 
115     public String interpolate(String input) throws InterpolationCycleException {
116         return interpolate(input, new InterpolationState());
117     }
118 
119     public static FixedStringSearchInterpolator empty() {
120         return create();
121     }
122 
123     // Find out how to return null when we cannot interpolate this expression
124     // At this point we should always be a ${expr}
125     public Object getValue(String realExpr, InterpolationState interpolationState) {
126 
127         interpolationState.recursionInterceptor.expressionResolutionStarted(realExpr);
128 
129         try {
130             Object value = null;
131 
132             for (FixedValueSource valueSource : valueSources) {
133                 value = valueSource.getValue(realExpr, interpolationState);
134                 if (value != null) {
135                     break;
136                 }
137             }
138 
139             if (value != null) {
140                 if (interpolationState.root != null) {
141                     value = interpolationState.root.interpolate(String.valueOf(value), interpolationState);
142                 }
143                 return String.valueOf(value);
144             } else {
145                 return null;
146             }
147         } finally {
148             interpolationState.recursionInterceptor.expressionResolutionFinished(realExpr);
149         }
150     }
151 
152     public BasicInterpolator asBasicInterpolator() {
153         final InterpolationState is = new InterpolationState();
154         return new BasicInterpolator() {
155 
156             public String interpolate(String input) throws InterpolationException {
157                 return FixedStringSearchInterpolator.this.interpolate(input, is);
158             }
159 
160             public String interpolate(String input, RecursionInterceptor recursionInterceptor)
161                     throws InterpolationException {
162                 is.setRecursionInterceptor(recursionInterceptor);
163                 return FixedStringSearchInterpolator.this.interpolate(input, is);
164             }
165         };
166     }
167 
168     public String interpolate(String input, InterpolationState interpolationState) throws InterpolationCycleException {
169         if (interpolationState.root == null) {
170             interpolationState.root = this;
171         }
172 
173         if (input == null) {
174             // return empty String to prevent NPE too
175             return "";
176         }
177         StringBuilder result = new StringBuilder(input.length() * 2);
178 
179         int startIdx;
180         int endIdx = -1;
181         while ((startIdx = input.indexOf(startExpr, endIdx + 1)) > -1) {
182             result.append(input, endIdx + 1, startIdx);
183 
184             endIdx = input.indexOf(endExpr, startIdx + 1);
185             if (endIdx < 0) {
186                 break;
187             }
188 
189             final String wholeExpr = input.substring(startIdx, endIdx + endExpr.length());
190             String realExpr = wholeExpr.substring(startExpr.length(), wholeExpr.length() - endExpr.length());
191 
192             if (startIdx >= 0 && escapeString != null && escapeString.length() > 0) {
193                 int startEscapeIdx = startIdx == 0 ? 0 : startIdx - escapeString.length();
194                 if (startEscapeIdx >= 0) {
195                     String escape = input.substring(startEscapeIdx, startIdx);
196                     if (escapeString.equals(escape)) {
197                         result.append(wholeExpr);
198                         result.replace(startEscapeIdx, startEscapeIdx + escapeString.length(), "");
199                         continue;
200                     }
201                 }
202             }
203 
204             boolean resolved = false;
205             if (!interpolationState.unresolvable.contains(wholeExpr)) {
206                 if (realExpr.startsWith(".")) {
207                     realExpr = realExpr.substring(1);
208                 }
209 
210                 if (interpolationState.recursionInterceptor.hasRecursiveExpression(realExpr)) {
211                     throw new InterpolationCycleException(interpolationState.recursionInterceptor, realExpr, wholeExpr);
212                 }
213 
214                 Object value = getValue(realExpr, interpolationState);
215                 if (value != null) {
216                     value = interpolate(String.valueOf(value), interpolationState);
217 
218                     if (postProcessor != null) {
219                         Object newVal = postProcessor.execute(realExpr, value);
220                         if (newVal != null) {
221                             value = newVal;
222                         }
223                     }
224 
225                     result.append(String.valueOf(value));
226                     resolved = true;
227                 } else {
228                     interpolationState.unresolvable.add(wholeExpr);
229                 }
230             }
231 
232             if (!resolved) {
233                 result.append(wholeExpr);
234             }
235 
236             if (endIdx > -1) {
237                 endIdx += endExpr.length() - 1;
238             }
239         }
240 
241         if (endIdx == -1 && startIdx > -1) {
242             result.append(input, startIdx, input.length());
243         } else if (endIdx < input.length()) {
244             result.append(input, endIdx + 1, input.length());
245         }
246 
247         return result.toString();
248     }
249 }