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.svl; 018 019import static org.apache.juneau.commons.utils.CollectionUtils.*; 020 021import java.io.*; 022import java.util.*; 023import java.util.concurrent.*; 024 025import org.apache.juneau.*; 026import org.apache.juneau.cp.*; 027import org.apache.juneau.svl.vars.*; 028 029/** 030 * Utility class for resolving variables of the form <js>"$X{key}"</js> in strings. 031 * 032 * <p> 033 * Variables are of the form <c>$X{key}</c>, where <c>X</c> can consist of zero or more ASCII characters. 034 * <br>The variable key can contain anything, even nested variables that get recursively resolved. 035 * 036 * <p> 037 * Variables are defined through the {@link Builder#vars(Class[])} method. 038 * 039 * <p> 040 * The {@link Var} interface defines how variables are converted to values. 041 * 042 * <h5 class='section'>Example:</h5> 043 * <p class='bjava'> 044 * <jk>public class</jk> SystemPropertiesVar <jk>extends</jk> SimpleVar { 045 * 046 * <jc>// Must have a no-arg constructor!</jc> 047 * <jk>public</jk> SystemPropertiesVar() { 048 * <jk>super</jk>(<js>"S"</js>); 049 * } 050 * 051 * <ja>@Override</ja> 052 * <jk>public</jk> String resolve(VarResolverSession <jv>session</jv>, String <jv>value</jv>) { 053 * <jk>return</jk> System.<jsm>getProperty</jsm>(<jv>value</jv>); 054 * } 055 * } 056 * 057 * <jc>// Create a variable resolver that resolves system properties (e.g. "$S{java.home}")</jc> 058 * VarResolver <jv>varResolver</jv> = VarResolver.<jsm>create</jsm>().vars(SystemPropertiesVar.<jk>class</jk>).build(); 059 * 060 * <jc>// Use it!</jc> 061 * System.<jsf>out</jsf>.println(<jv>varResolver</jv>.resolve(<js>"java.home is set to $S{java.home}"</js>)); 062 * </p> 063 * 064 * <h5 class='section'>See Also:</h5><ul> 065 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SimpleVariableLanguageBasics">Simple Variable Language Basics</a> 066 067 * </ul> 068 */ 069@SuppressWarnings("resource") 070public class VarResolver { 071 /** 072 * Builder class. 073 */ 074 public static class Builder extends BeanBuilder<VarResolver> { 075 076 final VarList vars; 077 078 /** 079 * Constructor. 080 */ 081 protected Builder() { 082 super(VarResolver.class, BeanStore.create().build()); 083 vars = VarList.create(); 084 } 085 086 /** 087 * Copy constructor. 088 * 089 * @param copyFrom The bean to copy from. 090 */ 091 protected Builder(VarResolver copyFrom) { 092 super(copyFrom.getClass(), copyFrom.beanStore); 093 vars = VarList.of(copyFrom.vars); 094 } 095 096 /** 097 * Adds a bean to the bean store in this session. 098 * 099 * @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}