1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.juneau.svl;
18
19 import static org.apache.juneau.commons.utils.CollectionUtils.*;
20
21 import java.io.*;
22 import java.util.*;
23 import java.util.concurrent.*;
24
25 import org.apache.juneau.*;
26 import org.apache.juneau.cp.*;
27 import org.apache.juneau.svl.vars.*;
28
29 /**
30 * Utility class for resolving variables of the form <js>"$X{key}"</js> in strings.
31 *
32 * <p>
33 * Variables are of the form <c>$X{key}</c>, where <c>X</c> can consist of zero or more ASCII characters.
34 * <br>The variable key can contain anything, even nested variables that get recursively resolved.
35 *
36 * <p>
37 * Variables are defined through the {@link Builder#vars(Class[])} method.
38 *
39 * <p>
40 * The {@link Var} interface defines how variables are converted to values.
41 *
42 * <h5 class='section'>Example:</h5>
43 * <p class='bjava'>
44 * <jk>public class</jk> SystemPropertiesVar <jk>extends</jk> SimpleVar {
45 *
46 * <jc>// Must have a no-arg constructor!</jc>
47 * <jk>public</jk> SystemPropertiesVar() {
48 * <jk>super</jk>(<js>"S"</js>);
49 * }
50 *
51 * <ja>@Override</ja>
52 * <jk>public</jk> String resolve(VarResolverSession <jv>session</jv>, String <jv>value</jv>) {
53 * <jk>return</jk> System.<jsm>getProperty</jsm>(<jv>value</jv>);
54 * }
55 * }
56 *
57 * <jc>// Create a variable resolver that resolves system properties (e.g. "$S{java.home}")</jc>
58 * VarResolver <jv>varResolver</jv> = VarResolver.<jsm>create</jsm>().vars(SystemPropertiesVar.<jk>class</jk>).build();
59 *
60 * <jc>// Use it!</jc>
61 * System.<jsf>out</jsf>.println(<jv>varResolver</jv>.resolve(<js>"java.home is set to $S{java.home}"</js>));
62 * </p>
63 *
64 * <h5 class='section'>See Also:</h5><ul>
65 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SimpleVariableLanguageBasics">Simple Variable Language Basics</a>
66
67 * </ul>
68 */
69 @SuppressWarnings("resource")
70 public class VarResolver {
71 /**
72 * Builder class.
73 */
74 public static class Builder extends BeanBuilder<VarResolver> {
75
76 final VarList vars;
77
78 /**
79 * Constructor.
80 */
81 protected Builder() {
82 super(VarResolver.class, BeanStore.create().build());
83 vars = VarList.create();
84 }
85
86 /**
87 * Copy constructor.
88 *
89 * @param copyFrom The bean to copy from.
90 */
91 protected Builder(VarResolver copyFrom) {
92 super(copyFrom.getClass(), copyFrom.beanStore);
93 vars = VarList.of(copyFrom.vars);
94 }
95
96 /**
97 * Adds a bean to the bean store in this session.
98 *
99 * @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 }