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