View Javadoc
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 }