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.ThrowableUtils.*; 020 021import java.io.*; 022import java.util.function.*; 023 024/** 025 * A pre-compiled SVL template. 026 * 027 * <p> 028 * A {@link VarTemplate} is the result of compiling a template string against a {@link VarResolver}. 029 * It separates the "parse" step from the "evaluate" step so that repeated resolutions of the same 030 * input string skip tokenization, var-registry lookup, and (eventually) function-dispatch metadata. 031 * 032 * <p> 033 * Templates are obtained via {@link VarResolver#compile(String)} or 034 * {@link VarResolverSession#compile(String)} — there is no public constructor. 035 * 036 * <h5 class='section'>Compile-binding contract:</h5> 037 * <p> 038 * A compiled template is bound to the {@link VarResolver} instance it was compiled against. Cached 039 * {@link Var} (and, in the future, {@link VarFunction}) references are resolved at compile time. 040 * Passing a {@link VarTemplate} produced by one resolver to a session belonging to a different 041 * resolver is undefined behavior. Rebuilding the resolver invalidates any compiled templates from 042 * the previous instance — caller's responsibility to re-compile. 043 * 044 * <h5 class='section'>Threadsafety:</h5> 045 * <p> 046 * {@link VarTemplate} instances are immutable and threadsafe — the same template can be safely 047 * shared across threads. Note that {@link VarResolverSession} is <b>not</b> threadsafe; the 048 * session passed to {@link #resolve(VarResolverSession)} or {@link #asSupplier(VarResolverSession)} 049 * inherits the standard session-threadsafety contract. The {@link #asSupplierWithFreshSessions(VarResolver)} 050 * variant opens a fresh session per call and is safe to share across threads. 051 * 052 * <h5 class='section'>Which Supplier do I want?</h5> 053 * <ul> 054 * <li>{@link #asSupplierWithFreshSessions(VarResolver)} — opens a fresh session per 055 * {@code .get()} call. Safe to share across threads. Use this for user-facing code, 056 * hot-reload patterns, and any situation where the {@code Supplier} may be invoked 057 * on threads other than the caller's. <b>Recommended default.</b> 058 * <li>{@link #asSupplier(VarResolverSession)} — captures the existing session and reuses 059 * it on every {@code .get()} call. <b>Inherits the session's threadsafety contract</b> 060 * (i.e. not threadsafe). Use only in perf-sensitive paths where the caller owns the 061 * session lifecycle. 062 * </ul> 063 * 064 * <h5 class='section'>See Also:</h5><ul> 065 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/MarshallSimpleVariableLanguage">Marshall Simple Variable Language</a> 066 * </ul> 067 */ 068@SuppressWarnings({ 069 "resource" // 'out' and 'session' are caller-owned; this method must not close them. 070}) 071public final class VarTemplate { 072 073 /** 074 * The raw template string captured at compile time. Returned by {@link #getSource()}. 075 */ 076 private final String input; 077 078 /** 079 * The resolver this template was compiled against. Used by 080 * {@link #asSupplierWithFreshSessions(VarResolver)} to open fresh sessions and by tests for 081 * resolver-binding diagnostics. 082 */ 083 private final VarResolver resolver; 084 085 /** 086 * The compiled segments. Walked in order by {@link #resolve(VarResolverSession)} and 087 * {@link #resolveTo(VarResolverSession, Writer)}. Empty array for empty input; single 088 * {@link LiteralSegment} for inputs with no special chars; mixed sequence otherwise. 089 * 090 * <p> 091 * Compile-time stable-value folding may replace {@link VarRefSegment} entries with 092 * {@link LiteralSegment} entries when the wrapped {@link Var} opts in via 093 * {@link Var#isStable()}. 094 */ 095 private final TemplateSegment[] segments; 096 097 /** 098 * Cached {@link #isLiteral()} result. Computed once at construction by inspecting 099 * {@link #segments}. 100 */ 101 private final boolean literal; 102 103 /** 104 * Pre-computed result for literal-only templates. {@code null} when {@link #literal} is 105 * {@code false}. 106 * 107 * <p> 108 * <b>Why this is not just {@link #input}:</b> escape interpretation can produce a literal 109 * whose text differs from the source. For example {@code "\${literal}"} compiles to a single 110 * {@link LiteralSegment} carrying the string {@code "${literal}"} — the resolved output is 111 * <i>not</i> the raw input. Compile-time stable-value folding similarly produces 112 * literal-only templates whose resolved text is the folded value, not the source. Caching 113 * the joined literal text once at compile time avoids walking segments on every resolve. 114 */ 115 private final String literalText; 116 117 /** 118 * Package-private constructor — instances are created exclusively via 119 * {@link VarTemplateCompiler#compile(VarResolver, String)} (which is invoked by 120 * {@link VarResolver#compile(String)} and {@link VarResolverSession#compile(String)}). 121 * 122 * @param resolver The resolver this template is bound to. 123 * @param input The raw template string. 124 * @param segments The compiled segments. 125 */ 126 VarTemplate(VarResolver resolver, String input, TemplateSegment[] segments) { 127 this.resolver = resolver; 128 this.input = input; 129 this.segments = segments; 130 this.literal = computeIsLiteral(segments); 131 // For literal-only templates, pre-compute the resolved text. For null source we keep 132 // literalText null so resolve() returns null (matching VarResolverSession.resolve(null)). 133 if (literal && input != null) 134 this.literalText = joinLiteralSegments(segments); 135 else 136 this.literalText = null; 137 } 138 139 /** 140 * Resolves this template against the given session. 141 * 142 * <p> 143 * Walks the cached segment array in order, accumulating the result in a {@link StringBuilder}. 144 * Literal-only templates fast-path to a single allocation skip and return the source string 145 * directly. 146 * 147 * @param session The session to resolve against. Must not be {@code null}. The session is 148 * not required to belong to the same {@link VarResolver} this template was compiled 149 * against, but cached {@link Var} references will only honor that resolver's registry — 150 * see the "compile-binding contract" note in the class javadoc. 151 * @return The resolved string, or {@code null} if this template was compiled from a {@code null} source 152 * (mirroring {@link VarResolverSession#resolve(String)} returning {@code null} for {@code null} input). 153 * Otherwise never {@code null}. 154 */ 155 public String resolve(VarResolverSession session) { 156 if (literal) 157 return literalText; 158 var sb = new StringBuilder(input.length() * 2); 159 for (var seg : segments) 160 seg.resolve(session, sb); 161 return sb.toString(); 162 } 163 164 /** 165 * Resolves this template against the given session and writes the result directly to 166 * {@code out}. 167 * 168 * <p> 169 * Streamed-Var-bearing segments write through to {@code out} without materializing an 170 * intermediate {@code String}. Used by {@link VarResolver#resolveTo(String, Writer)} so 171 * downstream serializers can stream large content efficiently. 172 * 173 * @param session The session to resolve against. Must not be {@code null}. 174 * @param out The writer to append to. Must not be {@code null}. 175 * @return {@code out}, for fluent-API chaining. 176 * @throws IOException Thrown by the underlying stream. 177 */ 178 public Writer resolveTo(VarResolverSession session, Writer out) throws IOException { 179 if (literal) { 180 out.write(literalText); 181 return out; 182 } 183 for (var seg : segments) 184 seg.resolveTo(session, out); 185 return out; 186 } 187 188 /** 189 * Returns a {@link Supplier} that resolves this template against the given session each time 190 * its {@link Supplier#get()} method is called. 191 * 192 * <p> 193 * The captured session is reused across {@code .get()} calls; this Supplier is <b>not</b> 194 * threadsafe (it inherits the session's threadsafety contract). For a threadsafe variant 195 * use {@link #asSupplierWithFreshSessions(VarResolver)}. 196 * 197 * @param session The session to capture. Must not be {@code null}. 198 * @return A {@code Supplier<String>} bound to {@code session}. 199 */ 200 public Supplier<String> asSupplier(VarResolverSession session) { 201 return () -> resolve(session); 202 } 203 204 /** 205 * Returns a threadsafe {@link Supplier} that opens a fresh {@link VarResolverSession} on 206 * every {@link Supplier#get()} call. 207 * 208 * <p> 209 * Use this variant when the returned {@code Supplier} may be invoked from threads other 210 * than the caller's — e.g. when stashed on a long-lived bean field or shared across 211 * worker threads. The fresh-session-per-{@code .get()} pattern is the recommended default 212 * for user-facing code. 213 * 214 * @param vr The resolver to open sessions against. Must not be {@code null}. Typically 215 * the same resolver this template was compiled against. 216 * @return A threadsafe {@code Supplier<String>}. 217 */ 218 public Supplier<String> asSupplierWithFreshSessions(VarResolver vr) { 219 return () -> resolve(vr.createSession()); 220 } 221 222 /** 223 * Returns {@code true} if this template contains no variable references and resolves to a 224 * fixed string. 225 * 226 * <p> 227 * Computed at compile time by inspecting the segment array: {@code true} iff the array is 228 * empty or contains only {@link LiteralSegment} entries. Stable-value folding may flip a 229 * template with stable {@link Var}s to literal by replacing the relevant {@link VarRefSegment} 230 * entries with {@link LiteralSegment} entries at compile time. 231 * 232 * <p> 233 * Callers can use this hint to fast-path purely-literal templates — for example, by skipping 234 * the {@code Supplier} wrapping overhead for a {@code @Value("plain string")} field: 235 * 236 * <p class='bjava'> 237 * <jk>var</jk> <jv>tpl</jv> = <jv>vr</jv>.compile(<jv>raw</jv>); 238 * <jk>if</jk> (<jv>tpl</jv>.isLiteral()) 239 * <jc>// No need to wrap in a Supplier; the value never changes.</jc> 240 * <jk>return</jk> <jv>tpl</jv>.resolve(<jv>session</jv>); 241 * </p> 242 * 243 * @return {@code true} if the template resolves to a fixed string. 244 */ 245 public boolean isLiteral() { 246 return literal; 247 } 248 249 /** 250 * Returns the raw input string this template was compiled from. 251 * 252 * <p> 253 * Useful for diagnostics, error messages, and debugging. The string is preserved verbatim 254 * including any escape sequences. 255 * 256 * @return The raw input string. 257 */ 258 public String getSource() { 259 return input; 260 } 261 262 /** 263 * The resolver this template is bound to. 264 * 265 * <p> 266 * Package-private accessor for use by {@link VarResolver}/{@link VarResolverSession} and 267 * test code. Callers that need fresh-session resolution should use 268 * {@link #asSupplierWithFreshSessions(VarResolver)} instead. 269 * 270 * @return The bound resolver. 271 */ 272 VarResolver getResolver() { 273 return resolver; 274 } 275 276 /** 277 * Package-private accessor for the compiled segments. Used by tests and by the 278 * stable-value folding pass. 279 * 280 * @return The compiled segments. The array is shared (not copied); callers must not mutate. 281 */ 282 TemplateSegment[] segments() { 283 return segments; 284 } 285 286 /** 287 * Inspects the segment array to determine whether this template is purely literal. 288 * 289 * <p> 290 * Implementation detail: empty array → literal; all-{@link LiteralSegment} → literal; 291 * anything else → not literal. 292 */ 293 private static boolean computeIsLiteral(TemplateSegment[] segments) { 294 if (segments == null || segments.length == 0) 295 return true; 296 for (var seg : segments) 297 if (!seg.isLiteral()) 298 return false; 299 return true; 300 } 301 302 /** 303 * Concatenates all {@link LiteralSegment} text into a single string. Caller must have 304 * verified {@link #computeIsLiteral(TemplateSegment[])} is {@code true} before calling. 305 * 306 * <p> 307 * Used to pre-compute {@link #literalText} at construction time so the literal-only 308 * fast-path in {@link #resolve(VarResolverSession)} avoids walking segments per call. 309 */ 310 private static String joinLiteralSegments(TemplateSegment[] segments) { 311 if (segments.length == 0) 312 return ""; 313 if (segments.length == 1) 314 return ((LiteralSegment) segments[0]).text; 315 var sb = new StringBuilder(); 316 for (var seg : segments) 317 sb.append(((LiteralSegment) seg).text); 318 return sb.toString(); 319 } 320 321 /** 322 * Convenience helper for callers that need to {@link #resolveTo(VarResolverSession, Writer)} 323 * but don't want to handle {@link IOException}. Wraps any thrown {@link IOException} in a 324 * {@link RuntimeException} via the standard {@code ThrowableUtils.toRex} helper. 325 * 326 * @param session The session to resolve against. Must not be {@code null}. 327 * @param out The writer to append to. Must not be {@code null}. 328 * @return {@code out}. 329 */ 330 public Writer resolveToUnchecked(VarResolverSession session, Writer out) { 331 try { 332 return resolveTo(session, out); 333 } catch (IOException e) { 334 throw toRex(e); 335 } 336 } 337 338 @Override /* Overridden from Object */ 339 public String toString() { 340 return "VarTemplate[" + input + "]"; 341 } 342}