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}