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