Continued work on TemplateLanguage configuration and new file extensions: Renamed...
[freemarker.git] / freemarker-core / src / main / java / org / apache / freemarker / core / Template.java
1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 package org.apache.freemarker.core;
21
22 import java.io.BufferedInputStream;
23 import java.io.BufferedReader;
24 import java.io.FilterReader;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.io.PrintStream;
29 import java.io.Reader;
30 import java.io.Serializable;
31 import java.io.StringReader;
32 import java.io.StringWriter;
33 import java.io.Writer;
34 import java.lang.reflect.UndeclaredThrowableException;
35 import java.nio.charset.Charset;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.HashMap;
39 import java.util.LinkedHashMap;
40 import java.util.List;
41 import java.util.Locale;
42 import java.util.Map;
43 import java.util.TimeZone;
44 import java.util.concurrent.ConcurrentHashMap;
45
46 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
47 import org.apache.freemarker.core.debug._DebuggerService;
48 import org.apache.freemarker.core.model.ObjectWrapper;
49 import org.apache.freemarker.core.model.ObjectWrappingException;
50 import org.apache.freemarker.core.model.TemplateHashModel;
51 import org.apache.freemarker.core.model.TemplateModel;
52 import org.apache.freemarker.core.model.TemplateNodeModel;
53 import org.apache.freemarker.core.model.impl.SimpleHash;
54 import org.apache.freemarker.core.outputformat.OutputFormat;
55 import org.apache.freemarker.core.templateresolver.TemplateLoader;
56 import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
57 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
58 import org.apache.freemarker.core.templateresolver.impl.FileTemplateLoader;
59 import org.apache.freemarker.core.util.BugException;
60 import org.apache.freemarker.core.util._CollectionUtils;
61 import org.apache.freemarker.core.util._NullArgumentException;
62 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
63 import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
64
65 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
66
67 /**
68 * Stores an already parsed template, ready to be processed (rendered) for unlimited times, possibly from multiple
69 * threads.
70 * <p>
71 * Typically, you will use {@link Configuration#getTemplate(String)} to invoke/get {@link Template} objects, so you
72 * don't construct them directly. But you can also construct a template from a {@link Reader} or a {@link String} that
73 * contains the template source code. But then it's important to know that while the resulting {@link Template} is
74 * efficient for later processing, creating a new {@link Template} itself is relatively expensive. So try to re-use
75 * {@link Template} objects if possible. {@link Configuration#getTemplate(String)} (and its overloads) does that
76 * (caching {@link Template}-s) for you, but the constructor of course doesn't, so it's up to you to solve then.
77 * <p>
78 * The {@link ProcessingConfiguration} reader methods of this class don't throw {@link CoreSettingValueNotSetException}
79 * because unset settings are ultimately inherited from {@link Configuration}.
80 * <p>
81 * Objects of this class are immutable and thus thread-safe.
82 */
83 // TODO [FM3] Try to make Template serializable for distributed caching. Transient fields will have to be restored.
84 public class Template implements ProcessingConfiguration, CustomStateScope {
85 public static final String DEFAULT_NAMESPACE_PREFIX = "D";
86 public static final String NO_NS_PREFIX = "N";
87
88 private static final int READER_BUFFER_SIZE = 8192;
89
90 private ASTElement rootElement;
91 private Map macros = new HashMap(); // TODO Don't create new object if it remains empty.
92 private List imports = new ArrayList(); // TODO Don't create new object if it remains empty.
93
94 // Source (TemplateLoader) related information:
95 private final String sourceName;
96 // TODO [FM3] Get rid of this...
97 private final ArrayList<String> lines = new ArrayList<>();
98
99 // TODO [FM3] We want to get rid of these, then the same Template object could be reused for different lookups.
100 // Template lookup parameters:
101 private final String lookupName;
102 private Locale lookupLocale;
103 private Serializable customLookupCondition;
104
105 // Inherited settings:
106 private final transient Configuration cfg;
107 private final transient TemplateConfiguration tCfg;
108 private final transient ParsingConfiguration pCfg;
109
110 // Values from the template content (#ftl header parameters usually), as opposed to from the TemplateConfiguration:
111 private transient OutputFormat outputFormat; // TODO Deserialization: use the name of the output format
112 private String defaultNS;
113 private Map prefixToNamespaceURILookup = new HashMap();
114 private Map namespaceURIToPrefixLookup = new HashMap();
115 /** Custom settings specified inside the template with the #ftl directive. Maybe {@code null}. */
116 private Map<String, Serializable> headerCustomSettings;
117 /**
118 * In case {@link #headerCustomSettings} is not {@code null} and the {@link TemplateConfiguration} also specifies
119 * custom settings, this is the two set of custom settings merged. Otherwise it's {@code null}.
120 */
121 private transient Map<Serializable, Object> tcAndHeaderCustomSettings;
122
123 private AutoEscapingPolicy autoEscapingPolicy;
124 // Values from template content that are detected automatically:
125 private Charset actualSourceEncoding;
126 TagSyntax actualTagSyntax; // TODO [FM3][CF] Should be private
127 private InterpolationSyntax interpolationSyntax;
128
129 // Custom state:
130 private final Object customStateMapLock = new Object();
131 private final ConcurrentHashMap<CustomStateKey, Object> customStateMap = new ConcurrentHashMap<>(0);
132
133 /**
134 * Indicates that the Template constructor has run completely.
135 */
136 private boolean writeProtected;
137
138 /**
139 * Same as {@link #Template(String, String, Reader, Configuration)} with {@code null} {@code sourceName} parameter.
140 */
141 public Template(String lookupName, Reader reader, Configuration cfg) throws IOException {
142 this(lookupName, null, reader, cfg);
143 }
144
145 /**
146 * Convenience constructor for {@link #Template(String, Reader, Configuration)
147 * Template(lookupName, new StringReader(reader), cfg)}.
148 */
149 public Template(String lookupName, String sourceCode, Configuration cfg) throws IOException {
150 this(lookupName, new StringReader(sourceCode), cfg);
151 }
152
153 /**
154 * Convenience constructor for {@link #Template(String, String, Reader, Configuration, TemplateConfiguration,
155 * Charset) Template(lookupName, null, new StringReader(reader), cfg), tc, null}.
156 */
157 public Template(String lookupName, String sourceCode, Configuration cfg, TemplateConfiguration tc) throws IOException {
158 this(lookupName, null, new StringReader(sourceCode), cfg, tc, null);
159 }
160
161 /**
162 * Convenience constructor for {@link #Template(String, String, Reader, Configuration, Charset) Template(lookupName, null,
163 * reader, cfg, sourceEncoding)}.
164 */
165 public Template(String lookupName, Reader reader, Configuration cfg, Charset sourceEncoding) throws IOException {
166 this(lookupName, null, reader, cfg, sourceEncoding);
167 }
168
169 /**
170 * Constructs a template from a character stream. Note that this is a relatively expensive operation; where higher
171 * performance matters, you should re-use (cache) {@link Template} instances instead of re-creating them from the
172 * same source again and again. ({@link Configuration#getTemplate(String) and its overloads already do such reuse.})
173 *
174 * @param lookupName
175 * The name (path) with which the template was get (usually via
176 * {@link Configuration#getTemplate(String)}), after basic normalization. (Basic normalization means
177 * things that doesn't require accessing the backing storage, such as {@code "/a/../b/foo.f3ah"}
178 * becomes to {@code "b/foo.f3ah"}).
179 * This is usually the path of the template file relatively to the (virtual) directory that you use to
180 * store the templates (except if the {@link #getSourceName()} sourceName} differs from it).
181 * Shouldn't start with {@code '/'}. Should use {@code '/'}, not {@code '\'}. Check
182 * {@link #getLookupName()} to see how the name will be used. The name should be independent of the actual
183 * storage mechanism and physical location as far as possible. Even when the templates are stored
184 * straightforwardly in real files (they often aren't; see {@link TemplateLoader}), the name shouldn't be
185 * an absolute file path. Like if the template is stored in {@code "/www/templates/forum/main.f3ah"}, and
186 * you are using {@code "/www/templates/"} as the template root directory via
187 * {@link FileTemplateLoader#FileTemplateLoader(java.io.File)}, then the template name will be
188 * {@code "forum/main.f3ah"}. The name can be {@code null} (should be used for template made on-the-fly
189 * instead of being loaded from somewhere), in which case relative paths in it will be relative to
190 * the template root directory (and here again, it's the {@link TemplateLoader} that knows what that
191 * "physically" means).
192 * @param sourceName
193 * Often the same as the {@code lookupName}; see {@link #getSourceName()} for more. Can be
194 * {@code null}, in which case error messages will fall back to use {@link #getLookupName()}.
195 * @param reader
196 * The character stream to read from. The {@link Reader} is <em>not</em> closed by this method (unlike
197 * in FreeMarker 2.x.x), so be sure that it's closed somewhere. (Except of course, readers like
198 * {@link StringReader} need not be closed.) The {@link Reader} need not be buffered, because this
199 * method ensures that it will be read in few kilobyte chunks, not byte by byte.
200 * @param cfg
201 * The Configuration object that this Template is associated with. Can't be {@code null}.
202 */
203 public Template(
204 String lookupName, String sourceName, Reader reader, Configuration cfg) throws IOException {
205 this(lookupName, sourceName, reader, cfg, null);
206 }
207
208 /**
209 * Same as {@link #Template(String, String, Reader, Configuration)}, but also specifies the template's source
210 * encoding.
211 *
212 * @param actualSourceEncoding
213 * This is the charset that was used to read the template. This sould be {@code null} if the template
214 * was loaded from a source that returns it already as text. If this is not {@code null} and there's an
215 * {@code #ftl} header with {@code encoding} parameter, they must match, or else a
216 * {@link WrongTemplateCharsetException} is thrown.
217 */
218 public Template(
219 String lookupName, String sourceName, Reader reader, Configuration cfg, Charset actualSourceEncoding) throws
220 IOException {
221 this(lookupName, sourceName, reader, cfg, null, actualSourceEncoding);
222 }
223
224 /**
225 * Same as {@link #Template(String, String, Reader, Configuration, Charset)}, but also specifies a
226 * {@link TemplateConfiguration}. This is mostly meant to be used by FreeMarker internally, but advanced users might
227 * still find this useful.
228 *
229 * @param templateConfiguration
230 * Overrides the configuration settings of the {@link Configuration} parameter; can be
231 * {@code null}. This is useful as the {@link Configuration} is normally a singleton shared by all
232 * templates, and so it's not good for specifying template-specific settings. Settings that influence
233 * parsing always have an effect, while settings that influence processing only have effect when the
234 * template is the main template of the {@link Environment}.
235 */
236 public Template(
237 String lookupName, String sourceName,
238 Reader reader,
239 Configuration cfg, TemplateConfiguration templateConfiguration, Charset actualSourceEncoding)
240 throws IOException, ParseException {
241 this(lookupName, sourceName,
242 null, actualSourceEncoding, reader,
243 cfg, templateConfiguration,
244 null, null);
245 }
246
247 /**
248 * See {@link #Template(String, String, Reader, Configuration, TemplateConfiguration, Charset)}, except that this
249 * one expects an {@link InputStream} and an initial {@link Charset}.
250 *
251 * @param initialEncoding
252 * The {@link Charset} we try to decode the {@link InputStream} with. If the template language specifies
253 * its own encoding, this will be overridden by that (hence it's just "inital"), however, that requires
254 * that the template is still parseable with the initial encoding (in practice, that at least the
255 * US-ASCII characters are decoded correctly).
256 */
257 public Template(
258 String lookupName, String sourceName,
259 InputStream inputStream, Charset initialEncoding,
260 Configuration cfg, TemplateConfiguration templateConfiguration) throws IOException, ParseException {
261 this(lookupName, sourceName,
262 inputStream, initialEncoding, null,
263 cfg, templateConfiguration,
264 null, null);
265 }
266
267 /**
268 * Same as the other overloads, but allows specifying the output format and the auto escaping policy, with similar
269 * effect as if they were specified in the template content (like in the #ftl header).
270 * <p>
271 * <p>
272 * This method is currently only used internally, as it's not generalized enough and so it carries too much backward
273 * compatibility risk. Also, the same functionality can be achieved by constructing an appropriate
274 * {@link TemplateConfiguration}, only that's somewhat slower.
275 *
276 * @param inputStream
277 * Exactly one of this and the {@code reader} must be non-null. This is normally used if the source is
278 * binary (not textual), that is, we have to use a charset to decode it to text.
279 * @param initialEncoding
280 * If {@code inputStream} is not-{@code null} then this is the charset we try to use (and so it can't be
281 * {@code null}), but might will be overridden in the template header, in which case this method manages
282 * the re-decoding internally, and the caller need not worry about it. If {@code reader} is
283 * non-{@code null}, and this is non-{@code null}, it's used to check if the charset specified in the
284 * template (if any) matches, and if not, a {@link WrongTemplateCharsetException} is thrown that the
285 * caller is expected to handle. If the template source code comes from a textual source, and so you
286 * don't care about the charset specified in the template, then use {@code null} here (with a
287 * non-{@code null} {@code reader} parameter of course), and then the check should be omitted by the
288 * {@link TemplateLanguage} implementation, so you need not expect a
289 * {@link WrongTemplateCharsetException}.
290 * @param reader
291 * Exactly one of this and the {@code inputStream} must be non-null. This is normally used if the source
292 * is textual (not binary), that is, we don't have to worry about the charset to interpret it.
293 * @param contextOutputFormat
294 * The output format of the enclosing lexical context, used when a template snippet is parsed on runtime.
295 * If not {@code null}, this will override the value coming from the {@link TemplateConfiguration} or the
296 * {@link Configuration}.
297 * @param contextAutoEscapingPolicy
298 * Similar to {@code contextOutputFormat}; usually this and the that is set together.
299 */
300 Template(
301 String lookupName, String sourceName,
302 InputStream inputStream, Charset initialEncoding, Reader reader,
303 Configuration configuration, TemplateConfiguration templateConfiguration,
304 OutputFormat contextOutputFormat, AutoEscapingPolicy contextAutoEscapingPolicy)
305 throws IOException, ParseException {
306 _NullArgumentException.check("configuration", configuration);
307 this.cfg = configuration;
308 this.tCfg = templateConfiguration;
309 // this.pCfg = ...
310 {
311 ParsingConfiguration pCfgWithFallback = tCfg != null
312 ? new ParsingConfigurationWithFallback(tCfg, cfg) : cfg;
313
314 TemplateLanguage tempLangFromPCfg = pCfgWithFallback.getTemplateLanguage();
315 TemplateLanguage tempLangFromExt = pCfgWithFallback.getRecognizeStandardFileExtensions()
316 ? configuration.getTemplateLanguageForTemplateName(sourceName != null ? sourceName : lookupName)
317 : null;
318 if (tempLangFromExt != null && tempLangFromExt != tempLangFromPCfg) {
319 this.pCfg = new ParsingConfigurationWithTemplateLanguageOverride(pCfgWithFallback, tempLangFromExt);
320 } else {
321 this.pCfg = pCfgWithFallback;
322 }
323 }
324
325 this.lookupName = lookupName;
326 this.sourceName = sourceName;
327
328 boolean inputStreamMarked;
329 Charset guessedEncoding = initialEncoding;
330 boolean closeReader;
331 if (reader != null) {
332 if (inputStream != null) {
333 throw new IllegalArgumentException("Both the Reader and InputStream was non-null.");
334 }
335 closeReader = false; // It wasn't created by this method, so it's not our responsibility
336 inputStreamMarked = false; // N/A
337 } else if (inputStream != null) {
338 if (pCfg.getTemplateLanguage().getCanSpecifyEncodingInContent()) {
339 // We need mark support, to restart if the charset suggested by the template content differs
340 // from the one we use initially.
341 if (!inputStream.markSupported()) {
342 inputStream = new BufferedInputStream(inputStream);
343 }
344 inputStream.mark(Integer.MAX_VALUE); // Mark will be released as soon as we know the charset for sure.
345 inputStreamMarked = true;
346 } else {
347 inputStreamMarked = false;
348 }
349 // Regarding buffering worries: On the Reader side we should only read in chunks (like through a
350 // BufferedReader), so there shouldn't be a problem if the InputStream is not buffered. (Also, at least
351 // on Oracle JDK and OpenJDK 7 the InputStreamReader itself has an internal ~8K buffer.)
352 reader = new InputStreamReader(inputStream, initialEncoding);
353 closeReader = true; // Because it was created here.
354 } else {
355 throw new IllegalArgumentException("Both the Reader and InputStream was null.");
356 }
357
358 try {
359 try {
360 parseWithEncoding(
361 reader,
362 contextOutputFormat, contextAutoEscapingPolicy,
363 guessedEncoding, inputStreamMarked ? inputStream : null);
364 } catch (WrongTemplateCharsetException charsetException) {
365 final Charset templateSpecifiedEncoding = charsetException.getTemplateSpecifiedEncoding();
366
367 if (inputStreamMarked) {
368 // We restart InputStream to re-decode it with the new charset.
369 inputStream.reset();
370
371 // Don't close `reader`; it's an InputStreamReader that would close the wrapped InputStream.
372 reader = new InputStreamReader(inputStream, templateSpecifiedEncoding);
373 } else {
374 // We can't handle it, the caller should.
375 throw charsetException;
376 }
377 parseWithEncoding(
378 reader,
379 contextOutputFormat, contextAutoEscapingPolicy,
380 templateSpecifiedEncoding, inputStream);
381 }
382 } finally {
383 if (closeReader) {
384 reader.close();
385 }
386 }
387
388 _DebuggerService.registerTemplate(this);
389 namespaceURIToPrefixLookup = _CollectionUtils.unmodifiableMap(namespaceURIToPrefixLookup);
390 prefixToNamespaceURILookup = _CollectionUtils.unmodifiableMap(prefixToNamespaceURILookup);
391
392 finishConstruction();
393 }
394
395 /**
396 * This was extracted from the {@link Template} constructor, so that we can do this again if the attempted charset
397 * was incorrect.
398 *
399 * @param streamToUnmarkWhenEncEstabd
400 * When the parser meets a directive that established the charset, this is where it should release the
401 * mark (so we don't buffer unnecessarily). Maybe {@code null}.
402 */
403 private void parseWithEncoding(
404 Reader reader, OutputFormat contextOutputFormat, AutoEscapingPolicy contextAutoEscapingPolicy,
405 Charset attemptedCharset, InputStream streamToUnmarkWhenEncEstabd) throws ParseException, IOException {
406 setActualSourceEncoding(attemptedCharset);
407 LineTableBuilder ltbReader;
408 try {
409 // Ensure that the parameter Reader is only read in bigger chunks, as we don't know if the it's buffered.
410 // In particular, inside the FreeMarker code, we assume that the stream stages need not be buffered.
411 if (!(reader instanceof BufferedReader) && !(reader instanceof StringReader)) {
412 reader = new BufferedReader(reader, READER_BUFFER_SIZE);
413 }
414
415 lines.clear();
416 ltbReader = new LineTableBuilder(reader, pCfg, lines);
417 reader = ltbReader;
418
419 try {
420 try {
421 rootElement = pCfg.getTemplateLanguage().parse(
422 this, reader,
423 pCfg, contextOutputFormat, contextAutoEscapingPolicy,
424 streamToUnmarkWhenEncEstabd);
425 } catch (IndexOutOfBoundsException exc) {
426 // There's a JavaCC bug where the Reader throws a RuntimeExcepton and then JavaCC fails with
427 // IndexOutOfBoundsException. If that wasn't the case, we just rethrow. Otherwise we suppress the
428 // IndexOutOfBoundsException and let the real cause to be thrown later.
429 if (!ltbReader.hasFailure()) {
430 throw exc;
431 }
432 rootElement = null;
433 }
434 } catch (TokenMgrError exc) {
435 // TokenMgrError VS ParseException is not an interesting difference for the user, so we just convert it
436 // to ParseException
437 throw exc.toParseException(this);
438 }
439 } catch (ParseException e) {
440 e.setTemplate(this);
441 throw e;
442 }
443
444 // Throws any exception that JavaCC has silently treated as EOF:
445 ltbReader.throwFailure();
446 }
447
448 /**
449 * {@link Template} is technically mutable (to simplify internals), but it has to be finalized and then write
450 * protected when construction is done.
451 */
452 private void finishConstruction() {
453 headerCustomSettings = _CollectionUtils.unmodifiableMap(headerCustomSettings);
454 tcAndHeaderCustomSettings = _CollectionUtils.unmodifiableMap(tcAndHeaderCustomSettings);
455 writeProtected = true;
456 }
457
458 private void checkWritable() {
459 if (writeProtected) {
460 throw new IllegalStateException("Template can't be modified anymore");
461 }
462 }
463
464 /**
465 * Same as {@link #createPlainTextTemplate(String, String, String, Configuration, Charset)} with {@code null}
466 * {@code sourceName} argument.
467 */
468 static public Template createPlainTextTemplate(String lookupName, String content, Configuration config) {
469 return createPlainTextTemplate(lookupName, null, content, config, null);
470 }
471
472 /**
473 * Creates a {@link Template} that only contains a single block of static text, no dynamic content.
474 *
475 * @param lookupName
476 * See {@link #getLookupName} for more details.
477 * @param sourceName
478 * See {@link #getSourceName} for more details. If {@code null}, it will be the same as the {@code name}.
479 * @param content
480 * the block of text that this template represents
481 * @param config
482 * the configuration to which this template belongs
483 *
484 * @param sourceEncoding The charset used to decode the template content to the {@link String} passed in with the
485 * {@code content} parameter. If that information is not known or irrelevant, this should be
486 * {@code null}.
487 */
488 static public Template createPlainTextTemplate(String lookupName, String sourceName, String content, Configuration config,
489 Charset sourceEncoding) {
490 Template template;
491 try {
492 template = new Template(lookupName, sourceName, new StringReader(""), config, sourceEncoding);
493 } catch (IOException e) {
494 throw new BugException("Plain text template creation failed", e);
495 }
496 ((ASTStaticText) template.rootElement).replaceText(content);
497
498 _DebuggerService.registerTemplate(template);
499
500 return template;
501 }
502
503 /**
504 * Executes template, using the data-model provided, writing the generated output to the supplied {@link Writer}.
505 *
506 * <p>
507 * For finer control over the runtime environment setup, such as per-HTTP-request configuring of FreeMarker
508 * settings, you may need to use {@link #createProcessingEnvironment(Object, Writer)} instead.
509 *
510 * @param dataModel
511 * the holder of the variables visible from the template (name-value pairs); usually a
512 * {@code Map<String, Object>} or a JavaBean (where the JavaBean properties will be the variables). Can
513 * be any object that the {@link ObjectWrapper} in use turns into a {@link TemplateHashModel}. You can
514 * also use an object that already implements {@link TemplateHashModel}; in that case it won't be
515 * wrapped. If it's {@code null}, an empty data model is used.
516 * @param out
517 * The {@link Writer} where the output of the template will go. Note that unless you have set
518 * {@link ProcessingConfiguration#getAutoFlush() autoFlush} to {@code false} to disable this,
519 * {@link Writer#flush()} will be called at the when the template processing was finished.
520 * {@link Writer#close()} is not called. Can't be {@code null}.
521 *
522 * @throws TemplateException
523 * if an exception occurs during template processing
524 * @throws ObjectWrappingException
525 * if the {@code dataModel} couldn't be wrapped; note that this extends {@link TemplateException}
526 * @throws IOException
527 * if an I/O exception occurs during writing to the writer.
528 */
529 public void process(Object dataModel, Writer out)
530 throws TemplateException, IOException {
531 createProcessingEnvironment(dataModel, out, null).process();
532 }
533
534 /**
535 * Like {@link #process(Object, Writer)}, but also sets a (XML-)node to be recursively processed by the template.
536 * That node is accessed in the template with <tt>.node</tt>, <tt>#recurse</tt>, etc. See the
537 * <a href="http://freemarker.org/docs/xgui_declarative.html" target="_blank">Declarative XML Processing</a> as a
538 * typical example of recursive node processing.
539 *
540 * @param rootNode The root node for recursive processing or {@code null}.
541 */
542 public void process(Object dataModel, Writer out, ObjectWrapper wrapper, TemplateNodeModel rootNode)
543 throws TemplateException, IOException {
544 Environment env = createProcessingEnvironment(dataModel, out, wrapper);
545 if (rootNode != null) {
546 env.setCurrentVisitorNode(rootNode);
547 }
548 env.process();
549 }
550
551 /**
552 * Like {@link #process(Object, Writer)}, but overrides the {@link Configuration#getObjectWrapper()}.
553 *
554 * @param wrapper The {@link ObjectWrapper} to be used instead of what {@link Configuration#getObjectWrapper()}
555 * provides, or {@code null} if you don't want to override that.
556 */
557 public void process(Object dataModel, Writer out, ObjectWrapper wrapper)
558 throws TemplateException, IOException {
559 createProcessingEnvironment(dataModel, out, wrapper).process();
560 }
561
562 /**
563 * Creates a {@link org.apache.freemarker.core.Environment Environment} object, using this template, the data-model provided as
564 * parameter. You have to call {@link Environment#process()} on the return value to set off the actual rendering.
565 *
566 * <p>Use this method if you want to do some special initialization on the {@link Environment} before template
567 * processing, or if you want to read the {@link Environment} after template processing. Otherwise using
568 * {@link Template#process(Object, Writer)} is simpler.
569 *
570 * <p>Example:
571 *
572 * <pre>
573 * Environment env = myTemplate.createProcessingEnvironment(root, out, null);
574 * env.process();</pre>
575 *
576 * <p>The above is equivalent with this:
577 *
578 * <pre>
579 * myTemplate.process(root, out);</pre>
580 *
581 * <p>But with <tt>createProcessingEnvironment</tt>, you can manipulate the environment
582 * before and after the processing:
583 *
584 * <pre>
585 * Environment env = myTemplate.createProcessingEnvironment(root, out);
586 *
587 * env.setLocale(myUsersPreferredLocale);
588 * env.setTimeZone(myUsersPreferredTimezone);
589 *
590 * env.process(); // output is rendered here
591 *
592 * TemplateModel x = env.getVariable("x"); // read back a variable set by the template</pre>
593 *
594 * @param dataModel the holder of the variables visible from all templates; see {@link #process(Object, Writer)} for
595 * more details.
596 * @param wrapper The {@link ObjectWrapper} to use to wrap objects into {@link TemplateModel}
597 * instances. Normally you left it {@code null}, in which case {@link Configuration#getObjectWrapper()} will be
598 * used.
599 * @param out The {@link Writer} where the output of the template will go; see {@link #process(Object, Writer)} for
600 * more details.
601 *
602 * @return the {@link Environment} object created for processing. Call {@link Environment#process()} to process the
603 * template.
604 *
605 * @throws TemplateException
606 * if an exception occurs during template processing
607 * @throws ObjectWrappingException
608 * if the {@code dataModel} couldn't be wrapped; note that this extends {@link TemplateException}
609 * @throws IOException
610 * if an I/O exception occurs during writing to the writer.
611 */
612 public Environment createProcessingEnvironment(Object dataModel, Writer out, ObjectWrapper wrapper)
613 throws TemplateException, IOException {
614 final TemplateHashModel dataModelHash;
615 if (dataModel instanceof TemplateHashModel) {
616 dataModelHash = (TemplateHashModel) dataModel;
617 } else {
618 if (wrapper == null) {
619 wrapper = getConfiguration().getObjectWrapper();
620 }
621
622 if (dataModel == null) {
623 dataModelHash = new SimpleHash(wrapper);
624 } else {
625 TemplateModel wrappedDataModel = wrapper.wrap(dataModel);
626 if (wrappedDataModel instanceof TemplateHashModel) {
627 dataModelHash = (TemplateHashModel) wrappedDataModel;
628 } else if (wrappedDataModel == null) {
629 throw new IllegalArgumentException(
630 wrapper.getClass().getName() + " converted " + dataModel.getClass().getName() + " to null.");
631 } else {
632 throw new IllegalArgumentException(
633 wrapper.getClass().getName() + " didn't convert " + dataModel.getClass().getName()
634 + " to a TemplateHashModel. Generally, you want to use a Map<String, Object> or a "
635 + "JavaBean as the root-map (aka. data-model) parameter. The Map key-s or JavaBean "
636 + "property names will be the variable names in the template.");
637 }
638 }
639 }
640 return new Environment(this, dataModelHash, out);
641 }
642
643 /**
644 * Same as {@link #createProcessingEnvironment(Object, Writer, ObjectWrapper)
645 * createProcessingEnvironment(dataModel, out, null)}.
646 */
647 public Environment createProcessingEnvironment(Object dataModel, Writer out)
648 throws TemplateException, IOException {
649 return createProcessingEnvironment(dataModel, out, null);
650 }
651
652 /**
653 * Returns a string representing the raw template
654 * text in canonical form.
655 */
656 @Override
657 public String toString() {
658 StringWriter sw = new StringWriter();
659 try {
660 dump(sw);
661 } catch (IOException ioe) {
662 throw new RuntimeException(ioe.getMessage());
663 }
664 return sw.toString();
665 }
666
667
668 /**
669 * The usually path-like (or URL-like) normalized identifier of the template, with which the template was get
670 * (usually via {@link Configuration#getTemplate(String)}), or possibly {@code null} for non-stored templates.
671 * It usually looks like a relative UN*X path; it should use {@code /}, not {@code \}, and shouldn't
672 * start with {@code /} (but there are no hard guarantees). It's not a real path in a file-system, it's just a name
673 * that a {@link TemplateLoader} used to load the backing resource (in simple cases; actually that name is
674 * {@link #getSourceName()}, but see it there). Or, it can also be a name that was never used to load the template
675 * (directly created with {@link #Template(String, Reader, Configuration)}). Even if the templates are stored
676 * straightforwardly in files, this is relative to the base directory of the {@link TemplateLoader}. So it really
677 * could be anything, except that it has importance in these situations:
678 *
679 * <p>
680 * Relative paths to other templates in this template will be resolved relatively to the directory part of this.
681 * Like if the template name is {@code "foo/this.f3ah"}, then {@code <#include "other.f3ah">} gets the template with
682 * name {@code "foo/other.f3ah"}.
683 * </p>
684 *
685 * <p>
686 * You should not use this name to indicate error locations, or to find the actual templates in general, because
687 * localized template lookup, acquisition and other lookup strategies can transform names before they get to the
688 * {@link TemplateLoader} (the template storage) mechanism. Use {@link #getSourceName()} for these purposes.
689 * </p>
690 *
691 * <p>
692 * Some frameworks use URL-like template names like {@code "someSchema://foo/bar.f3ah"}. FreeMarker understands this
693 * notation, so an absolute path like {@code "/baaz.f3ah"} in that template will be resolved too
694 * {@code "someSchema://baaz.f3ah"}.
695 */
696 public String getLookupName() {
697 return lookupName;
698 }
699
700 /**
701 * The name that was actually used to load this template from the {@link TemplateLoader} (or from other custom
702 * storage mechanism), or possibly {@code null} for non-stored templates. This is what should be shown in error
703 * messages as the error location. This is usually the same as {@link #getLookupName()}, except when localized
704 * template lookup, template acquisition ({@code *} step in the name), or other {@link TemplateLookupStrategy}
705 * transforms the requested name ({@link #getLookupName()}) to a different final {@link TemplateLoader}-level
706 * name. For example, when you get a template with name {@code "foo .f3ah"} then because of localized template
707 * lookup, it's possible that something like {@code "foo_en.f3ah"} will be loaded behind the scenes. While the
708 * template name will be still the same as the requested template name ({@code "foo .f3ah"}), errors should point
709 * to {@code "foo_de.f3ah"}. Note that relative paths are always resolved relatively to the {@code name}, not to
710 * the {@code sourceName}.
711 */
712 public String getSourceName() {
713 return sourceName;
714 }
715
716 /**
717 * Returns the {@linkplain #getSourceName() source name}, or if that's {@code null} then the
718 * {@linkplain #getLookupName() lookup name}. This name is primarily meant to be used in error messages.
719 */
720 public String getSourceOrLookupName() {
721 return getSourceName() != null ? getSourceName() : getLookupName();
722 }
723
724 /**
725 * Returns the Configuration object associated with this template.
726 */
727 public Configuration getConfiguration() {
728 return cfg;
729 }
730
731 /**
732 * The {@link TemplateConfiguration} associated to this template, or {@code null} if there was none.
733 */
734 public TemplateConfiguration getTemplateConfiguration() {
735 return tCfg;
736 }
737
738 /**
739 * The {@link ParsingConfiguration} that was actually used to parse this template; this is deduced from the
740 * other configurations, and the {@link TemplateLanguage} detected from the file extension.
741 */
742 public ParsingConfiguration getParsingConfiguration() {
743 return pCfg;
744 }
745
746
747 /**
748 * @param actualSourceEncoding
749 * The sourceEncoding that was used to read this template, or {@code null} if the source of the template
750 * already gives back text (as opposed to binary data), so no decoding with a charset was needed.
751 */
752 void setActualSourceEncoding(Charset actualSourceEncoding) {
753 checkWritable();
754 this.actualSourceEncoding = actualSourceEncoding;
755 }
756
757 /**
758 * The charset that was actually used to read this template from the binary source, or {@code null} if that
759 * information is not known.
760 * When using {@link DefaultTemplateResolver}, this is {@code null} exactly if the {@link TemplateLoader}
761 * returns text instead of binary content, which should only be the case for data sources that naturally return
762 * text (such as varchar and CLOB columns in a database).
763 */
764 public Charset getActualSourceEncoding() {
765 return actualSourceEncoding;
766 }
767
768 /**
769 * Gets the custom lookup condition with which this template was found. See the {@code customLookupCondition}
770 * parameter of {@link Configuration#getTemplate(String, Locale, Serializable, boolean)} for more
771 * explanation.
772 */
773 public Serializable getCustomLookupCondition() {
774 return customLookupCondition;
775 }
776
777 // TODO [FM3] Should be gone; see comment above the lookupLocale field
778 /**
779 * Mostly only used internally; setter pair of {@link #getCustomLookupCondition()}. This meant to be called directly
780 * after instantiating the template with its constructor, after a successfull lookup that used this condition. So
781 * this should only be called from code that deals with creating new {@code Template} objects, like from
782 * {@link DefaultTemplateResolver}.
783 */
784 public void setCustomLookupCondition(Serializable customLookupCondition) {
785 this.customLookupCondition = customLookupCondition;
786 }
787
788 /**
789 * Returns the output format (see {@link Configuration#getOutputFormat()}) used for this template.
790 * The output format of a template can come from various places, in order of increasing priority:
791 * {@link Configuration#getOutputFormat()}, {@link ParsingConfiguration#getOutputFormat()} (which is usually
792 * provided by {@link Configuration#getTemplateConfigurations()}) and the {@code #ftl} header's
793 * {@code outputFormat} option in the template.
794 */
795 public OutputFormat getOutputFormat() {
796 return outputFormat;
797 }
798
799 /**
800 * Should be called by the parser, for example to apply the output format specified in the #ftl header.
801 */
802 void setOutputFormat(OutputFormat outputFormat) {
803 checkWritable();
804 this.outputFormat = outputFormat;
805 }
806
807 /**
808 * Returns the {@link Configuration#getAutoEscapingPolicy()} autoEscapingPolicy) that this template uses.
809 * This is decided from these, in increasing priority:
810 * {@link Configuration#getAutoEscapingPolicy()}, {@link ParsingConfiguration#getAutoEscapingPolicy()},
811 * {@code #ftl} header's {@code auto_esc} option in the template.
812 */
813 public AutoEscapingPolicy getAutoEscapingPolicy() {
814 return autoEscapingPolicy != null ? autoEscapingPolicy
815 : tCfg != null && tCfg.isAutoEscapingPolicySet() ? tCfg.getAutoEscapingPolicy()
816 : cfg.getAutoEscapingPolicy();
817 }
818
819 /**
820 * Should be called by the parser, for example to apply the auto escaping policy specified in the #ftl header.
821 */
822 void setAutoEscapingPolicy(AutoEscapingPolicy autoEscapingPolicy) {
823 checkWritable();
824 this.autoEscapingPolicy = autoEscapingPolicy;
825 }
826
827 /**
828 * Dump the raw template in canonical form.
829 */
830 public void dump(PrintStream ps) {
831 ps.print(rootElement.getCanonicalForm());
832 }
833
834 /**
835 * Dump the raw template in canonical form.
836 */
837 public void dump(Writer out) throws IOException {
838 out.write(rootElement != null ? rootElement.getCanonicalForm() : "Unfinished template");
839 }
840
841 void addMacro(ASTDirMacroOrFunction macro) {
842 macros.put(macro.getName(), macro);
843 }
844
845 void addImport(ASTDirImport ll) {
846 imports.add(ll);
847 }
848
849 /**
850 * Returns the template source at the location specified by the coordinates given, or {@code null} if unavailable.
851 * A strange legacy in the behavior of this method is that it replaces tab characters with spaces according the
852 * value of {@link Template#getParsingConfiguration()}/{@link ParsingConfiguration#getTabSize()} (which usually
853 * comes from {@link Configuration#getTabSize()}), because tab characters move the column number with more than
854 * 1 in error messages. However, if you set the tab size to 1, this method leaves the tab characters as is.
855 *
856 * @param beginColumn the first column of the requested source, 1-based
857 * @param beginLine the first line of the requested source, 1-based
858 * @param endColumn the last column of the requested source, 1-based
859 * @param endLine the last line of the requested source, 1-based
860 *
861 * @see org.apache.freemarker.core.ASTNode#getSource()
862 */
863 public String getSource(int beginColumn,
864 int beginLine,
865 int endColumn,
866 int endLine) {
867 if (beginLine < 1 || endLine < 1) return null; // dynamically ?eval-ed expressions has no source available
868
869 // Our container is zero-based.
870 --beginLine;
871 --beginColumn;
872 --endColumn;
873 --endLine;
874 StringBuilder buf = new StringBuilder();
875 for (int i = beginLine ; i <= endLine; i++) {
876 if (i < lines.size()) {
877 buf.append(lines.get(i));
878 }
879 }
880 int lastLineLength = lines.get(endLine).toString().length();
881 int trailingCharsToDelete = lastLineLength - endColumn - 1;
882 buf.delete(0, beginColumn);
883 buf.delete(buf.length() - trailingCharsToDelete, buf.length());
884 return buf.toString();
885 }
886
887 @Override
888 public Locale getLocale() {
889 // TODO [FM3] Temporary hack; See comment above the locale field
890 if (lookupLocale != null) {
891 return lookupLocale;
892 }
893
894 return tCfg != null && tCfg.isLocaleSet() ? tCfg.getLocale() : cfg.getLocale();
895 }
896
897 // TODO [FM3] Temporary hack; See comment above the lookupLocale field
898 public void setLookupLocale(Locale lookupLocale) {
899 this.lookupLocale = lookupLocale;
900 }
901
902 @Override
903 public boolean isLocaleSet() {
904 return tCfg != null && tCfg.isLocaleSet();
905 }
906
907 @Override
908 public TimeZone getTimeZone() {
909 return tCfg != null && tCfg.isTimeZoneSet() ? tCfg.getTimeZone() : cfg.getTimeZone();
910 }
911
912 @Override
913 public boolean isTimeZoneSet() {
914 return tCfg != null && tCfg.isTimeZoneSet();
915 }
916
917 @Override
918 public TimeZone getSQLDateAndTimeTimeZone() {
919 return tCfg != null && tCfg.isSQLDateAndTimeTimeZoneSet() ? tCfg.getSQLDateAndTimeTimeZone() : cfg.getSQLDateAndTimeTimeZone();
920 }
921
922 @Override
923 public boolean isSQLDateAndTimeTimeZoneSet() {
924 return tCfg != null && tCfg.isSQLDateAndTimeTimeZoneSet();
925 }
926
927 @Override
928 public String getNumberFormat() {
929 return tCfg != null && tCfg.isNumberFormatSet() ? tCfg.getNumberFormat() : cfg.getNumberFormat();
930 }
931
932 @Override
933 public boolean isNumberFormatSet() {
934 return tCfg != null && tCfg.isNumberFormatSet();
935 }
936
937 @Override
938 public Map<String, TemplateNumberFormatFactory> getCustomNumberFormats() {
939 return tCfg != null && tCfg.isCustomNumberFormatsSet() ? tCfg.getCustomNumberFormats()
940 : cfg.getCustomNumberFormats();
941 }
942
943 @Override
944 public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
945 if (tCfg != null && tCfg.isCustomNumberFormatsSet()) {
946 TemplateNumberFormatFactory value = tCfg.getCustomNumberFormats().get(name);
947 if (value != null) {
948 return value;
949 }
950 }
951 return cfg.getCustomNumberFormat(name);
952 }
953
954 @Override
955 public boolean isCustomNumberFormatsSet() {
956 return tCfg != null && tCfg.isCustomNumberFormatsSet();
957 }
958
959 @Override
960 public String getBooleanFormat() {
961 return tCfg != null && tCfg.isBooleanFormatSet() ? tCfg.getBooleanFormat() : cfg.getBooleanFormat();
962 }
963
964 @Override
965 public boolean isBooleanFormatSet() {
966 return tCfg != null && tCfg.isBooleanFormatSet();
967 }
968
969 @Override
970 public String getTimeFormat() {
971 return tCfg != null && tCfg.isTimeFormatSet() ? tCfg.getTimeFormat() : cfg.getTimeFormat();
972 }
973
974 @Override
975 public boolean isTimeFormatSet() {
976 return tCfg != null && tCfg.isTimeFormatSet();
977 }
978
979 @Override
980 public String getDateFormat() {
981 return tCfg != null && tCfg.isDateFormatSet() ? tCfg.getDateFormat() : cfg.getDateFormat();
982 }
983
984 @Override
985 public boolean isDateFormatSet() {
986 return tCfg != null && tCfg.isDateFormatSet();
987 }
988
989 @Override
990 public String getDateTimeFormat() {
991 return tCfg != null && tCfg.isDateTimeFormatSet() ? tCfg.getDateTimeFormat() : cfg.getDateTimeFormat();
992 }
993
994 @Override
995 public boolean isDateTimeFormatSet() {
996 return tCfg != null && tCfg.isDateTimeFormatSet();
997 }
998
999 @Override
1000 public Map<String, TemplateDateFormatFactory> getCustomDateFormats() {
1001 return tCfg != null && tCfg.isCustomDateFormatsSet() ? tCfg.getCustomDateFormats() : cfg.getCustomDateFormats();
1002 }
1003
1004 @Override
1005 public TemplateDateFormatFactory getCustomDateFormat(String name) {
1006 if (tCfg != null && tCfg.isCustomDateFormatsSet()) {
1007 TemplateDateFormatFactory value = tCfg.getCustomDateFormats().get(name);
1008 if (value != null) {
1009 return value;
1010 }
1011 }
1012 return cfg.getCustomDateFormat(name);
1013 }
1014
1015 @Override
1016 public boolean isCustomDateFormatsSet() {
1017 return tCfg != null && tCfg.isCustomDateFormatsSet();
1018 }
1019
1020 @Override
1021 public TemplateExceptionHandler getTemplateExceptionHandler() {
1022 return tCfg != null && tCfg.isTemplateExceptionHandlerSet() ? tCfg.getTemplateExceptionHandler()
1023 : cfg.getTemplateExceptionHandler();
1024 }
1025
1026 @Override
1027 public boolean isTemplateExceptionHandlerSet() {
1028 return tCfg != null && tCfg.isTemplateExceptionHandlerSet();
1029 }
1030
1031 @Override
1032 public AttemptExceptionReporter getAttemptExceptionReporter() {
1033 return tCfg != null && tCfg.isAttemptExceptionReporterSet() ? tCfg.getAttemptExceptionReporter()
1034 : cfg.getAttemptExceptionReporter();
1035 }
1036
1037 @Override
1038 public boolean isAttemptExceptionReporterSet() {
1039 return tCfg != null && tCfg.isAttemptExceptionReporterSet();
1040 }
1041
1042 @Override
1043 public ArithmeticEngine getArithmeticEngine() {
1044 return tCfg != null && tCfg.isArithmeticEngineSet() ? tCfg.getArithmeticEngine() : cfg.getArithmeticEngine();
1045 }
1046
1047 @Override
1048 public boolean isArithmeticEngineSet() {
1049 return tCfg != null && tCfg.isArithmeticEngineSet();
1050 }
1051
1052 @Override
1053 public Charset getOutputEncoding() {
1054 return tCfg != null && tCfg.isOutputEncodingSet() ? tCfg.getOutputEncoding() : cfg.getOutputEncoding();
1055 }
1056
1057 @Override
1058 public boolean isOutputEncodingSet() {
1059 return tCfg != null && tCfg.isOutputEncodingSet();
1060 }
1061
1062 @Override
1063 public Charset getURLEscapingCharset() {
1064 return tCfg != null && tCfg.isURLEscapingCharsetSet() ? tCfg.getURLEscapingCharset() : cfg.getURLEscapingCharset();
1065 }
1066
1067 @Override
1068 public boolean isURLEscapingCharsetSet() {
1069 return tCfg != null && tCfg.isURLEscapingCharsetSet();
1070 }
1071
1072 @Override
1073 public TemplateClassResolver getNewBuiltinClassResolver() {
1074 return tCfg != null && tCfg.isNewBuiltinClassResolverSet() ? tCfg.getNewBuiltinClassResolver() : cfg.getNewBuiltinClassResolver();
1075 }
1076
1077 @Override
1078 public boolean isNewBuiltinClassResolverSet() {
1079 return tCfg != null && tCfg.isNewBuiltinClassResolverSet();
1080 }
1081
1082 @Override
1083 public boolean getAPIBuiltinEnabled() {
1084 return tCfg != null && tCfg.isAPIBuiltinEnabledSet() ? tCfg.getAPIBuiltinEnabled() : cfg.getAPIBuiltinEnabled();
1085 }
1086
1087 @Override
1088 public boolean isAPIBuiltinEnabledSet() {
1089 return tCfg != null && tCfg.isAPIBuiltinEnabledSet();
1090 }
1091
1092 @Override
1093 public boolean getAutoFlush() {
1094 return tCfg != null && tCfg.isAutoFlushSet() ? tCfg.getAutoFlush() : cfg.getAutoFlush();
1095 }
1096
1097 @Override
1098 public boolean isAutoFlushSet() {
1099 return tCfg != null && tCfg.isAutoFlushSet();
1100 }
1101
1102 @Override
1103 public boolean getShowErrorTips() {
1104 return tCfg != null && tCfg.isShowErrorTipsSet() ? tCfg.getShowErrorTips() : cfg.getShowErrorTips();
1105 }
1106
1107 @Override
1108 public boolean isShowErrorTipsSet() {
1109 return tCfg != null && tCfg.isShowErrorTipsSet();
1110 }
1111
1112 @Override
1113 public boolean getLazyImports() {
1114 return tCfg != null && tCfg.isLazyImportsSet() ? tCfg.getLazyImports() : cfg.getLazyImports();
1115 }
1116
1117 @Override
1118 public boolean isLazyImportsSet() {
1119 return tCfg != null && tCfg.isLazyImportsSet();
1120 }
1121
1122 @Override
1123 public Boolean getLazyAutoImports() {
1124 return tCfg != null && tCfg.isLazyAutoImportsSet() ? tCfg.getLazyAutoImports() : cfg.getLazyAutoImports();
1125 }
1126
1127 @Override
1128 public boolean isLazyAutoImportsSet() {
1129 return tCfg != null && tCfg.isLazyAutoImportsSet();
1130 }
1131
1132 @Override
1133 public Map<String, String> getAutoImports() {
1134 return tCfg != null && tCfg.isAutoImportsSet() ? tCfg.getAutoImports() : cfg.getAutoImports();
1135 }
1136
1137 @Override
1138 public boolean isAutoImportsSet() {
1139 return tCfg != null && tCfg.isAutoImportsSet();
1140 }
1141
1142 @Override
1143 public List<String> getAutoIncludes() {
1144 return tCfg != null && tCfg.isAutoIncludesSet() ? tCfg.getAutoIncludes() : cfg.getAutoIncludes();
1145 }
1146
1147 @Override
1148 public boolean isAutoIncludesSet() {
1149 return tCfg != null && tCfg.isAutoIncludesSet();
1150 }
1151
1152 @SuppressWarnings({ "unchecked", "rawtypes" })
1153 @Override
1154 public Map<Serializable, Object> getCustomSettings(boolean includeInherited) {
1155 boolean nonInheritedAttrsFinal;
1156 Map<? extends Serializable, ? extends Object> nonInheritedAttrs;
1157 if (tcAndHeaderCustomSettings != null) {
1158 nonInheritedAttrs = tcAndHeaderCustomSettings;
1159 nonInheritedAttrsFinal = writeProtected;
1160 } else if (headerCustomSettings != null) {
1161 nonInheritedAttrs = headerCustomSettings;
1162 nonInheritedAttrsFinal = writeProtected;
1163 } else if (tCfg != null) {
1164 nonInheritedAttrs = tCfg.getCustomSettings(false);
1165 nonInheritedAttrsFinal = true;
1166 } else {
1167 nonInheritedAttrs = Collections.emptyMap();
1168 nonInheritedAttrsFinal = true;
1169 }
1170
1171 Map<Serializable, Object> inheritedAttrs = includeInherited ? cfg.getCustomSettings(true)
1172 : Collections.<Serializable, Object>emptyMap();
1173
1174 LinkedHashMap<Serializable, Object> mergedAttrs;
1175 if (nonInheritedAttrs.isEmpty()) {
1176 return inheritedAttrs;
1177 } else if (inheritedAttrs.isEmpty() && nonInheritedAttrsFinal) {
1178 return (Map) nonInheritedAttrs;
1179 } else {
1180 LinkedHashMap<Serializable, Object> result = new LinkedHashMap<>(
1181 (inheritedAttrs.size() + nonInheritedAttrs.size()) * 4 / 3 + 1, 0.75f);
1182 result.putAll(inheritedAttrs);
1183 result.putAll(nonInheritedAttrs);
1184 return Collections.unmodifiableMap(result);
1185 }
1186 }
1187
1188 @Override
1189 public boolean isCustomSettingSet(Serializable key) {
1190 if (tcAndHeaderCustomSettings != null) {
1191 return tcAndHeaderCustomSettings.containsKey(key);
1192 }
1193 return headerCustomSettings != null && headerCustomSettings.containsKey(key)
1194 || tCfg != null && tCfg.isCustomSettingSet(key);
1195 }
1196
1197 @Override
1198 public Object getCustomSetting(Serializable key) {
1199 return getCustomSetting(key, null, false);
1200 }
1201
1202 @Override
1203 public Object getCustomSetting(Serializable key, Object defaultValue) {
1204 return getCustomSetting(key, defaultValue, true);
1205 }
1206
1207 private Object getCustomSetting(Serializable key, Object defaultValue, boolean useDefaultValue) {
1208 if (tcAndHeaderCustomSettings != null) {
1209 Object value = tcAndHeaderCustomSettings.get(key);
1210 if (value != null || tcAndHeaderCustomSettings.containsKey(key)) {
1211 return value;
1212 }
1213 } else {
1214 if (headerCustomSettings != null) {
1215 Object value = headerCustomSettings.get(key);
1216 if (value != null || headerCustomSettings.containsKey(key)) {
1217 return value;
1218 }
1219 }
1220 if (tCfg != null) {
1221 Object value = tCfg.getCustomSetting(key, MISSING_VALUE_MARKER);
1222 if (value != MISSING_VALUE_MARKER) {
1223 return value;
1224 }
1225 }
1226 }
1227 return useDefaultValue ? cfg.getCustomSetting(key, defaultValue) : cfg.getCustomSetting(key);
1228 }
1229
1230 /**
1231 * Should be called by the parser, for example to add the attributes specified in the #ftl header.
1232 */
1233 void setHeaderCustomSetting(String attName, Serializable attValue) {
1234 checkWritable();
1235
1236 if (headerCustomSettings == null) {
1237 headerCustomSettings = new LinkedHashMap<>();
1238 }
1239 headerCustomSettings.put(attName, attValue);
1240
1241 if (tCfg != null) {
1242 Map<Serializable, Object> tcCustAttrs = tCfg.getCustomSettings(false);
1243 if (!tcCustAttrs.isEmpty()) {
1244 if (tcAndHeaderCustomSettings == null) {
1245 tcAndHeaderCustomSettings = new LinkedHashMap<>(tcCustAttrs);
1246 }
1247 tcAndHeaderCustomSettings.put(attName, attValue);
1248 }
1249 }
1250 }
1251
1252 /**
1253 * Reader that builds up the line table info for us, and also helps in working around JavaCC's exception
1254 * suppression.
1255 */
1256 private static class LineTableBuilder extends FilterReader {
1257
1258 private final int tabSize;
1259 private final StringBuilder lineBuf = new StringBuilder();
1260 private final List<String> lines;
1261
1262 int lastChar;
1263 boolean closed;
1264
1265 /** Needed to work around JavaCC behavior where it silently treats any errors as EOF. */
1266 private Exception failure;
1267
1268 /**
1269 * @param r the character stream to wrap
1270 */
1271 LineTableBuilder(Reader r, ParsingConfiguration parserConfiguration, List<String> lines) {
1272 super(r);
1273 tabSize = parserConfiguration.getTabSize();
1274 this.lines = lines;
1275 }
1276
1277 public boolean hasFailure() {
1278 return failure != null;
1279 }
1280
1281 public void throwFailure() throws IOException {
1282 if (failure != null) {
1283 if (failure instanceof IOException) {
1284 throw (IOException) failure;
1285 }
1286 if (failure instanceof RuntimeException) {
1287 throw (RuntimeException) failure;
1288 }
1289 throw new UndeclaredThrowableException(failure);
1290 }
1291 }
1292
1293 @Override
1294 public int read() throws IOException {
1295 try {
1296 int c = in.read();
1297 handleChar(c);
1298 return c;
1299 } catch (Exception e) {
1300 throw rememberException(e);
1301 }
1302 }
1303
1304 private IOException rememberException(Exception e) throws IOException {
1305 // JavaCC used to read from the Reader after it was closed. So we must not treat that as a failure.
1306 if (!closed) {
1307 failure = e;
1308 }
1309 if (e instanceof IOException) {
1310 return (IOException) e;
1311 }
1312 if (e instanceof RuntimeException) {
1313 throw (RuntimeException) e;
1314 }
1315 throw new UndeclaredThrowableException(e);
1316 }
1317
1318 @Override
1319 public int read(char cbuf[], int off, int len) throws IOException {
1320 try {
1321 int numchars = in.read(cbuf, off, len);
1322 for (int i = off; i < off + numchars; i++) {
1323 char c = cbuf[i];
1324 handleChar(c);
1325 }
1326 return numchars;
1327 } catch (Exception e) {
1328 throw rememberException(e);
1329 }
1330 }
1331
1332 @Override
1333 public void close() throws IOException {
1334 if (lineBuf.length() > 0) {
1335 lines.add(lineBuf.toString());
1336 lineBuf.setLength(0);
1337 }
1338 super.close();
1339 closed = true;
1340 }
1341
1342 private void handleChar(int c) {
1343 if (c == '\n' || c == '\r') {
1344 if (lastChar == '\r' && c == '\n') { // CRLF under Windoze
1345 int lastIndex = lines.size() - 1;
1346 String lastLine = lines.get(lastIndex);
1347 lines.set(lastIndex, lastLine + '\n');
1348 } else {
1349 lineBuf.append((char) c);
1350 lines.add(lineBuf.toString());
1351 lineBuf.setLength(0);
1352 }
1353 } else if (c == '\t' && tabSize != 1) {
1354 int numSpaces = tabSize - (lineBuf.length() % tabSize);
1355 for (int i = 0; i < numSpaces; i++) {
1356 lineBuf.append(' ');
1357 }
1358 } else {
1359 lineBuf.append((char) c);
1360 }
1361 lastChar = c;
1362 }
1363 }
1364
1365 ASTElement getRootASTNode() {
1366 return rootElement;
1367 }
1368
1369 Map getMacros() {
1370 return macros;
1371 }
1372
1373 List getImports() {
1374 return imports;
1375 }
1376
1377 void addPrefixNSMapping(String prefix, String nsURI) {
1378 if (nsURI.length() == 0) {
1379 throw new IllegalArgumentException("Cannot map empty string URI");
1380 }
1381 if (prefix.length() == 0) {
1382 throw new IllegalArgumentException("Cannot map empty string prefix");
1383 }
1384 if (prefix.equals(NO_NS_PREFIX)) {
1385 throw new IllegalArgumentException("The prefix: " + prefix + " cannot be registered, it's reserved for special internal use.");
1386 }
1387 if (prefixToNamespaceURILookup.containsKey(prefix)) {
1388 throw new IllegalArgumentException("The prefix: '" + prefix + "' was repeated. This is illegal.");
1389 }
1390 if (namespaceURIToPrefixLookup.containsKey(nsURI)) {
1391 throw new IllegalArgumentException("The namespace URI: " + nsURI + " cannot be mapped to 2 different prefixes.");
1392 }
1393 if (prefix.equals(DEFAULT_NAMESPACE_PREFIX)) {
1394 defaultNS = nsURI;
1395 } else {
1396 prefixToNamespaceURILookup.put(prefix, nsURI);
1397 namespaceURIToPrefixLookup.put(nsURI, prefix);
1398 }
1399 }
1400
1401 public String getDefaultNS() {
1402 return defaultNS;
1403 }
1404
1405 /**
1406 * @return the NamespaceUri mapped to this prefix in this template. (Or null if there is none.)
1407 */
1408 public String getNamespaceForPrefix(String prefix) {
1409 if (prefix.equals("")) {
1410 return defaultNS == null ? "" : defaultNS;
1411 }
1412 return (String) prefixToNamespaceURILookup.get(prefix);
1413 }
1414
1415 /**
1416 * @return the prefix mapped to this nsURI in this template. (Or null if there is none.)
1417 */
1418 public String getPrefixForNamespace(String nsURI) {
1419 if (nsURI == null) {
1420 return null;
1421 }
1422 if (nsURI.length() == 0) {
1423 return defaultNS == null ? "" : NO_NS_PREFIX;
1424 }
1425 if (nsURI.equals(defaultNS)) {
1426 return "";
1427 }
1428 return (String) namespaceURIToPrefixLookup.get(nsURI);
1429 }
1430
1431 /**
1432 * @return the prefixed name, based on the ns_prefixes defined
1433 * in this template's header for the local name and node namespace
1434 * passed in as parameters.
1435 */
1436 public String getPrefixedName(String localName, String nsURI) {
1437 if (nsURI == null || nsURI.length() == 0) {
1438 if (defaultNS != null) {
1439 return NO_NS_PREFIX + ":" + localName;
1440 } else {
1441 return localName;
1442 }
1443 }
1444 if (nsURI.equals(defaultNS)) {
1445 return localName;
1446 }
1447 String prefix = getPrefixForNamespace(nsURI);
1448 if (prefix == null) {
1449 return null;
1450 }
1451 return prefix + ":" + localName;
1452 }
1453
1454 @Override
1455 @SuppressWarnings("unchecked")
1456 @SuppressFBWarnings("AT_OPERATION_SEQUENCE_ON_CONCURRENT_ABSTRACTION")
1457 public <T> T getCustomState(CustomStateKey<T> customStateKey) {
1458 T customState = (T) customStateMap.get(customStateKey);
1459 if (customState == null) {
1460 synchronized (customStateMapLock) {
1461 customState = (T) customStateMap.get(customStateKey);
1462 if (customState == null) {
1463 customState = customStateKey.create();
1464 if (customState == null) {
1465 throw new IllegalStateException("CustomStateKey.create() must not return null (for key: "
1466 + customStateKey + ")");
1467 }
1468 customStateMap.put(customStateKey, customState);
1469 }
1470 }
1471 }
1472 return customState;
1473 }
1474
1475 }
1476