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