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