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