001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.svl;
014
015import static org.apache.juneau.internal.CollectionUtils.*;
016
017import java.io.*;
018import java.util.*;
019import java.util.concurrent.*;
020
021import org.apache.juneau.*;
022import org.apache.juneau.cp.*;
023import org.apache.juneau.internal.*;
024import org.apache.juneau.svl.vars.*;
025
026/**
027 * Utility class for resolving variables of the form <js>"$X{key}"</js> in strings.
028 *
029 * <p>
030 * Variables are of the form <c>$X{key}</c>, where <c>X</c> can consist of zero or more ASCII characters.
031 * <br>The variable key can contain anything, even nested variables that get recursively resolved.
032 *
033 * <p>
034 * Variables are defined through the {@link Builder#vars(Class[])} method.
035 *
036 * <p>
037 * The {@link Var} interface defines how variables are converted to values.
038 *
039 * <h5 class='section'>Example:</h5>
040 * <p class='bjava'>
041 *    <jk>public class</jk> SystemPropertiesVar <jk>extends</jk> SimpleVar {
042 *
043 *       <jc>// Must have a no-arg constructor!</jc>
044 *       <jk>public</jk> SystemPropertiesVar() {
045 *          <jk>super</jk>(<js>"S"</js>);
046 *       }
047 *
048 *       <ja>@Override</ja>
049 *       <jk>public</jk> String resolve(VarResolverSession <jv>session</jv>, String <jv>value</jv>) {
050 *          <jk>return</jk> System.<jsm>getProperty</jsm>(<jv>value</jv>);
051 *       }
052 *    }
053 *
054 *    <jc>// Create a variable resolver that resolves system properties (e.g. "$S{java.home}")</jc>
055 *    VarResolver <jv>varResolver</jv> = VarResolver.<jsm>create</jsm>().vars(SystemPropertiesVar.<jk>class</jk>).build();
056 *
057 *    <jc>// Use it!</jc>
058 *    System.<jsf>out</jsf>.println(<jv>varResolver</jv>.resolve(<js>"java.home is set to $S{java.home}"</js>));
059 * </p>
060 *
061 * <h5 class='section'>See Also:</h5><ul>
062 *    <li class='link'><a class="doclink" href="../../../../index.html#jm.SimpleVariableLanguage">Simple Variable Language</a>
063
064 * </ul>
065 */
066public class VarResolver {
067
068   //-----------------------------------------------------------------------------------------------------------------
069   // Static
070   //-----------------------------------------------------------------------------------------------------------------
071
072   /**
073    * Default string variable resolver with support for system properties and environment variables:
074    *
075    * <ul>
076    *    <li><c>$S{key[,default]}</c> - {@link SystemPropertiesVar}
077    *    <li><c>$E{key[,default]}</c> - {@link EnvVariablesVar}
078    *    <li><c>$A{key[,default]}</c> - {@link ArgsVar}
079    *    <li><c>$MF{key[,default]}</c> - {@link ManifestFileVar}
080    *    <li><c>$SW{stringArg,pattern:thenValue[,pattern:thenValue...]}</c> - {@link SwitchVar}
081    *    <li><c>$IF{arg,then[,else]}</c> - {@link IfVar}
082    *    <li><c>$CO{arg[,arg2...]}</c> - {@link CoalesceVar}
083    *    <li><c>$PM{arg,pattern}</c> - {@link PatternMatchVar}
084    *    <li><c>$PR{stringArg,pattern,replace}</c>- {@link PatternReplaceVar}
085    *    <li><c>$PE{arg,pattern,groupIndex}</c> - {@link PatternExtractVar}
086    *    <li><c>$UC{arg}</c> - {@link UpperCaseVar}
087    *    <li><c>$LC{arg}</c> - {@link LowerCaseVar}
088    *    <li><c>$NE{arg}</c> - {@link NotEmptyVar}
089    *    <li><c>$LN{arg[,delimiter]}</c> - {@link LenVar}
090    *    <li><c>$ST{arg,start[,end]}</c> - {@link SubstringVar}
091    * </ul>
092    */
093   public static final VarResolver DEFAULT = create().defaultVars().build();
094
095   /**
096    * Instantiates a new clean-slate {@link Builder} object.
097    *
098    * @return A new {@link Builder} object.
099    */
100   public static Builder create() {
101      return new Builder();
102   }
103
104   //-----------------------------------------------------------------------------------------------------------------
105   // Builder
106   //-----------------------------------------------------------------------------------------------------------------
107
108   /**
109    * Builder class.
110    */
111   @FluentSetters
112   public static class Builder extends BeanBuilder<VarResolver> {
113
114      final VarList vars;
115
116      /**
117       * Constructor.
118       */
119      protected Builder() {
120         super(VarResolver.class, BeanStore.create().build());
121         vars = VarList.create();
122      }
123
124      /**
125       * Copy constructor.
126       *
127       * @param copyFrom The bean to copy from.
128       */
129      protected Builder(VarResolver copyFrom) {
130         super(copyFrom.getClass(), copyFrom.beanStore);
131         vars = VarList.of(copyFrom.vars);
132      }
133
134      @Override /* BeanBuilder */
135      protected VarResolver buildDefault() {
136         return new VarResolver(this);
137      }
138
139      //-------------------------------------------------------------------------------------------------------------
140      // Properties
141      //-------------------------------------------------------------------------------------------------------------
142
143      /**
144       * Register new variables with this resolver.
145       *
146       * @param values
147       *    The variable resolver classes.
148       *    These classes must subclass from {@link Var} and have no-arg constructors.
149       * @return This object .
150       */
151      @SafeVarargs
152      public final Builder vars(Class<? extends Var>...values) {
153         vars.append(values);
154         return this;
155      }
156
157      /**
158       * Register new variables with this resolver.
159       *
160       * @param values
161       *    The variable resolver classes.
162       *    These classes must subclass from {@link Var} and have no-arg constructors.
163       * @return This object .
164       */
165      public Builder vars(Var...values) {
166         vars.append(values);
167         return this;
168      }
169
170      /**
171       * Register new variables with this resolver.
172       *
173       * @param values
174       *    The variable resolver classes.
175       *    These classes must subclass from {@link Var} and have no-arg constructors.
176       * @return This object .
177       */
178      public Builder vars(VarList values) {
179         vars.append(values);
180         return this;
181      }
182
183      /**
184       * Adds the default variables to this builder.
185       *
186       * <p>
187       * The default variables are:
188       * <ul>
189       *    <li>{@link SystemPropertiesVar}
190       *    <li>{@link EnvVariablesVar}
191       *    <li>{@link ArgsVar}
192       *    <li>{@link ManifestFileVar}
193       *    <li>{@link SwitchVar}
194       *    <li>{@link IfVar}
195       *    <li>{@link CoalesceVar}
196       *    <li>{@link PatternMatchVar}
197       *    <li>{@link PatternReplaceVar}
198       *    <li>{@link PatternExtractVar}
199       *    <li>{@link UpperCaseVar}
200       *    <li>{@link LowerCaseVar}
201       *    <li>{@link NotEmptyVar}
202       *    <li>{@link LenVar}
203       *    <li>{@link SubstringVar}
204       * </ul>
205       *
206       * @return This object .
207       */
208      public Builder defaultVars() {
209         vars.addDefault();
210         return this;
211      }
212
213      /**
214       * Adds a bean to the bean store in this session.
215       *
216       * @param <T> The bean type.
217       * @param c The bean type.
218       * @param value The bean.
219       * @return This object .
220       */
221      public <T> Builder bean(Class<T> c, T value) {
222         beanStore().addBean(c, value);
223         return this;
224      }
225
226      // <FluentSetters>
227
228      @Override /* GENERATED - org.apache.juneau.BeanBuilder */
229      public Builder impl(Object value) {
230         super.impl(value);
231         return this;
232      }
233
234      @Override /* GENERATED - org.apache.juneau.BeanBuilder */
235      public Builder type(Class<?> value) {
236         super.type(value);
237         return this;
238      }
239
240      // </FluentSetters>
241   }
242
243   //-----------------------------------------------------------------------------------------------------------------
244   // Instance
245   //-----------------------------------------------------------------------------------------------------------------
246
247   final Var[] vars;
248   private final Map<String,Var> varMap;
249   final BeanStore beanStore;
250
251   /**
252    * Constructor.
253    *
254    * @param builder The builder for this object.
255    */
256   protected VarResolver(Builder builder) {
257      this.vars = builder.vars.stream().map(x -> toVar(builder.beanStore(),x)).toArray(Var[]::new);
258
259      Map<String,Var> m = new ConcurrentSkipListMap<>();
260      for (Var v : vars)
261         m.put(v.getName(), v);
262
263      this.varMap = unmodifiable(m);
264      this.beanStore = BeanStore.of(builder.beanStore());
265   }
266
267   private static Var toVar(BeanStore bs, Object o) {
268      if (o instanceof Class)
269         return bs.createBean(Var.class).type((Class<?>)o).run();
270      return (Var)o;
271   }
272
273   /**
274    * Returns a new builder object using the settings in this resolver as a base.
275    *
276    * @return A new var resolver builder.
277    */
278   public Builder copy() {
279      return new Builder(this);
280   }
281
282   /**
283    * Returns an unmodifiable map of {@link Var Vars} associated with this context.
284    *
285    * @return A map whose keys are var names (e.g. <js>"S"</js>) and values are {@link Var} instances.
286    */
287   protected Map<String,Var> getVarMap() {
288      return varMap;
289   }
290
291   /**
292    * Returns an array of variables define in this variable resolver context.
293    *
294    * @return A new array containing the variables in this context.
295    */
296   protected Var[] getVars() {
297      return Arrays.copyOf(vars, vars.length);
298   }
299
300   /**
301    * Adds a bean to this session.
302    *
303    * @param <T> The bean type.
304    * @param c The bean type.
305    * @param value The bean.
306    * @return This object .
307    */
308   public <T> VarResolver addBean(Class<T> c, T value) {
309      beanStore.addBean(c, value);
310      return this;
311   }
312
313   /**
314    * Creates a new resolver session with no session objects.
315    *
316    * @return A new resolver session.
317    */
318   public VarResolverSession createSession() {
319      return new VarResolverSession(this, null);
320   }
321
322   /**
323    * Same as {@link #createSession()} except allows you to specify a bean store for resolving beans.
324    *
325    * @param beanStore The bean store to associate with this session.
326    * @return A new resolver session.
327    */
328   public VarResolverSession createSession(BeanStore beanStore) {
329      return new VarResolverSession(this, beanStore);
330   }
331
332   /**
333    * Resolve variables in the specified string.
334    *
335    * <p>
336    * This is a shortcut for calling <code>createSession(<jk>null</jk>).resolve(s);</code>.
337    * <br>This method can only be used if the string doesn't contain variables that rely on the existence of session
338    * variables.
339    *
340    * @param s The input string.
341    * @return The string with variables resolved, or the same string if it doesn't contain any variables to resolve.
342    */
343   public String resolve(String s) {
344      return createSession(null).resolve(s);
345   }
346
347   /**
348    * Resolve variables in the specified string and sends the results to the specified writer.
349    *
350    * <p>
351    * This is a shortcut for calling <code>createSession(<jk>null</jk>).resolveTo(s, w);</code>.
352    * <br>This method can only be used if the string doesn't contain variables that rely on the existence of session
353    * variables.
354    *
355    * @param s The input string.
356    * @param w The writer to send the result to.
357    * @throws IOException Thrown by underlying stream.
358    */
359   public void resolveTo(String s, Writer w) throws IOException {
360      createSession(null).resolveTo(s, w);
361   }
362}