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}