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