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.commons.svl;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021
022import java.io.*;
023import java.util.*;
024import java.util.concurrent.*;
025import java.util.function.*;
026
027import org.apache.juneau.commons.inject.*;
028import org.apache.juneau.commons.svl.functions.*;
029import org.apache.juneau.commons.svl.vars.*;
030
031/**
032 * Utility class for resolving variables of the form <js>"$X{key}"</js> in strings.
033 *
034 * <p>
035 * Variables are of the form <c>$X{key}</c>, where <c>X</c> can consist of zero or more ASCII characters.
036 * <br>The variable key can contain anything, even nested variables that get recursively resolved.
037 *
038 * <p>
039 * Variables are defined through the {@link Builder#vars(Class[])} method.
040 *
041 * <p>
042 * The {@link Var} interface defines how variables are converted to values.
043 *
044 * <h5 class='section'>Example:</h5>
045 * <p class='bjava'>
046 *    <jk>public class</jk> SystemPropertiesVar <jk>extends</jk> SimpleVar {
047 *
048 *       <jc>// Must have a no-arg constructor!</jc>
049 *       <jk>public</jk> SystemPropertiesVar() {
050 *          <jk>super</jk>(<js>"S"</js>);
051 *       }
052 *
053 *       <ja>@Override</ja>
054 *       <jk>public</jk> String resolve(VarResolverSession <jv>session</jv>, String <jv>value</jv>) {
055 *          <jk>return</jk> System.<jsm>getProperty</jsm>(<jv>value</jv>);
056 *       }
057 *    }
058 *
059 *    <jc>// Create a variable resolver that resolves system properties (e.g. "$S{java.home}")</jc>
060 *    VarResolver <jv>varResolver</jv> = VarResolver.<jsm>create</jsm>().vars(SystemPropertiesVar.<jk>class</jk>).build();
061 *
062 *    <jc>// Use it!</jc>
063 *    System.<jsf>out</jsf>.println(<jv>varResolver</jv>.resolve(<js>"java.home is set to $S{java.home}"</js>));
064 * </p>
065 *
066 * <h5 class='section'>See Also:</h5><ul>
067 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/MarshallSimpleVariableLanguage">Marshall Simple Variable Language</a>
068 * </ul>
069 */
070@SuppressWarnings({
071   "java:S3740", // Raw Var/Class types used intentionally for dynamic variable resolver registration where type parameters are not available
072   "resource", // VarResolver manages Closeable resources
073   "unchecked" // Var class/instance registration casts (Class<? extends Var>) and the raw Class passed to addBean come from Builder-validated entries
074})
075public class VarResolver {
076
077   /**
078    * Builder class.
079    */
080   public static class Builder {
081
082      private final WritableBeanStore beanStore;
083      final VarList vars;
084      final LinkedHashMap<Class<?>,Object> userBeans;
085      final List<VarFunction> functions;
086
087      /**
088       * Constructor.
089       */
090      protected Builder() {
091         this.beanStore = new BasicBeanStore();
092         vars = VarList.create();
093         userBeans = new LinkedHashMap<>();
094         functions = l();
095      }
096
097      /**
098       * Copy constructor.
099       *
100       * @param copyFrom The bean to copy from.  Must not be {@code null}.
101       */
102      protected Builder(VarResolver copyFrom) {
103         this.beanStore = copyFrom.beanStore;
104         vars = VarList.of(copyFrom.vars);
105         userBeans = new LinkedHashMap<>();
106         functions = list(copyFrom.functions);
107      }
108
109      /**
110       * Returns the bean store used by this builder.
111       *
112       * @return The bean store used by this builder.
113       */
114      public WritableBeanStore beanStore() {
115         return beanStore;
116      }
117
118      /**
119       * Adds a bean to the resolved {@link VarResolver}'s local bean store.
120       *
121       * <p>
122       * The bean is stored on this builder and merged into the resolver's local bean store at
123       * {@link #build()} time.  The parent bean store passed at construction is not mutated, so beans
124       * added here are isolated to {@link VarResolver}s built from this builder.
125       *
126       * @param <T> The bean type.
127       * @param c The bean type.
128       * @param value The bean.
129       * @return This object .
130       */
131      public <T> Builder bean(Class<T> c, T value) {
132         userBeans.put(c, value);
133         return this;
134      }
135
136      /**
137       * Adds the default variables to this builder.
138       *
139       * <p>
140       * The default variables are:
141       * <ul>
142       *    <li>{@link SystemPropertiesVar}
143       *    <li>{@link EnvVariablesVar}
144       *    <li>{@link EnvFileVar}
145       *    <li>{@link DotenvVar}
146       *    <li>{@link ArgsVar}
147       *    <li>{@link ManifestFileVar}
148       * </ul>
149       *
150       * <p>
151       * The 11 transformation {@code Var}s previously listed here ({@code SwitchVar},
152       * {@code IfVar}, {@code CoalesceVar}, {@code PatternMatchVar}, {@code PatternReplaceVar},
153       * {@code PatternExtractVar}, {@code UpperCaseVar}, {@code LowerCaseVar},
154       * {@code NotEmptyVar}, {@code LenVar}, {@code SubstringVar}) were deleted in 10.0.0 and
155       * replaced by the {@code #{...}} script syntax. Use {@link #defaultFunctions()} to
156       * register the built-in catalog.
157       *
158       * @return This object .
159       */
160      public Builder defaultVars() {
161         vars.addDefault();
162         return this;
163      }
164
165
166      /**
167       * Register new variables with this resolver.
168       *
169       * @param values
170       *    The variable resolver classes.
171       *    These classes must subclass from {@link Var} and have no-arg constructors.
172       * @return This object .
173       */
174      @SafeVarargs
175      public final Builder vars(Class<? extends Var>...values) {
176         vars.append(values);
177         return this;
178      }
179
180      /**
181       * Register new variables with this resolver.
182       *
183       * @param values
184       *    The variable resolver classes.
185       *    These classes must subclass from {@link Var} and have no-arg constructors.
186       * @return This object .
187       */
188      public Builder vars(Var...values) {
189         vars.append(values);
190         return this;
191      }
192
193      /**
194       * Register new variables with this resolver.
195       *
196       * @param values
197       *    The variable resolver classes.
198       *    These classes must subclass from {@link Var} and have no-arg constructors.
199       * @return This object .
200       */
201      public Builder vars(VarList values) {
202         vars.append(values);
203         return this;
204      }
205
206      /**
207       * Register one or more {@link VarFunction} instances on this builder.
208       *
209       * <p>
210       * Functions registered here win over {@code BeanStore} / {@code ServiceLoader} / built-in
211       * catalog entries with the same {@link VarFunction#name() name} (later-wins-on-collision).
212       * Discovery channels merge these registrations with the auto-discovered set at
213       * {@link #build()} time.
214       *
215       * @param values The functions to register.
216       * @return This builder.
217       */
218      public Builder functions(VarFunction... values) {
219         Collections.addAll(functions, values);
220         return this;
221      }
222
223      /**
224       * Register one or more {@link VarFunction} <i>classes</i> on this builder. Each class
225       * must have a public no-arg constructor; instances are created via the bean store at
226       * {@link #build()} time so dependency injection works the same way it does for
227       * {@link Var}s.
228       *
229       * @param values The function classes to register.
230       * @return This builder.
231       */
232      @SafeVarargs
233      public final Builder functions(Class<? extends VarFunction>... values) {
234         for (var c : values)
235            functions.add(BeanInstantiator.of(VarFunction.class, beanStore).type(c).run());
236         return this;
237      }
238
239      /**
240       * Registers the built-in {@link VarFunction} catalog plus any third-party functions
241       * discovered via {@link ServiceLoader#load(Class)} on {@link VarFunction}.
242       *
243       * <p>
244       * Registration order is:
245       * <ol>
246       *    <li>Built-in catalog — string, type-conversion, arithmetic, boolean, conditional,
247       *       regex, encoding, date, random/UUID, and JSON-navigation functions.
248       *    <li>Third-party functions from {@code META-INF/services/org.apache.juneau.commons.svl.VarFunction}.
249       * </ol>
250       *
251       * <p>
252       * Later registrations win on name collision — third-party {@code ServiceLoader} entries
253       * override built-ins, and explicit {@link #functions(VarFunction...)} /
254       * {@link #functions(Class...)} calls override both.
255       *
256       * @return This builder.
257       */
258      public Builder defaultFunctions() {
259         for (var c : BUILTIN_FUNCTION_CLASSES)
260            functions.add(BeanInstantiator.of(VarFunction.class, beanStore).type(c).run());
261         for (var f : ServiceLoader.load(VarFunction.class, VarResolver.class.getClassLoader()))
262            functions.add(f);
263         return this;
264      }
265
266      /**
267       * Builds the var resolver.
268       *
269       * @return A new {@link VarResolver}.
270       */
271      public VarResolver build() {
272         return new VarResolver(this);
273      }
274   }
275
276   /**
277    * Built-in {@link VarFunction} classes registered by {@link Builder#defaultFunctions()}.
278    *
279    * <p>
280    * Order matters only for collisions inside the built-in catalog itself (and there should be
281    * none). Third-party functions discovered via {@link ServiceLoader} register <i>after</i>
282    * this list, so they override built-ins on name collision.
283    */
284   private static final Class<? extends VarFunction>[] BUILTIN_FUNCTION_CLASSES = (Class<? extends VarFunction>[]) concat(
285      StringFunctions.ALL,
286      TypeConversionFunctions.ALL,
287      ArithmeticFunctions.ALL,
288      BooleanFunctions.ALL,
289      ConditionalFunctions.ALL,
290      RegexFunctions.ALL,
291      EncodingFunctions.ALL,
292      DateFunctions.ALL,
293      RandomFunctions.ALL,
294      JsonFunctions.ALL
295   );
296
297   @SafeVarargs
298   private static <T> Class<? extends T>[] concat(Class<? extends T>[]... arrays) {
299      var total = 0;
300      for (var a : arrays) total += a.length;
301      var out = new Class[total];
302      var idx = 0;
303      for (var a : arrays) {
304         System.arraycopy(a, 0, out, idx, a.length);
305         idx += a.length;
306      }
307      return out;
308   }
309
310   /**
311    * Default string variable resolver with support for system properties, environment
312    * variables, and the built-in {@link VarFunction} catalog.
313    *
314    * <h5 class='section'>Built-in {@link Var} bindings:</h5>
315    * <ul>
316    *    <li><c>$S{key[,default]}</c> - {@link SystemPropertiesVar}
317    *    <li><c>$E{key[,default]}</c> - {@link EnvVariablesVar}
318    *    <li><c>$EF{key[,default]}</c> - {@link EnvFileVar}
319    *    <li><c>$DE{key[,default]}</c> - {@link DotenvVar}
320    *    <li><c>$A{key[,default]}</c> - {@link ArgsVar}
321    *    <li><c>$MF{key[,default]}</c> - {@link ManifestFileVar}
322    * </ul>
323    *
324    * <h5 class='section'>Built-in {@link VarFunction} catalog:</h5>
325    * <p>
326    * Ships ~68 built-in functions across 10 categories — string, type-conversion, arithmetic,
327    * boolean, conditional, regex, encoding, date/time, random/UUID, and JSON-navigation.
328    * Functions are invoked via the {@code #{name(args...)}} syntax. See
329    * {@link Builder#defaultFunctions()} for the registration mechanism.
330    *
331    * <p>
332    * The 11 transformation/conditional {@code Var}s previously bundled here
333    * ({@code SwitchVar}, {@code IfVar}, {@code CoalesceVar}, {@code PatternMatchVar},
334    * {@code PatternReplaceVar}, {@code PatternExtractVar}, {@code UpperCaseVar},
335    * {@code LowerCaseVar}, {@code NotEmptyVar}, {@code LenVar}, {@code SubstringVar}) were
336    * deleted in 10.0.0; their behavior is now covered by the {@code #{...}} script syntax.
337    */
338   public static final VarResolver DEFAULT = create().defaultVars().defaultFunctions().build();
339
340   /**
341    * Instantiates a new clean-slate {@link Builder} object.
342    *
343    * @return A new {@link Builder} object.
344    */
345   public static Builder create() {
346      return new Builder();
347   }
348
349   private static Var toVar(BeanStore bs, Object o) {
350      if (o instanceof Class<?> o2) {
351         var subType = (Class<? extends Var>) o2;
352         return BeanInstantiator.of(Var.class, bs).type(subType).run();
353      }
354      return (Var)o;
355   }
356
357   final Var[] vars;
358   private final Map<String,Var> varMap;
359
360   final VarFunction[] functions;
361   private final Map<String,VarFunction> functionMap;
362
363   final WritableBeanStore beanStore;
364
365   /**
366    * Constructor.
367    *
368    * @param builder The builder for this object.
369    */
370   @SuppressWarnings({
371      "rawtypes" // Same reason — Builder.bean(Class<T>, T) ensures Class/value pairing
372   })
373   protected VarResolver(Builder builder) {
374      this.vars = builder.vars.stream().map(x -> toVar(builder.beanStore(), x)).toArray(Var[]::new);
375
376      var m = new ConcurrentSkipListMap<String,Var>();
377      for (var v : vars)
378         m.put(v.getName(), v);
379
380      this.varMap = u(m);
381      this.functions = builder.functions.toArray(new VarFunction[0]);
382
383      var fm = new ConcurrentSkipListMap<String,VarFunction>();
384      for (var f : functions)
385         fm.put(f.name(), f);
386      this.functionMap = u(fm);
387
388      var bs = new BasicBeanStore(builder.beanStore());
389      builder.userBeans.forEach((c, v) -> bs.addBean((Class) c, v));
390      this.beanStore = bs;
391   }
392
393   /**
394    * Adds a bean to this session.
395    *
396    * @param <T> The bean type.
397    * @param c The bean type.
398    * @param value The bean.
399    * @return This object .
400    */
401   public <T> VarResolver addBean(Class<T> c, T value) {
402      beanStore.addBean(c, value);
403      return this;
404   }
405
406   /**
407    * Returns a new builder object using the settings in this resolver as a base.
408    *
409    * @return A new var resolver builder.
410    */
411   public Builder copy() {
412      return new Builder(this);
413   }
414
415   /**
416    * Creates a new resolver session with no session objects.
417    *
418    * @return A new resolver session.
419    */
420   public VarResolverSession createSession() {
421      return new VarResolverSession(this, null);
422   }
423
424   /**
425    * Same as {@link #createSession()} except allows you to specify a bean store for resolving beans.
426    *
427    * @param beanStore The bean store to associate with this session.  May be {@code null}.
428    * @return A new resolver session.
429    */
430   public VarResolverSession createSession(WritableBeanStore beanStore) {
431      return new VarResolverSession(this, beanStore);
432   }
433
434   /**
435    * Resolve variables in the specified string.
436    *
437    * <p>
438    * This is a shortcut for calling <code>createSession(<jk>null</jk>).resolve(s);</code>.
439    * <br>This method can only be used if the string doesn't contain variables that rely on the existence of session
440    * variables.
441    *
442    * @param s The input string.  May be {@code null}.
443    * @return The string with variables resolved, or the same string if it doesn't contain any variables to resolve.  Returns {@code null} if the input was {@code null}.
444    */
445   public String resolve(String s) {
446      return createSession(null).resolve(s);
447   }
448
449   /**
450    * Compiles a template string into a reusable {@link VarTemplate}.
451    *
452    * <p>
453    * Compilation is the dual of {@link #resolve(String)} — instead of tokenizing and resolving
454    * in one pass, the input is parsed once and the result returned as an immutable
455    * {@link VarTemplate} that callers can reuse across many resolutions:
456    *
457    * <p class='bjava'>
458    *    <jc>// Compile once...</jc>
459    *    <jk>var</jk> <jv>tpl</jv> = <jv>vr</jv>.compile(<js>"hello, ${name:world}"</js>);
460    *
461    *    <jc>// ...resolve many times.</jc>
462    *    <jk>for</jk> (<jk>var</jk> <jv>req</jv> : <jv>requests</jv>) {
463    *       <jk>var</jk> <jv>session</jv> = <jv>vr</jv>.createSession().bean(<jv>req</jv>);
464    *       <jv>writer</jv>.write(<jv>tpl</jv>.resolve(<jv>session</jv>));
465    *    }
466    * </p>
467    *
468    * <p>
469    * The returned {@link VarTemplate} is immutable and threadsafe; the {@link VarResolverSession}
470    * passed to {@link VarTemplate#resolve(VarResolverSession)} inherits its own threadsafety
471    * contract (sessions are <b>not</b> threadsafe). Use {@link #resolveSupplier(String)} or
472    * {@link VarTemplate#asSupplierWithFreshSessions(VarResolver)} for a threadsafe re-evaluating
473    * Supplier.
474    *
475    * <h5 class='section'>Compile-binding contract:</h5>
476    * <p>
477    * The compiled template is bound to <i>this</i> resolver. Cached {@link Var} (and, in the
478    * future, function) references are resolved at compile time. Passing the compiled template
479    * to a session belonging to a different {@link VarResolver} is undefined behavior.
480    * Rebuilding the resolver invalidates compiled templates from the previous instance.
481    *
482    * <p>
483    * Templates are <b>not</b> cached on the resolver. Callers that compile the same input
484    * repeatedly (e.g. framework consumers compiling per {@code @Value} field) should cache
485    * the returned {@link VarTemplate} on their own per-site metadata.
486    *
487    * @param input The template string to compile. May be {@code null}.
488    * @return A new {@link VarTemplate} bound to this resolver.
489    */
490   public VarTemplate compile(String input) {
491      return VarTemplateCompiler.compile(this, input);
492   }
493
494   /**
495    * Returns a threadsafe {@link Supplier} that resolves the given template against this
496    * resolver each time {@link Supplier#get()} is called.
497    *
498    * <p>
499    * Each call to {@link Supplier#get()} opens a fresh {@link VarResolverSession}, so the
500    * returned {@code Supplier} is safe to share across threads — making it the recommended
501    * default for user-facing code (e.g. {@code @Value Supplier<String>} field types,
502    * hot-reload config patterns).
503    *
504    * <p>
505    * Equivalent to {@code compile(input).asSupplierWithFreshSessions(this)}.
506    *
507    * <p>
508    * For a session-bound (and therefore <i>not</i> threadsafe) variant, see
509    * {@link VarResolverSession#resolveSupplier(String)}.
510    *
511    * @param input The template string. May be {@code null}.
512    * @return A threadsafe {@code Supplier<String>}.
513    */
514   public Supplier<String> resolveSupplier(String input) {
515      return compile(input).asSupplierWithFreshSessions(this);
516   }
517
518   /**
519    * Resolve variables in the specified string and sends the results to the specified writer.
520    *
521    * <p>
522    * This is a shortcut for calling <code>createSession(<jk>null</jk>).resolveTo(s, w);</code>.
523    * <br>This method can only be used if the string doesn't contain variables that rely on the existence of session
524    * variables.
525    *
526    * @param s The input string.  May be {@code null}, in which case the writer is left unchanged.
527    * @param w The writer to send the result to.  Must not be {@code null}.
528    * @throws IOException Thrown by underlying stream.
529    */
530   public void resolveTo(String s, Writer w) throws IOException {
531      createSession(null).resolveTo(s, w);
532   }
533
534   /**
535    * Returns an unmodifiable map of {@link Var Vars} associated with this context.
536    *
537    * @return A map whose keys are var names (e.g. <js>"S"</js>) and values are {@link Var} instances.
538    */
539   protected Map<String,Var> getVarMap() { return varMap; }
540
541   /**
542    * Returns an array of variables define in this variable resolver context.
543    *
544    * @return A new array containing the variables in this context.
545    */
546   protected Var[] getVars() { return Arrays.copyOf(vars, vars.length); }
547
548   /**
549    * Returns an unmodifiable map of {@link VarFunction VarFunctions} associated with this context.
550    *
551    * @return A map whose keys are function names (e.g. <js>"upper"</js>) and values are
552    *  {@link VarFunction} instances.
553    */
554   protected Map<String,VarFunction> getFunctionMap() { return functionMap; }
555}