Continued work on TemplateLanguage configuration and new file extensions: Renamed...
[freemarker.git] / freemarker-core / src / main / java / org / apache / freemarker / core / Configuration.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 static org.apache.freemarker.core.Configuration.ExtendableBuilder.*;
23
24 import java.io.IOException;
25 import java.io.Serializable;
26 import java.nio.charset.Charset;
27 import java.nio.charset.StandardCharsets;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Iterator;
35 import java.util.LinkedHashMap;
36 import java.util.List;
37 import java.util.Locale;
38 import java.util.Map;
39 import java.util.Map.Entry;
40 import java.util.Properties;
41 import java.util.Set;
42 import java.util.TimeZone;
43 import java.util.TreeSet;
44 import java.util.concurrent.ConcurrentHashMap;
45
46 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
47 import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
48 import org.apache.freemarker.core.model.ObjectWrapper;
49 import org.apache.freemarker.core.model.ObjectWrappingException;
50 import org.apache.freemarker.core.model.TemplateModel;
51 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
52 import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
53 import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
54 import org.apache.freemarker.core.outputformat.OutputFormat;
55 import org.apache.freemarker.core.outputformat.UnregisteredOutputFormatException;
56 import org.apache.freemarker.core.outputformat.impl.CSSOutputFormat;
57 import org.apache.freemarker.core.outputformat.impl.CombinedMarkupOutputFormat;
58 import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
59 import org.apache.freemarker.core.outputformat.impl.JSONOutputFormat;
60 import org.apache.freemarker.core.outputformat.impl.JavaScriptOutputFormat;
61 import org.apache.freemarker.core.outputformat.impl.PlainTextOutputFormat;
62 import org.apache.freemarker.core.outputformat.impl.RTFOutputFormat;
63 import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
64 import org.apache.freemarker.core.outputformat.impl.XHTMLOutputFormat;
65 import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
66 import org.apache.freemarker.core.templateresolver.CacheStorage;
67 import org.apache.freemarker.core.templateresolver.GetTemplateResult;
68 import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
69 import org.apache.freemarker.core.templateresolver.MergingTemplateConfigurationFactory;
70 import org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory;
71 import org.apache.freemarker.core.templateresolver.TemplateLoader;
72 import org.apache.freemarker.core.templateresolver.TemplateLookupContext;
73 import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
74 import org.apache.freemarker.core.templateresolver.TemplateNameFormat;
75 import org.apache.freemarker.core.templateresolver.TemplateResolver;
76 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateLookupStrategy;
77 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
78 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
79 import org.apache.freemarker.core.templateresolver.impl.MruCacheStorage;
80 import org.apache.freemarker.core.templateresolver.impl.SoftCacheStorage;
81 import org.apache.freemarker.core.util._ClassUtils;
82 import org.apache.freemarker.core.util._CollectionUtils;
83 import org.apache.freemarker.core.util._NullArgumentException;
84 import org.apache.freemarker.core.util._SortedArraySet;
85 import org.apache.freemarker.core.util._StringUtils;
86 import org.apache.freemarker.core.util._UnmodifiableCompositeSet;
87 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
88 import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
89
90 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
91
92 /**
93 * <b>The main entry point into the FreeMarker API</b>; encapsulates the configuration settings of FreeMarker,
94 * also serves as a central template-loading and caching service.
95 *
96 * <p>This class is meant to be used in a singleton pattern. That is, you create an instance of this at the beginning of
97 * the application life-cycle with {@link Configuration.Builder}, set its settings
98 * (either with the setter methods like {@link Configuration.Builder#setTemplateLoader(TemplateLoader)} or by loading a
99 * {@code .properties} file and use that with {@link Configuration.Builder#setSettings(Properties)}}), and then
100 * use that single instance everywhere in your application. Frequently re-creating {@link Configuration} is a typical
101 * and grave mistake from performance standpoint, as the {@link Configuration} holds the template cache, and often also
102 * the class introspection cache, which then will be lost. (Note that, naturally, having multiple long-lived instances,
103 * like one per component that internally uses FreeMarker is fine.)
104 *
105 * <p>The basic usage pattern is like:
106 *
107 * <pre>
108 * // Where the application is initialized; in general you do this ONLY ONCE in the application life-cycle!
109 * Configuration cfg = new Configuration.Builder(VERSION_<i>X</i>_<i>Y</i>_<i>Z</i>));
110 * .<i>someSetting</i>(...)
111 * .<i>otherSetting</i>(...)
112 * .build()
113 * // VERSION_<i>X</i>_<i>Y</i>_<i>Z</i> enables the not-100%-backward-compatible fixes introduced in
114 * // FreeMarker version X.Y.Z and earlier (see {@link Configuration#getIncompatibleImprovements()}).
115 * ...
116 *
117 * // Later, whenever the application needs a template (so you may do this a lot, and from multiple threads):
118 * {@link Template Template} myTemplate = cfg.{@link #getTemplate(String) getTemplate}("myTemplate.f3ah");
119 * myTemplate.{@link Template#process(Object, java.io.Writer) process}(dataModel, out);</pre>
120 *
121 * <p>Note that you certainly want to set the {@link #getTemplateLoader templateLoader} setting, as its default
122 * value is {@code null}, so you won't be able to load any templates (as FreeMarker doesn't know where from should it
123 * load them).
124 *
125 * <p>{@link Configuration} is thread-safe and (as of 3.0.0) immutable (apart from internal caches).
126 *
127 * <p>The setting reader methods of this class don't throw {@link CoreSettingValueNotSetException}, because all settings
128 * are set on the {@link Configuration} level (even if they were just initialized to a default value).
129 */
130 public final class Configuration implements TopLevelConfiguration, CustomStateScope {
131
132 private static final String VERSION_PROPERTIES_PATH = "/org/apache/freemarker/core/version.properties";
133
134 static final Map<String, OutputFormat> STANDARD_OUTPUT_FORMATS;
135 static {
136 STANDARD_OUTPUT_FORMATS = new HashMap<>();
137 STANDARD_OUTPUT_FORMATS.put(UndefinedOutputFormat.INSTANCE.getName(), UndefinedOutputFormat.INSTANCE);
138 STANDARD_OUTPUT_FORMATS.put(HTMLOutputFormat.INSTANCE.getName(), HTMLOutputFormat.INSTANCE);
139 STANDARD_OUTPUT_FORMATS.put(XHTMLOutputFormat.INSTANCE.getName(), XHTMLOutputFormat.INSTANCE);
140 STANDARD_OUTPUT_FORMATS.put(XMLOutputFormat.INSTANCE.getName(), XMLOutputFormat.INSTANCE);
141 STANDARD_OUTPUT_FORMATS.put(RTFOutputFormat.INSTANCE.getName(), RTFOutputFormat.INSTANCE);
142 STANDARD_OUTPUT_FORMATS.put(PlainTextOutputFormat.INSTANCE.getName(), PlainTextOutputFormat.INSTANCE);
143 STANDARD_OUTPUT_FORMATS.put(CSSOutputFormat.INSTANCE.getName(), CSSOutputFormat.INSTANCE);
144 STANDARD_OUTPUT_FORMATS.put(JavaScriptOutputFormat.INSTANCE.getName(), JavaScriptOutputFormat.INSTANCE);
145 STANDARD_OUTPUT_FORMATS.put(JSONOutputFormat.INSTANCE.getName(), JSONOutputFormat.INSTANCE);
146 }
147
148 /** FreeMarker version 3.0.0 */
149 public static final Version VERSION_3_0_0 = new Version(3, 0, 0);
150
151 /** The default of {@link #getIncompatibleImprovements()}, currently {@link #VERSION_3_0_0}. */
152 public static final Version DEFAULT_INCOMPATIBLE_IMPROVEMENTS = Configuration.VERSION_3_0_0;
153
154 private static final Version VERSION;
155 static {
156 try {
157 Properties props = _ClassUtils.loadProperties(Configuration.class, VERSION_PROPERTIES_PATH);
158 String versionString = getRequiredVersionProperty(props, "version");
159 final Boolean gaeCompliant = Boolean.valueOf(getRequiredVersionProperty(props, "isGAECompliant"));
160 VERSION = new Version(versionString, gaeCompliant, null);
161 } catch (IOException e) {
162 throw new RuntimeException("Failed to load and parse " + VERSION_PROPERTIES_PATH, e);
163 }
164 }
165
166 // Configuration-specific settings:
167
168 private final Version incompatibleImprovements;
169 private final TemplateResolver templateResolver;
170 private final TemplateLoader templateLoader;
171 private final CacheStorage templateCacheStorage;
172 private final TemplateLookupStrategy templateLookupStrategy;
173 private final TemplateNameFormat templateNameFormat;
174 private final TemplateConfigurationFactory templateConfigurations;
175 private final Long templateUpdateDelayMilliseconds;
176 private final Boolean localizedTemplateLookup;
177 private final List<OutputFormat> registeredCustomOutputFormats;
178 private final Map<String, OutputFormat> registeredCustomOutputFormatsByName;
179 private final Map<String, Object> sharedVariables;
180 private final Map<String, TemplateModel> wrappedSharedVariables;
181
182 // ParsingConfiguration settings:
183
184 private final TemplateLanguage templateLanguage;
185 private final boolean whitespaceStripping;
186 private final AutoEscapingPolicy autoEscapingPolicy;
187 private final OutputFormat outputFormat;
188 private final Boolean recognizeStandardFileExtensions;
189 private final int tabSize;
190 private final Charset sourceEncoding;
191
192 // ProcessingConfiguration settings:
193
194 private final Locale locale;
195 private final String numberFormat;
196 private final String timeFormat;
197 private final String dateFormat;
198 private final String dateTimeFormat;
199 private final TimeZone timeZone;
200 private final TimeZone sqlDateAndTimeTimeZone;
201 private final String booleanFormat;
202 private final TemplateExceptionHandler templateExceptionHandler;
203 private final AttemptExceptionReporter attemptExceptionReporter;
204 private final ArithmeticEngine arithmeticEngine;
205 private final ObjectWrapper objectWrapper;
206 private final Charset outputEncoding;
207 private final Charset urlEscapingCharset;
208 private final Boolean autoFlush;
209 private final TemplateClassResolver newBuiltinClassResolver;
210 private final Boolean showErrorTips;
211 private final Boolean apiBuiltinEnabled;
212 private final Map<String, TemplateDateFormatFactory> customDateFormats;
213 private final Map<String, TemplateNumberFormatFactory> customNumberFormats;
214 private final Map<String, String> autoImports;
215 private final List<String> autoIncludes;
216 private final Boolean lazyImports;
217 private final Boolean lazyAutoImports;
218 private final Map<Serializable, Object> customSettings;
219
220 // CustomStateScope:
221
222 private final ConcurrentHashMap<CustomStateKey<?>, Object> customStateMap = new ConcurrentHashMap<>(0);
223 private final Object customStateMapLock = new Object();
224
225 private <SelfT extends ExtendableBuilder<SelfT>> Configuration(ExtendableBuilder<SelfT> builder)
226 throws ConfigurationException {
227 // Configuration-specific settings (except templateResolver):
228 incompatibleImprovements = builder.getIncompatibleImprovements();
229
230 {
231 final Collection<OutputFormat> regCustOutputFormats;
232 {
233 Collection<OutputFormat> directRegCustOutputFormats = builder.getRegisteredCustomOutputFormats();
234 Collection<OutputFormat> impliedRegCustOutputFormats =
235 builder.getImpliedRegisteredCustomOutputFormats();
236 if (impliedRegCustOutputFormats.isEmpty()) {
237 regCustOutputFormats = directRegCustOutputFormats;
238 } else if (directRegCustOutputFormats.isEmpty()) {
239 regCustOutputFormats = impliedRegCustOutputFormats;
240 } else {
241 List<OutputFormat> mergedOutputFormats = new ArrayList<>(
242 impliedRegCustOutputFormats.size() + directRegCustOutputFormats.size());
243 HashSet<String> directNames = new HashSet<>(directRegCustOutputFormats.size() * 4 / 3 + 1, .75f);
244 for (OutputFormat directRegCustOutputFormat : directRegCustOutputFormats) {
245 directNames.add(directRegCustOutputFormat.getName());
246 }
247 for (OutputFormat impliedRegCustOutputFormat : impliedRegCustOutputFormats) {
248 if (!directNames.contains(impliedRegCustOutputFormat.getName())) {
249 mergedOutputFormats.add(impliedRegCustOutputFormat);
250 }
251 }
252 mergedOutputFormats.addAll(directRegCustOutputFormats);
253 regCustOutputFormats = Collections.unmodifiableList(mergedOutputFormats);
254 }
255 }
256
257 _NullArgumentException.check(regCustOutputFormats);
258 Map<String, OutputFormat> registeredCustomOutputFormatsByName = new LinkedHashMap<>(
259 regCustOutputFormats.size() * 4 / 3, 1f);
260 for (OutputFormat outputFormat : regCustOutputFormats) {
261 String name = outputFormat.getName();
262 if (name.equals(UndefinedOutputFormat.INSTANCE.getName())) {
263 throw new InvalidSettingValueException(
264 ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
265 "The \"" + name + "\" output format can't be redefined",
266 null);
267 }
268 if (name.equals(PlainTextOutputFormat.INSTANCE.getName())) {
269 throw new InvalidSettingValueException(
270 ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
271 "The \"" + name + "\" output format can't be redefined",
272 null);
273 }
274 if (name.length() == 0) {
275 throw new InvalidSettingValueException(
276 ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
277 "The output format name can't be 0 long",
278 null);
279 }
280 if (!Character.isLetterOrDigit(name.charAt(0))) {
281 throw new InvalidSettingValueException(
282 ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
283 "The output format name must start with letter or digit: " + name,
284 null);
285 }
286 if (name.indexOf('+') != -1) {
287 throw new InvalidSettingValueException(
288 ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
289 "The output format name can't contain \"+\" character: " + name,
290 null);
291 }
292 if (name.indexOf('{') != -1) {
293 throw new InvalidSettingValueException(
294 ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
295 "The output format name can't contain \"{\" character: " + name,
296 null);
297 }
298 if (name.indexOf('}') != -1) {
299 throw new InvalidSettingValueException(
300 ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
301 "The output format name can't contain \"}\" character: " + name,
302 null);
303 }
304
305 OutputFormat replaced = registeredCustomOutputFormatsByName.put(outputFormat.getName(), outputFormat);
306 if (replaced != null) {
307 if (replaced == outputFormat) {
308 throw new IllegalArgumentException(
309 "Duplicate output format in the collection: " + outputFormat);
310 }
311 throw new InvalidSettingValueException(
312 ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
313 "Clashing output format names between " + replaced + " and " + outputFormat + ".",
314 null);
315 }
316 }
317
318 this.registeredCustomOutputFormatsByName = registeredCustomOutputFormatsByName;
319 this.registeredCustomOutputFormats = Collections.unmodifiableList(
320 new ArrayList<> (regCustOutputFormats));
321 }
322
323 this.objectWrapper = builder.getObjectWrapper();
324
325 {
326 Map<String, Object> sharedVariables = _CollectionUtils.mergeImmutableMaps(builder
327 .getImpliedSharedVariables(), builder.getSharedVariables(), false);
328
329 HashMap<String, TemplateModel> wrappedSharedVariables = new HashMap<>(
330 sharedVariables.size() * 4 / 3 + 1, 0.75f);
331 for (Entry<String, Object> ent : sharedVariables.entrySet()) {
332 try {
333 wrappedSharedVariables.put(ent.getKey(), objectWrapper.wrap(ent.getValue()));
334 } catch (ObjectWrappingException e) {
335 throw new InvalidSettingValueException(
336 ExtendableBuilder.SHARED_VARIABLES_KEY, null, false,
337 "Failed to wrap shared variable " + _StringUtils.jQuote(ent.getKey()),
338 e);
339 }
340 }
341
342 this.wrappedSharedVariables = wrappedSharedVariables;
343 this.sharedVariables = Collections.unmodifiableMap(sharedVariables);
344 }
345
346 // ParsingConfiguration settings:
347
348 templateLanguage = builder.getTemplateLanguage();
349 whitespaceStripping = builder.getWhitespaceStripping();
350 autoEscapingPolicy = builder.getAutoEscapingPolicy();
351 outputFormat = builder.getOutputFormat();
352 recognizeStandardFileExtensions = builder.getRecognizeStandardFileExtensions();
353 tabSize = builder.getTabSize();
354 sourceEncoding = builder.getSourceEncoding();
355
356 // ProcessingConfiguration settings:
357
358 locale = builder.getLocale();
359 numberFormat = builder.getNumberFormat();
360 timeFormat = builder.getTimeFormat();
361 dateFormat = builder.getDateFormat();
362 dateTimeFormat = builder.getDateTimeFormat();
363 timeZone = builder.getTimeZone();
364 sqlDateAndTimeTimeZone = builder.getSQLDateAndTimeTimeZone();
365 booleanFormat = builder.getBooleanFormat();
366 templateExceptionHandler = builder.getTemplateExceptionHandler();
367 attemptExceptionReporter = builder.getAttemptExceptionReporter();
368 arithmeticEngine = builder.getArithmeticEngine();
369 outputEncoding = builder.getOutputEncoding();
370 urlEscapingCharset = builder.getURLEscapingCharset();
371 autoFlush = builder.getAutoFlush();
372 newBuiltinClassResolver = builder.getNewBuiltinClassResolver();
373 showErrorTips = builder.getShowErrorTips();
374 apiBuiltinEnabled = builder.getAPIBuiltinEnabled();
375 customDateFormats = _CollectionUtils.mergeImmutableMaps(
376 builder.getImpliedCustomDateFormats(), builder.getCustomDateFormats(), false);
377 customNumberFormats = _CollectionUtils.mergeImmutableMaps(
378 builder.getImpliedCustomNumberFormats(), builder.getCustomNumberFormats(), false);
379 autoImports = _CollectionUtils.mergeImmutableMaps(
380 builder.getImpliedAutoImports(), builder.getAutoImports(), true);
381 autoIncludes = _CollectionUtils.mergeImmutableLists(
382 builder.getImpliedAutoIncludes(), builder.getAutoIncludes(), true);
383 lazyImports = builder.getLazyImports();
384 lazyAutoImports = builder.getLazyAutoImports();
385 customSettings = builder.getCustomSettings(false);
386
387 // Configuration-specific settings continued... templateResolver):
388
389 templateResolver = builder.getTemplateResolver();
390
391 templateLoader = builder.getTemplateLoader();
392 if (!templateResolver.supportsTemplateLoaderSetting()) {
393 checkSettingIsNullForThisTemplateResolver(
394 templateResolver, TEMPLATE_LOADER_KEY, templateLoader);
395 }
396
397 templateCacheStorage = builder.getTemplateCacheStorage();
398 if (!templateResolver.supportsTemplateCacheStorageSetting()) {
399 checkSettingIsNullForThisTemplateResolver(
400 templateResolver, TEMPLATE_CACHE_STORAGE_KEY, templateCacheStorage);
401 }
402
403 templateUpdateDelayMilliseconds = builder.getTemplateUpdateDelayMilliseconds();
404 if (!templateResolver.supportsTemplateUpdateDelayMillisecondsSetting()) {
405 checkSettingIsNullForThisTemplateResolver(
406 templateResolver, TEMPLATE_UPDATE_DELAY_KEY, templateUpdateDelayMilliseconds);
407 }
408
409 templateLookupStrategy = builder.getTemplateLookupStrategy();
410 if (!templateResolver.supportsTemplateLookupStrategySetting()) {
411 checkSettingIsNullForThisTemplateResolver(
412 templateResolver, TEMPLATE_LOOKUP_STRATEGY_KEY, templateLookupStrategy);
413 }
414
415 localizedTemplateLookup = builder.getLocalizedTemplateLookup();
416 if (!templateResolver.supportsLocalizedTemplateLookupSetting()) {
417 checkSettingIsNullForThisTemplateResolver(
418 templateResolver, LOCALIZED_TEMPLATE_LOOKUP_KEY, localizedTemplateLookup);
419 }
420
421 templateNameFormat = builder.getTemplateNameFormat();
422 if (!templateResolver.supportsTemplateNameFormatSetting()) {
423 checkSettingIsNullForThisTemplateResolver(
424 templateResolver, TEMPLATE_NAME_FORMAT_KEY, templateNameFormat);
425 }
426
427 TemplateConfigurationFactory templateConfigurations = builder.getTemplateConfigurations();
428 if (!templateResolver.supportsTemplateConfigurationsSetting()) {
429 checkSettingIsNullForThisTemplateResolver(
430 templateResolver, TEMPLATE_CONFIGURATIONS_KEY, templateConfigurations);
431 }
432 TemplateConfigurationFactory impliedTemplateConfigurations = builder.getImpliedTemplateConfigurations();
433 if (impliedTemplateConfigurations != null) {
434 if (templateConfigurations != null) {
435 templateConfigurations = new MergingTemplateConfigurationFactory(
436 impliedTemplateConfigurations, templateConfigurations);
437 } else {
438 templateConfigurations = impliedTemplateConfigurations;
439 }
440 }
441 this.templateConfigurations = templateConfigurations;
442
443 templateResolver.setDependencies(new TemplateResolverDependenciesImpl(this, templateResolver));
444 }
445
446 private void checkSettingIsNullForThisTemplateResolver(
447 TemplateResolver templateResolver,
448 String settingName, Object value) {
449 if (value != null) {
450 throw new InvalidSettingValueException(
451 settingName, null, false,
452 "The templateResolver is a "
453 + templateResolver.getClass().getName() + ", which doesn't support this setting, hence it "
454 + "mustn't be set or must be set to null.",
455 null);
456 }
457 }
458
459 @Override
460 public TemplateExceptionHandler getTemplateExceptionHandler() {
461 return templateExceptionHandler;
462 }
463
464 /**
465 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
466 * value in the {@link Configuration}.
467 */
468 @Override
469 public boolean isTemplateExceptionHandlerSet() {
470 return true;
471 }
472
473 @Override
474 public AttemptExceptionReporter getAttemptExceptionReporter() {
475 return attemptExceptionReporter;
476 }
477
478 /**
479 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
480 * value in the {@link Configuration}.
481 */
482 @Override
483 public boolean isAttemptExceptionReporterSet() {
484 return true;
485 }
486
487 private static class DefaultSoftCacheStorage extends SoftCacheStorage {
488 // Nothing to override
489 }
490
491 @Override
492 public TemplateResolver getTemplateResolver() {
493 return templateResolver;
494 }
495
496
497
498 /**
499 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
500 * value in the {@link Configuration}.
501 */
502 @Override
503 public boolean isTemplateResolverSet() {
504 return true;
505 }
506
507 @Override
508 public TemplateLoader getTemplateLoader() {
509 return templateLoader;
510 }
511
512 /**
513 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
514 * value in the {@link Configuration}.
515 */
516 @Override
517 public boolean isTemplateLoaderSet() {
518 return true;
519 }
520
521 @Override
522 public TemplateLookupStrategy getTemplateLookupStrategy() {
523 return templateLookupStrategy;
524 }
525
526 /**
527 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
528 * value in the {@link Configuration}.
529 */
530 @Override
531 public boolean isTemplateLookupStrategySet() {
532 return true;
533 }
534
535 @Override
536 public TemplateNameFormat getTemplateNameFormat() {
537 return templateNameFormat;
538 }
539
540 /**
541 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
542 * value in the {@link Configuration}.
543 */
544 @Override
545 public boolean isTemplateNameFormatSet() {
546 return true;
547 }
548
549 @Override
550 public TemplateConfigurationFactory getTemplateConfigurations() {
551 return templateConfigurations;
552 }
553
554 /**
555 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
556 * value in the {@link Configuration}.
557 */
558 @Override
559 public boolean isTemplateConfigurationsSet() {
560 return true;
561 }
562
563 @Override
564 public CacheStorage getTemplateCacheStorage() {
565 return templateCacheStorage;
566 }
567
568 /**
569 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
570 * value in the {@link Configuration}.
571 */
572 @Override
573 public boolean isTemplateCacheStorageSet() {
574 return true;
575 }
576
577 @Override
578 public Long getTemplateUpdateDelayMilliseconds() {
579 return templateUpdateDelayMilliseconds;
580 }
581
582 /**
583 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
584 * value in the {@link Configuration}.
585 */
586 @Override
587 public boolean isTemplateUpdateDelayMillisecondsSet() {
588 return true;
589 }
590
591 @Override
592 public Version getIncompatibleImprovements() {
593 return incompatibleImprovements;
594 }
595
596 @Override
597 public boolean isIncompatibleImprovementsSet() {
598 return true;
599 }
600
601 @Override
602 public boolean getWhitespaceStripping() {
603 return whitespaceStripping;
604 }
605
606 /**
607 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
608 * value in the {@link Configuration}.
609 */
610 @Override
611 public boolean isWhitespaceStrippingSet() {
612 return true;
613 }
614
615 @Override
616 public AutoEscapingPolicy getAutoEscapingPolicy() {
617 return autoEscapingPolicy;
618 }
619
620 /**
621 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
622 * value in the {@link Configuration}.
623 */
624 @Override
625 public boolean isAutoEscapingPolicySet() {
626 return true;
627 }
628
629 @Override
630 public OutputFormat getOutputFormat() {
631 return outputFormat;
632 }
633
634 /**
635 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
636 * value in the {@link Configuration}.
637 */
638 @Override
639 public boolean isOutputFormatSet() {
640 return true;
641 }
642
643 /**
644 * Returns the output format for a name.
645 *
646 * @param name
647 * Either the name of the output format as it was registered with the
648 * {@link Configuration#getRegisteredCustomOutputFormats registeredCustomOutputFormats} setting,
649 * or a combined output format name.
650 * A combined output format is created ad-hoc from the registered formats. For example, if you need RTF
651 * embedded into HTML, the name will be <code>HTML{RTF}</code>, where "HTML" and "RTF" refer to the
652 * existing formats. This logic can be used recursively, so for example <code>XML{HTML{RTF}}</code> is
653 * also valid.
654 *
655 * @return Not {@code null}.
656 *
657 * @throws UnregisteredOutputFormatException
658 * If there's no output format registered with the given name.
659 * @throws IllegalArgumentException
660 * If the usage of <code>{</code> and <code>}</code> in the name is syntactically wrong, or if not all
661 * {@link OutputFormat}-s are {@link MarkupOutputFormat}-s in the <code>...{...}</code> expression.
662 */
663 public OutputFormat getOutputFormat(String name) throws UnregisteredOutputFormatException {
664 if (name.length() == 0) {
665 throw new IllegalArgumentException("0-length format name");
666 }
667 if (name.charAt(name.length() - 1) == '}') {
668 // Combined markup
669 int openBrcIdx = name.indexOf('{');
670 if (openBrcIdx == -1) {
671 throw new IllegalArgumentException("Missing opening '{' in: " + name);
672 }
673
674 MarkupOutputFormat outerOF = getMarkupOutputFormatForCombined(name.substring(0, openBrcIdx));
675 MarkupOutputFormat innerOF = getMarkupOutputFormatForCombined(
676 name.substring(openBrcIdx + 1, name.length() - 1));
677
678 return new CombinedMarkupOutputFormat(name, outerOF, innerOF);
679 } else {
680 OutputFormat custOF = registeredCustomOutputFormatsByName.get(name);
681 if (custOF != null) {
682 return custOF;
683 }
684
685 OutputFormat stdOF = STANDARD_OUTPUT_FORMATS.get(name);
686 if (stdOF == null) {
687 StringBuilder sb = new StringBuilder();
688 sb.append("Unregistered output format name, ");
689 sb.append(_StringUtils.jQuote(name));
690 sb.append(". The output formats registered in the Configuration are: ");
691
692 Set<String> registeredNames = new TreeSet<>();
693 registeredNames.addAll(STANDARD_OUTPUT_FORMATS.keySet());
694 registeredNames.addAll(registeredCustomOutputFormatsByName.keySet());
695
696 boolean first = true;
697 for (String registeredName : registeredNames) {
698 if (first) {
699 first = false;
700 } else {
701 sb.append(", ");
702 }
703 sb.append(_StringUtils.jQuote(registeredName));
704 }
705
706 throw new UnregisteredOutputFormatException(sb.toString());
707 }
708 return stdOF;
709 }
710 }
711
712 /**
713 * Returns the argument {@link OutputFormat} as is, unless a {@link #getRegisteredCustomOutputFormats()
714 * customOutputFormats} contains
715 * another {@link OutputFormat} with the same name, in which case it returns that instead.
716 */
717 public OutputFormat getCustomOrArgumentOutputFormat(OutputFormat original) {
718 _NullArgumentException.check("original", original);
719 OutputFormat custOF = registeredCustomOutputFormatsByName.get(original.getName());
720 return custOF != null ? custOF : original;
721 }
722
723 private MarkupOutputFormat getMarkupOutputFormatForCombined(String outerName)
724 throws UnregisteredOutputFormatException {
725 OutputFormat of = getOutputFormat(outerName);
726 if (!(of instanceof MarkupOutputFormat)) {
727 throw new IllegalArgumentException("The \"" + outerName + "\" output format can't be used in "
728 + "...{...} expression, because it's not a markup format.");
729 }
730 return (MarkupOutputFormat) of;
731 }
732
733 @Override
734 public Collection<OutputFormat> getRegisteredCustomOutputFormats() {
735 return registeredCustomOutputFormats;
736 }
737
738 /**
739 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
740 * value in the {@link Configuration}.
741 */
742 @Override
743 public boolean isRegisteredCustomOutputFormatsSet() {
744 return true;
745 }
746
747 @Override
748 public boolean getRecognizeStandardFileExtensions() {
749 return recognizeStandardFileExtensions == null
750 ? true
751 : recognizeStandardFileExtensions.booleanValue();
752 }
753
754 /**
755 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
756 * value in the {@link Configuration}.
757 */
758 @Override
759 public boolean isRecognizeStandardFileExtensionsSet() {
760 return true;
761 }
762
763 static final Map<String, TemplateLanguage> PREDEFINED_TEMPLATE_LANGUAGES_BY_EXTENSION;
764 static {
765 PREDEFINED_TEMPLATE_LANGUAGES_BY_EXTENSION = new HashMap<String, TemplateLanguage>(
766 (DefaultTemplateLanguage.STANDARD_INSTANCES.length + 1) * 2, 0.5f);
767 for (DefaultTemplateLanguage tl : DefaultTemplateLanguage.STANDARD_INSTANCES) {
768 PREDEFINED_TEMPLATE_LANGUAGES_BY_EXTENSION.put(tl.getFileExtension(), tl);
769 }
770 PREDEFINED_TEMPLATE_LANGUAGES_BY_EXTENSION.put(
771 UnparsedTemplateLanguage.INSTANCE.getFileExtension(), UnparsedTemplateLanguage.INSTANCE);
772 }
773
774 /**
775 * Returns the {@link TemplateLanguage} associated to the template name based on its "file" extension (the section
776 * after the last dot). This lookup looks for a matching {@link TemplateLanguage#getFileExtension()}. The file
777 * extension lookup is case insensitive.
778 *
779 * @param templateName
780 * This is preferably the {@link Template#getSourceName()}, though if that's {@code null}, then you
781 * should generally use the {@link Template#getLookupName()}. If {@code null}, the method returns
782 * {@code null}.
783 *
784 * @return The associated {@link TemplateLanguage}, or {@code null} if none matches the file extension, or if the
785 * {@code templateName} parameter was {@code null}.
786 */
787 public TemplateLanguage getTemplateLanguageForTemplateName(String templateName) {
788 if (templateName == null) {
789 return null;
790 }
791
792 int dotIdx = templateName.lastIndexOf('.');
793 if (dotIdx == -1) {
794 return null;
795 }
796
797 return getTemplateLanguageForFileExtension(templateName.substring(dotIdx + 1));
798 }
799
800 /**
801 * Returns the {@link TemplateLanguage} associated to given file extension, or {@code null} if none is associated
802 * with it. The lookup is case insensitive.
803 *
804 * @param fileExtension Case insensitive, not {@code null}.
805 */
806 public TemplateLanguage getTemplateLanguageForFileExtension(String fileExtension) {
807 _NullArgumentException.check(fileExtension, "extension");
808 return PREDEFINED_TEMPLATE_LANGUAGES_BY_EXTENSION.get(fileExtension.toLowerCase());
809 }
810
811 @Override
812 public TemplateLanguage getTemplateLanguage() {
813 return templateLanguage;
814 }
815
816 /**
817 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
818 * value in the {@link Configuration}.
819 */
820 @Override
821 public boolean isTemplateLanguageSet() {
822 return true;
823 }
824
825 @Override
826 public int getTabSize() {
827 return tabSize;
828 }
829
830 /**
831 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
832 * value in the {@link Configuration}.
833 */
834 @Override
835 public boolean isTabSizeSet() {
836 return true;
837 }
838
839 @Override
840 public Locale getLocale() {
841 return locale;
842 }
843
844 /**
845 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
846 * value in the {@link Configuration}.
847 */
848 @Override
849 public boolean isLocaleSet() {
850 return true;
851 }
852
853 @Override
854 public TimeZone getTimeZone() {
855 return timeZone;
856 }
857
858 /**
859 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
860 * value in the {@link Configuration}.
861 */
862 @Override
863 public boolean isTimeZoneSet() {
864 return true;
865 }
866
867 @Override
868 public TimeZone getSQLDateAndTimeTimeZone() {
869 return sqlDateAndTimeTimeZone;
870 }
871
872 /**
873 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
874 * value in the {@link Configuration}.
875 */
876 @Override
877 public boolean isSQLDateAndTimeTimeZoneSet() {
878 return true;
879 }
880
881 @Override
882 public ArithmeticEngine getArithmeticEngine() {
883 return arithmeticEngine;
884 }
885
886 /**
887 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
888 * value in the {@link Configuration}.
889 */
890 @Override
891 public boolean isArithmeticEngineSet() {
892 return true;
893 }
894
895 @Override
896 public String getNumberFormat() {
897 return numberFormat;
898 }
899
900 /**
901 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
902 * value in the {@link Configuration}.
903 */
904 @Override
905 public boolean isNumberFormatSet() {
906 return true;
907 }
908
909 @Override
910 public Map<String, TemplateNumberFormatFactory> getCustomNumberFormats() {
911 return customNumberFormats;
912 }
913
914 @Override
915 public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
916 return customNumberFormats.get(name);
917 }
918
919 /**
920 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
921 * value in the {@link Configuration}.
922 */
923 @Override
924 public boolean isCustomNumberFormatsSet() {
925 return true;
926 }
927
928 @Override
929 public String getBooleanFormat() {
930 return booleanFormat;
931 }
932
933 /**
934 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
935 * value in the {@link Configuration}.
936 */
937 @Override
938 public boolean isBooleanFormatSet() {
939 return true;
940 }
941
942 @Override
943 public String getTimeFormat() {
944 return timeFormat;
945 }
946
947 /**
948 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
949 * value in the {@link Configuration}.
950 */
951 @Override
952 public boolean isTimeFormatSet() {
953 return true;
954 }
955
956 @Override
957 public String getDateFormat() {
958 return dateFormat;
959 }
960
961 /**
962 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
963 * value in the {@link Configuration}.
964 */
965 @Override
966 public boolean isDateFormatSet() {
967 return true;
968 }
969
970 @Override
971 public String getDateTimeFormat() {
972 return dateTimeFormat;
973 }
974
975 /**
976 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
977 * value in the {@link Configuration}.
978 */
979 @Override
980 public boolean isDateTimeFormatSet() {
981 return true;
982 }
983
984 @Override
985 public Map<String, TemplateDateFormatFactory> getCustomDateFormats() {
986 return customDateFormats;
987 }
988
989 @Override
990 public TemplateDateFormatFactory getCustomDateFormat(String name) {
991 return customDateFormats.get(name);
992 }
993
994 /**
995 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
996 * value in the {@link Configuration}.
997 */
998 @Override
999 public boolean isCustomDateFormatsSet() {
1000 return true;
1001 }
1002
1003 @Override
1004 public ObjectWrapper getObjectWrapper() {
1005 return objectWrapper;
1006 }
1007
1008 /**
1009 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
1010 * value in the {@link Configuration}.
1011 */
1012 @Override
1013 public boolean isObjectWrapperSet() {
1014 return true;
1015 }
1016
1017 @Override
1018 public Charset getOutputEncoding() {
1019 return outputEncoding;
1020 }
1021
1022 /**
1023 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
1024 * value in the {@link Configuration}.
1025 */
1026 @Override
1027 public boolean isOutputEncodingSet() {
1028 return true;
1029 }
1030
1031 @Override
1032 public Charset getURLEscapingCharset() {
1033 return urlEscapingCharset;
1034 }
1035
1036 /**
1037 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
1038 * value in the {@link Configuration}.
1039 */
1040 @Override
1041 public boolean isURLEscapingCharsetSet() {
1042 return true;
1043 }
1044
1045 @Override
1046 public TemplateClassResolver getNewBuiltinClassResolver() {
1047 return newBuiltinClassResolver;
1048 }
1049
1050 /**
1051 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
1052 * value in the {@link Configuration}.
1053 */
1054 @Override
1055 public boolean isNewBuiltinClassResolverSet() {
1056 return true;
1057 }
1058
1059 @Override
1060 public boolean getAPIBuiltinEnabled() {
1061 return apiBuiltinEnabled;
1062 }
1063
1064 /**
1065 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
1066 * value in the {@link Configuration}.
1067 */
1068 @Override
1069 public boolean isAPIBuiltinEnabledSet() {
1070 return true;
1071 }
1072
1073 @Override
1074 public boolean getAutoFlush() {
1075 return autoFlush;
1076 }
1077
1078 /**
1079 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
1080 * value in the {@link Configuration}.
1081 */
1082 @Override
1083 public boolean isAutoFlushSet() {
1084 return true;
1085 }
1086
1087 @Override
1088 public boolean getShowErrorTips() {
1089 return showErrorTips;
1090 }
1091
1092 /**
1093 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
1094 * value in the {@link Configuration}.
1095 */
1096 @Override
1097 public boolean isShowErrorTipsSet() {
1098 return true;
1099 }
1100
1101 @Override
1102 public boolean getLazyImports() {
1103 return lazyImports;
1104 }
1105
1106 /**
1107 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
1108 * value in the {@link Configuration}.
1109 */
1110 @Override
1111 public boolean isLazyImportsSet() {
1112 return true;
1113 }
1114
1115 @Override
1116 public Boolean getLazyAutoImports() {
1117 return lazyAutoImports;
1118 }
1119
1120 /**
1121 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
1122 * value in the {@link Configuration}.
1123 */
1124 @Override
1125 public boolean isLazyAutoImportsSet() {
1126 return true;
1127 }
1128
1129 @Override
1130 public Map<String, String> getAutoImports() {
1131 return autoImports;
1132 }
1133
1134 /**
1135 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
1136 * value in the {@link Configuration}.
1137 */
1138 @Override
1139 public boolean isAutoImportsSet() {
1140 return true;
1141 }
1142
1143 @Override
1144 public List<String> getAutoIncludes() {
1145 return autoIncludes;
1146 }
1147
1148 /**
1149 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
1150 * value in the {@link Configuration}.
1151 */
1152 @Override
1153 public boolean isAutoIncludesSet() {
1154 return true;
1155 }
1156
1157 /**
1158 * {@inheritDoc}
1159 * <p>
1160 * Because {@link Configuration} has on parent, the {@code includeInherited} parameter is ignored.
1161 */
1162 @Override
1163 public Map<Serializable, Object> getCustomSettings(boolean includeInherited) {
1164 return customSettings;
1165 }
1166
1167 /**
1168 * {@inheritDoc}
1169 * <p>
1170 * Unlike the other isXxxSet methods of {@link Configuration}, this can return {@code false}, as at least the
1171 * builders in FreeMarker Core can't provide defaults for custom settings. Note that since
1172 * {@link #getCustomSetting(Serializable)} just returns {@code null} for unset custom settings, it's usually not a
1173 * problem.
1174 */
1175 @Override
1176 public boolean isCustomSettingSet(Serializable key) {
1177 return customSettings.containsKey(key);
1178 }
1179
1180 @Override
1181 public Object getCustomSetting(Serializable key) {
1182 return getCustomSetting(key, null, false);
1183 }
1184
1185 @Override
1186 public Object getCustomSetting(Serializable key, Object defaultValue) {
1187 return getCustomSetting(key, defaultValue, true);
1188 }
1189
1190 private Object getCustomSetting(Serializable key, Object defaultValue, boolean useDefaultValue) {
1191 Object value = customSettings.get(key);
1192 if (value != null || customSettings.containsKey(key)) {
1193 return value;
1194 }
1195 if (useDefaultValue) {
1196 return defaultValue;
1197 }
1198 throw new CustomSettingValueNotSetException(key);
1199 }
1200
1201 @Override
1202 @SuppressWarnings("unchecked")
1203 @SuppressFBWarnings("AT_OPERATION_SEQUENCE_ON_CONCURRENT_ABSTRACTION")
1204 public <T> T getCustomState(CustomStateKey<T> customStateKey) {
1205 T customState = (T) customStateMap.get(customStateKey);
1206 if (customState == null) {
1207 synchronized (customStateMapLock) {
1208 customState = (T) customStateMap.get(customStateKey);
1209 if (customState == null) {
1210 customState = customStateKey.create();
1211 if (customState == null) {
1212 throw new IllegalStateException("CustomStateKey.create() must not return null (for key: "
1213 + customStateKey + ")");
1214 }
1215 customStateMap.put(customStateKey, customState);
1216 }
1217 }
1218 }
1219 return customState;
1220 }
1221
1222 /**
1223 * Retrieves the template with the given name from the template cache, loading it into the cache first
1224 * if it's missing/staled.
1225 * <p>
1226 * This is a shorthand for {@link #getTemplate(String, Locale, Serializable, boolean)
1227 * getTemplate(name, null, null, false)}; see more details there.
1228 * <p>
1229 * See {@link Configuration} for an example of basic usage.
1230 */
1231 public Template getTemplate(String name)
1232 throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException {
1233 return getTemplate(name, null, null, false);
1234 }
1235
1236 /**
1237 * Shorthand for {@link #getTemplate(String, Locale, Serializable, boolean)
1238 * getTemplate(name, locale, null, null, false)}.
1239 */
1240 public Template getTemplate(String name, Locale locale)
1241 throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException {
1242 return getTemplate(name, locale, null, false);
1243 }
1244
1245 /**
1246 * Shorthand for {@link #getTemplate(String, Locale, Serializable, boolean)
1247 * getTemplate(name, locale, customLookupCondition, false)}.
1248 */
1249 public Template getTemplate(String name, Locale locale, Serializable customLookupCondition)
1250 throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException {
1251 return getTemplate(name, locale, customLookupCondition, false);
1252 }
1253
1254 /**
1255 * Retrieves the template with the given name (and according the specified further parameters) from the template
1256 * cache, loading it into the cache first if it's missing/staled.
1257 *
1258 * <p>
1259 * This method is thread-safe.
1260 *
1261 * <p>
1262 * See {@link Configuration} for an example of basic usage.
1263 *
1264 * @param name
1265 * The name or path of the template, which is not a real path, but interpreted inside the current
1266 * {@link TemplateLoader}. Can't be {@code null}. The exact syntax of the name depends on the
1267 * underlying {@link TemplateNameFormat} and to an extent on the {@link TemplateLoader} (or on the
1268 * {@link TemplateResolver} more generally), but the default configuration has some assumptions.
1269 * First, the name is expected to be a hierarchical path, with path components separated by a slash
1270 * character (not with backslash!). The path (the name) given here is always interpreted relative to
1271 * the "template root directory" and must <em>not</em> begin with slash. Then, the {@code ..} and
1272 * {@code .} path meta-elements will be resolved. For example, if the name is {@code a/../b/./c.f3ah},
1273 * then it will be simplified to {@code b/c.f3ah}. The rules regarding this are the same as with
1274 * conventional UN*X paths. The path must not reach outside the template root directory, that is, it
1275 * can't be something like {@code "../templates/my.f3ah"} (not even if this path happens to be
1276 * equivalent with {@code "/my.f3ah"}). Furthermore, the path is allowed to contain at most one path
1277 * element whose name is {@code *} (asterisk). This path meta-element triggers the <i>acquisition
1278 * mechanism</i>. If the template is not found in the location described by the concatenation of the
1279 * path left to the asterisk (called base path) and the part to the right of the asterisk (called
1280 * resource path), then the {@link TemplateResolver} (at least the default one) will attempt to remove
1281 * the rightmost path component from the base path (go up one directory) and concatenate that with
1282 * the resource path. The process is repeated until either a template is found, or the base path is
1283 * completely exhausted.
1284 *
1285 * @param locale
1286 * The requested locale of the template. This is what {@link Template#getLocale()} on the resulting
1287 * {@link Template} will return (unless it's overridden via {@link #getTemplateConfigurations()}). This
1288 * parameter can be {@code null} since 2.3.22, in which case it defaults to
1289 * {@link Configuration#getLocale()} (note that {@link Template#getLocale()} will give the default value,
1290 * not {@code null}). This parameter also drives localized template lookup. Assuming that you have
1291 * specified {@code en_US} as the locale and {@code myTemplate.f3ah} as the name of the template, and the
1292 * default {@link TemplateLookupStrategy} is used and
1293 * {@code #setLocalizedTemplateLookup(boolean) localizedTemplateLookup} is {@code true}, FreeMarker will first try to
1294 * retrieve {@code myTemplate_en_US.html}, then {@code myTemplate.en.f3ah}, and finally
1295 * {@code myTemplate.f3ah}. Note that that the template's locale will be {@code en_US} even if it only
1296 * finds {@code myTemplate.f3ah}. Note that when the {@code locale} setting is overridden with a
1297 * {@link TemplateConfiguration} provided by {@link #getTemplateConfigurations()}, that overrides the
1298 * value specified here, but only after the localized template lookup, that is, it modifies the template
1299 * found by the localized template lookup.
1300 *
1301 * @param customLookupCondition
1302 * This value can be used by a custom {@link TemplateLookupStrategy}; has no effect with the default one.
1303 * Can be {@code null} (though it's up to the custom {@link TemplateLookupStrategy} if it allows that).
1304 * This object will be used as part of the cache key, so it must to have a proper
1305 * {@link Object#equals(Object)} and {@link Object#hashCode()} method. It also should have reasonable
1306 * {@link Object#toString()}, as it's possibly quoted in error messages. The expected type is up to the
1307 * custom {@link TemplateLookupStrategy}. See also:
1308 * {@link TemplateLookupContext#getCustomLookupCondition()}.
1309 *
1310 * @param ignoreMissing
1311 * If {@code true}, the method won't throw {@link TemplateNotFoundException} if the template doesn't
1312 * exist, instead it returns {@code null}. Other kind of exceptions won't be suppressed.
1313 *
1314 * @return the requested template; maybe {@code null} when the {@code ignoreMissing} parameter is {@code true}.
1315 *
1316 * @throws TemplateNotFoundException
1317 * If the template could not be found. Note that this exception extends {@link IOException}.
1318 * @throws MalformedTemplateNameException
1319 * If the template name given was in violation with the {@link TemplateNameFormat} in use. Note that
1320 * this exception extends {@link IOException}.
1321 * @throws ParseException
1322 * (extends <code>IOException</code>) if the template is syntactically bad. Note that this exception
1323 * extends {@link IOException}.
1324 * @throws IOException
1325 * If there was some other problem with reading the template "file". Note that the other exceptions
1326 * extend {@link IOException}, so this should be catched the last.
1327 */
1328 public Template getTemplate(String name, Locale locale, Serializable customLookupCondition,
1329 boolean ignoreMissing)
1330 throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException {
1331 if (locale == null) {
1332 locale = getLocale();
1333 }
1334 final GetTemplateResult maybeTemp = getTemplateResolver().getTemplate(name, locale, customLookupCondition);
1335 final Template temp = maybeTemp.getTemplate();
1336 if (temp == null) {
1337 if (ignoreMissing) {
1338 return null;
1339 }
1340
1341 TemplateLoader tl = getTemplateLoader();
1342 String msg;
1343 if (tl == null) {
1344 msg = "Don't know where to load template " + _StringUtils.jQuote(name)
1345 + " from because the \"templateLoader\" FreeMarker "
1346 + "setting wasn't set (Configuration.setTemplateLoader), so it's null.";
1347 } else {
1348 final String missingTempNormName = maybeTemp.getMissingTemplateNormalizedName();
1349 final String missingTempReason = maybeTemp.getMissingTemplateReason();
1350 final TemplateLookupStrategy templateLookupStrategy = getTemplateLookupStrategy();
1351 msg = "Template not found for name " + _StringUtils.jQuote(name)
1352 + (missingTempNormName != null && name != null
1353 && !removeInitialSlash(name).equals(missingTempNormName)
1354 ? " (normalized: " + _StringUtils.jQuote(missingTempNormName) + ")"
1355 : "")
1356 + (customLookupCondition != null ? " and custom lookup condition "
1357 + _StringUtils.jQuote(customLookupCondition) : "")
1358 + "."
1359 + (missingTempReason != null
1360 ? "\nReason given: " + ensureSentenceIsClosed(missingTempReason)
1361 : "")
1362 + "\nThe name was interpreted by this TemplateLoader: "
1363 + _StringUtils.tryToString(tl) + "."
1364 + (!isKnownNonConfusingLookupStrategy(templateLookupStrategy)
1365 ? "\n(Before that, the name was possibly changed by this lookup strategy: "
1366 + _StringUtils.tryToString(templateLookupStrategy) + ".)"
1367 : "")
1368 + (missingTempReason == null && name.indexOf('\\') != -1
1369 ? "\nWarning: The name contains backslash (\"\\\") instead of slash (\"/\"); "
1370 + "template names should use slash only."
1371 : "");
1372 }
1373
1374 String normName = maybeTemp.getMissingTemplateNormalizedName();
1375 throw new TemplateNotFoundException(
1376 normName != null ? normName : name,
1377 customLookupCondition,
1378 msg);
1379 }
1380 return temp;
1381 }
1382
1383 private boolean isKnownNonConfusingLookupStrategy(TemplateLookupStrategy templateLookupStrategy) {
1384 return templateLookupStrategy == DefaultTemplateLookupStrategy.INSTANCE;
1385 }
1386
1387 private String removeInitialSlash(String name) {
1388 return name.startsWith("/") ? name.substring(1) : name;
1389 }
1390
1391 private String ensureSentenceIsClosed(String s) {
1392 if (s == null || s.length() == 0) {
1393 return s;
1394 }
1395
1396 final char lastChar = s.charAt(s.length() - 1);
1397 return lastChar == '.' || lastChar == '!' || lastChar == '?' ? s : s + ".";
1398 }
1399
1400 @Override
1401 public Charset getSourceEncoding() {
1402 return sourceEncoding;
1403 }
1404
1405 /**
1406 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
1407 * value in the {@link Configuration}.
1408 */
1409 @Override
1410 public boolean isSourceEncodingSet() {
1411 return true;
1412 }
1413
1414 @Override
1415 public Map<String, Object> getSharedVariables() {
1416 return sharedVariables;
1417 }
1418
1419 @Override
1420 public boolean isSharedVariablesSet() {
1421 return true;
1422 }
1423
1424 /**
1425 * Returns the shared variable as a {@link TemplateModel}, or {@code null} if it doesn't exist.
1426 */
1427 // TODO [FM3] How the caller can tell if a shared variable exists but null or it's missing?
1428 public TemplateModel getWrappedSharedVariable(String key) {
1429 return wrappedSharedVariables.get(key);
1430 }
1431
1432 /**
1433 * Removes all entries from the template cache, thus forcing reloading of templates
1434 * on subsequent <code>getTemplate</code> calls.
1435 *
1436 * <p>This method is thread-safe and can be called while the engine processes templates.
1437 */
1438 public void clearTemplateCache() {
1439 getTemplateResolver().clearTemplateCache();
1440 }
1441
1442 /**
1443 * Removes a template from the template cache, hence forcing the re-loading
1444 * of it when it's next time requested. This is to give the application
1445 * finer control over cache updating than the
1446 * {@link #getTemplateUpdateDelayMilliseconds() templateUpdateDelayMilliseconds} setting
1447 * alone does.
1448 *
1449 * <p>For the meaning of the parameters, see
1450 * {@link #getTemplate(String, Locale, Serializable, boolean)}.
1451 *
1452 * <p>This method is thread-safe and can be called while the engine processes templates.
1453 */
1454 public void removeTemplateFromCache(String name, Locale locale, Serializable customLookupCondition)
1455 throws IOException {
1456 getTemplateResolver().removeTemplateFromCache(name, locale, customLookupCondition);
1457 }
1458
1459 @Override
1460 public Boolean getLocalizedTemplateLookup() {
1461 return localizedTemplateLookup;
1462 }
1463
1464 /**
1465 * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
1466 * value in the {@link Configuration}.
1467 */
1468 @Override
1469 public boolean isLocalizedTemplateLookupSet() {
1470 return true;
1471 }
1472
1473 /**
1474 * Returns the FreeMarker version information, most importantly the major.minor.micro version numbers; do not use
1475 * this for {@link #getIncompatibleImprovements() #incompatibleImprovements} value, use constants like
1476 * {@link Configuration#VERSION_3_0_0} for that.
1477 */
1478 public static Version getVersion() {
1479 return VERSION;
1480 }
1481
1482 /**
1483 * Returns the names of the supported "built-ins". These are the ({@code expr?builtin_name}-like things). As of this
1484 * writing, this information doesn't depend on the configuration options, so it could be a static method, but
1485 * to be future-proof, it's an instance method.
1486 */
1487 public Set<String> getSupportedBuiltInNames() {
1488 return Collections.unmodifiableSet(ASTExpBuiltIn.BUILT_INS_BY_NAME.keySet());
1489 }
1490
1491 /**
1492 * Returns the names of the directives that are predefined by FreeMarker. These are the things that you call like
1493 * <tt>&lt;#directiveName ...&gt;</tt>.
1494 */
1495 public Set<String> getSupportedBuiltInDirectiveNames() {
1496 return ASTDirective.BUILT_IN_DIRECTIVE_NAMES;
1497 }
1498
1499 private static String getRequiredVersionProperty(Properties vp, String properyName) {
1500 String s = vp.getProperty(properyName);
1501 if (s == null) {
1502 throw new RuntimeException(
1503 "Version file is corrupt: \"" + properyName + "\" property is missing.");
1504 }
1505 return s;
1506 }
1507
1508 /**
1509 * Usually you use {@link Builder} instead of this abstract class, except where you declare the type of a method
1510 * parameter or field, where the more generic {@link ExtendableBuilder} should be used. {@link ExtendableBuilder}
1511 * might have other subclasses than {@link Builder}, because some applications needs different setting defaults
1512 * or other changes.
1513 */
1514 public abstract static class ExtendableBuilder<SelfT extends ExtendableBuilder<SelfT>>
1515 extends MutableParsingAndProcessingConfiguration<SelfT>
1516 implements TopLevelConfiguration, org.apache.freemarker.core.util.CommonBuilder<Configuration> {
1517
1518 public static final String LOCALIZED_TEMPLATE_LOOKUP_KEY = "localizedTemplateLookup";
1519 public static final String REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY = "registeredCustomOutputFormats";
1520 public static final String TEMPLATE_RESOLVER_KEY = "templateResolver";
1521 public static final String TEMPLATE_CACHE_STORAGE_KEY = "templateCacheStorage";
1522 public static final String TEMPLATE_UPDATE_DELAY_KEY = "templateUpdateDelay";
1523 public static final String TEMPLATE_LOADER_KEY = "templateLoader";
1524 public static final String TEMPLATE_LOOKUP_STRATEGY_KEY = "templateLookupStrategy";
1525 public static final String TEMPLATE_NAME_FORMAT_KEY = "templateNameFormat";
1526 public static final String SHARED_VARIABLES_KEY = "sharedVariables";
1527 public static final String TEMPLATE_CONFIGURATIONS_KEY = "templateConfigurations";
1528 public static final String OBJECT_WRAPPER_KEY = "objectWrapper";
1529
1530 private static final _UnmodifiableCompositeSet<String> SETTING_NAMES = new _UnmodifiableCompositeSet<>(
1531 MutableParsingAndProcessingConfiguration.getSettingNames(),
1532 new _SortedArraySet<>(
1533 // Must be sorted alphabetically!
1534 ExtendableBuilder.LOCALIZED_TEMPLATE_LOOKUP_KEY,
1535 ExtendableBuilder.OBJECT_WRAPPER_KEY,
1536 ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY,
1537 ExtendableBuilder.SHARED_VARIABLES_KEY,
1538 ExtendableBuilder.TEMPLATE_CACHE_STORAGE_KEY,
1539 ExtendableBuilder.TEMPLATE_CONFIGURATIONS_KEY,
1540 ExtendableBuilder.TEMPLATE_LOADER_KEY,
1541 ExtendableBuilder.TEMPLATE_LOOKUP_STRATEGY_KEY,
1542 ExtendableBuilder.TEMPLATE_NAME_FORMAT_KEY,
1543 ExtendableBuilder.TEMPLATE_RESOLVER_KEY,
1544 ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY
1545 ));
1546
1547 private Version incompatibleImprovements = Configuration.VERSION_3_0_0;
1548
1549 private TemplateResolver templateResolver;
1550 private TemplateResolver cachedDefaultTemplateResolver;
1551 private TemplateLoader templateLoader;
1552 private boolean templateLoaderSet;
1553 private CacheStorage templateCacheStorage;
1554 private boolean templateCacheStorageSet;
1555 private CacheStorage cachedDefaultTemplateCacheStorage;
1556 private TemplateLookupStrategy templateLookupStrategy;
1557 private boolean templateLookupStrategySet;
1558 private TemplateNameFormat templateNameFormat;
1559 private boolean templateNameFormatSet;
1560 private TemplateConfigurationFactory templateConfigurations;
1561 private boolean templateConfigurationsSet;
1562 private Long templateUpdateDelayMilliseconds;
1563 private boolean templateUpdateDelayMillisecondsSet;
1564 private Boolean localizedTemplateLookup;
1565 private boolean localizedTemplateLookupSet;
1566 private Collection<OutputFormat> registeredCustomOutputFormats;
1567 private Map<String, Object> sharedVariables;
1568 private ObjectWrapper objectWrapper;
1569
1570 private boolean alreadyBuilt;
1571
1572 /**
1573 * @param incompatibleImprovements
1574 * The initial value of the {@link Configuration#getIncompatibleImprovements()
1575 * incompatibleImprovements}; can't {@code null}. This can be later changed via
1576 * {@link #setIncompatibleImprovements(Version)}. The point here is just to ensure that it's never
1577 * {@code null}. Do NOT ever use {@link Configuration#getVersion()} to set this. Always use a fixed
1578 * value, like {@link #VERSION_3_0_0}, otherwise your application can break as you upgrade
1579 * FreeMarker.
1580 */
1581 protected ExtendableBuilder(Version incompatibleImprovements) {
1582 _NullArgumentException.check("incompatibleImprovements", incompatibleImprovements);
1583 this.incompatibleImprovements = incompatibleImprovements;
1584 }
1585
1586 @Override
1587 public Configuration build() throws ConfigurationException {
1588 if (alreadyBuilt) {
1589 throw new IllegalStateException("build() can only be executed once.");
1590 }
1591 Configuration configuration = new Configuration(this);
1592 alreadyBuilt = true;
1593 return configuration;
1594 }
1595
1596 @Override
1597 public void setSetting(String name, String value) throws ConfigurationException {
1598 boolean nameUnhandled = false;
1599 try {
1600 if (LOCALIZED_TEMPLATE_LOOKUP_KEY.equals(name)) {
1601 setLocalizedTemplateLookup(_StringUtils.getYesNo(value));
1602 } else if (REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY.equals(name)) {
1603 List list = (List) _ObjectBuilderSettingEvaluator.eval(
1604 value, List.class, true, _SettingEvaluationEnvironment.getCurrent());
1605 for (Object item : list) {
1606 if (!(item instanceof OutputFormat)) {
1607 throw new InvalidSettingValueException(name, value,
1608 "List items must be " + OutputFormat.class.getName() + " instances.");
1609 }
1610 }
1611 setRegisteredCustomOutputFormats(list);
1612 } else if (TEMPLATE_CACHE_STORAGE_KEY.equals(name)) {
1613 if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
1614 unsetTemplateCacheStorage();
1615 } if (value.indexOf('.') == -1) {
1616 int strongSize = 0;
1617 int softSize = 0;
1618 Map map = _StringUtils.parseNameValuePairList(
1619 value, String.valueOf(Integer.MAX_VALUE));
1620 Iterator it = map.entrySet().iterator();
1621 while (it.hasNext()) {
1622 Map.Entry ent = (Map.Entry) it.next();
1623 String pName = (String) ent.getKey();
1624 int pValue;
1625 try {
1626 pValue = Integer.parseInt((String) ent.getValue());
1627 } catch (NumberFormatException e) {
1628 throw new InvalidSettingValueException(name, value,
1629 "Malformed integer number (shown quoted): " + _StringUtils.jQuote(ent.getValue()));
1630 }
1631 if ("soft".equalsIgnoreCase(pName)) {
1632 softSize = pValue;
1633 } else if ("strong".equalsIgnoreCase(pName)) {
1634 strongSize = pValue;
1635 } else {
1636 throw new InvalidSettingValueException(name, value,
1637 "Unsupported cache parameter name (shown quoted): "
1638 + _StringUtils.jQuote(ent.getValue()));
1639 }
1640 }
1641 if (softSize == 0 && strongSize == 0) {
1642 throw new InvalidSettingValueException(name, value,
1643 "Either cache soft- or strong size must be set and non-0.");
1644 }
1645 setTemplateCacheStorage(new MruCacheStorage(strongSize, softSize));
1646 } else {
1647 setTemplateCacheStorage((CacheStorage) _ObjectBuilderSettingEvaluator.eval(
1648 value, CacheStorage.class, false, _SettingEvaluationEnvironment.getCurrent()));
1649 }
1650 } else if (TEMPLATE_UPDATE_DELAY_KEY.equals(name)) {
1651 final String valueWithoutUnit;
1652 final String unit;
1653 int numberEnd = 0;
1654 while (numberEnd < value.length() && !Character.isAlphabetic(value.charAt(numberEnd))) {
1655 numberEnd++;
1656 }
1657 valueWithoutUnit = value.substring(0, numberEnd).trim();
1658 unit = value.substring(numberEnd).trim();
1659
1660 final long multipier;
1661 if (unit.equals("ms")) {
1662 multipier = 1;
1663 } else if (unit.equals("s")) {
1664 multipier = 1000;
1665 } else if (unit.equals("m")) {
1666 multipier = 1000 * 60;
1667 } else if (unit.equals("h")) {
1668 multipier = 1000 * 60 * 60;
1669 } else if (!unit.isEmpty()) {
1670 throw new InvalidSettingValueException(name, value,
1671 "Unrecognized time unit " + _StringUtils.jQuote(unit) + ". Valid units are: ms, s, m, h");
1672 } else {
1673 multipier = 0;
1674 }
1675
1676 int parsedValue = Integer.parseInt(valueWithoutUnit);
1677 if (multipier == 0 && parsedValue != 0) {
1678 throw new InvalidSettingValueException(name, value,
1679 "Time unit must be specified for a non-0 value (examples: 500 ms, 3 s, 2 m, 1 h).");
1680 }
1681
1682 setTemplateUpdateDelayMilliseconds(parsedValue * multipier);
1683 } else if (SHARED_VARIABLES_KEY.equals(name)) {
1684 Map<?, ?> sharedVariables = (Map<?, ?>) _ObjectBuilderSettingEvaluator.eval(
1685 value, Map.class, false, _SettingEvaluationEnvironment.getCurrent());
1686 for (Object key : sharedVariables.keySet()) {
1687 if (!(key instanceof String)) {
1688 throw new InvalidSettingValueException(name, null, false,
1689 "All keys in this Map must be strings, but one of them is an instance of "
1690 + "this class: " + _ClassUtils.getShortClassNameOfObject(key), null);
1691 }
1692 }
1693 setSharedVariables((Map) sharedVariables);
1694 } else if (INCOMPATIBLE_IMPROVEMENTS_KEY.equals(name)) {
1695 setIncompatibleImprovements(new Version(value));
1696 } else if (TEMPLATE_RESOLVER_KEY.equals(name)) {
1697 if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
1698 unsetTemplateResolver();
1699 } else {
1700 setTemplateResolver((TemplateResolver) _ObjectBuilderSettingEvaluator.eval(
1701 value, TemplateResolver.class, false,
1702 _SettingEvaluationEnvironment.getCurrent()));
1703 }
1704 } else if (TEMPLATE_LOADER_KEY.equals(name)) {
1705 if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
1706 unsetTemplateLoader();
1707 } else {
1708 setTemplateLoader((TemplateLoader) _ObjectBuilderSettingEvaluator.eval(
1709 value, TemplateLoader.class, true,
1710 _SettingEvaluationEnvironment.getCurrent()));
1711 }
1712 } else if (TEMPLATE_LOOKUP_STRATEGY_KEY.equals(name)) {
1713 if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
1714 unsetTemplateLookupStrategy();
1715 } else {
1716 setTemplateLookupStrategy((TemplateLookupStrategy) _ObjectBuilderSettingEvaluator.eval(
1717 value, TemplateLookupStrategy.class, false,
1718 _SettingEvaluationEnvironment.getCurrent()));
1719 }
1720 } else if (TEMPLATE_NAME_FORMAT_KEY.equals(name)) {
1721 if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
1722 unsetTemplateNameFormat();
1723 } else {
1724 throw new InvalidSettingValueException(name, value,
1725 "No such predefined template name format");
1726 }
1727 } else if (TEMPLATE_CONFIGURATIONS_KEY.equals(name)) {
1728 if (value.equals(NULL_VALUE)) {
1729 setTemplateConfigurations(null);
1730 } else {
1731 setTemplateConfigurations((TemplateConfigurationFactory) _ObjectBuilderSettingEvaluator.eval(
1732 value, TemplateConfigurationFactory.class, false,
1733 _SettingEvaluationEnvironment.getCurrent()));
1734 }
1735 } else if (OBJECT_WRAPPER_KEY.equals(name)) {
1736 if (DEFAULT_VALUE.equalsIgnoreCase(value)) {
1737 this.unsetObjectWrapper();
1738 } else if ("restricted".equalsIgnoreCase(value)) {
1739 // FM3 TODO should depend on IcI, but maybe the simplest is to remove this convenience value
1740 setObjectWrapper(new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
1741 } else {
1742 setObjectWrapper((ObjectWrapper) _ObjectBuilderSettingEvaluator.eval(
1743 value, ObjectWrapper.class, false, _SettingEvaluationEnvironment.getCurrent()));
1744 }
1745 } else {
1746 nameUnhandled = true;
1747 }
1748 } catch (InvalidSettingValueException e) {
1749 throw e;
1750 } catch (Exception e) {
1751 throw new InvalidSettingValueException(name, value, e);
1752 }
1753 if (nameUnhandled) {
1754 super.setSetting(name, value);
1755 }
1756 }
1757
1758 /**
1759 * Returns the valid {@link Configuration} setting names. Naturally, this includes the
1760 * {@link MutableProcessingConfiguration} setting names too.
1761 *
1762 * @see MutableProcessingConfiguration#getSettingNames()
1763 */
1764 public static Set<String> getSettingNames() {
1765 return SETTING_NAMES;
1766 }
1767
1768 @Override
1769 protected Version getRemovalVersionForUnknownSetting(String name) {
1770 if (name.equals("strictSyntax") || name.equalsIgnoreCase("strict_syntax")) {
1771 return Configuration.VERSION_3_0_0;
1772 }
1773 return super.getRemovalVersionForUnknownSetting(name);
1774 }
1775
1776 @Override
1777 protected String getCorrectedNameForUnknownSetting(String name) {
1778 switch(name.toLowerCase()) {
1779 case "encoding":
1780 case "default_encoding":
1781 case "charset":
1782 case "default_charset":
1783 case "defaultencoding":
1784 case "defaultcharset":
1785 case "sourceencoding":
1786 case "source_encoding":
1787 return SOURCE_ENCODING_KEY;
1788 case "incompatible_enhancements":
1789 case "incompatibleenhancements":
1790 case "incompatibleimprovements":
1791 case "incompatibleImprovements":
1792 return INCOMPATIBLE_IMPROVEMENTS_KEY;
1793 case "cachestorage":
1794 case "cache_storage":
1795 case "templatecachestorage":
1796 case "template_cache_storage":
1797 return TEMPLATE_CACHE_STORAGE_KEY;
1798 case "localizedlookup":
1799 case "localized_lookup":
1800 case "localizedtemplatelookup":
1801 case "localized_template_lookup":
1802 return LOCALIZED_TEMPLATE_LOOKUP_KEY;
1803 case "templateupdateinterval":
1804 case "templateupdatedelay":
1805 case "template_update_delay":
1806 return TEMPLATE_UPDATE_DELAY_KEY;
1807 default:
1808 return super.getCorrectedNameForUnknownSetting(name);
1809 }
1810
1811 }
1812
1813 @Override
1814 public TemplateResolver getTemplateResolver() {
1815 return isTemplateResolverSet() ? templateResolver : getDefaultTemplateResolver();
1816 }
1817
1818 @Override
1819 public boolean isTemplateResolverSet() {
1820 return templateResolver != null;
1821 }
1822
1823 protected TemplateResolver getDefaultTemplateResolver() {
1824 if (cachedDefaultTemplateResolver == null) {
1825 cachedDefaultTemplateResolver = new DefaultTemplateResolver();
1826 }
1827 return cachedDefaultTemplateResolver;
1828 }
1829
1830 /**
1831 * Setter pair of {@link Configuration#getTemplateResolver()}; note {@code null}.
1832 */
1833 public void setTemplateResolver(TemplateResolver templateResolver) {
1834 _NullArgumentException.check("templateResolver", templateResolver);
1835 this.templateResolver = templateResolver;
1836 }
1837
1838 /**
1839 * Fluent API equivalent of {@link #setTemplateResolver(TemplateResolver)}
1840 */
1841 public SelfT templateResolver(TemplateResolver templateResolver) {
1842 setTemplateResolver(templateResolver);
1843 return self();
1844 }
1845
1846 /**
1847 * Resets this setting to its initial state, as if it was never set.
1848 */
1849 public void unsetTemplateResolver() {
1850 templateResolver = null;
1851 }
1852
1853 @Override
1854 public TemplateLoader getTemplateLoader() {
1855 return isTemplateLoaderSet() ? templateLoader : getDefaultTemplateLoaderTRAware();
1856 }
1857
1858 @Override
1859 public boolean isTemplateLoaderSet() {
1860 return templateLoaderSet;
1861 }
1862
1863 private TemplateLoader getDefaultTemplateLoaderTRAware() {
1864 return isTemplateResolverSet() && !getTemplateResolver().supportsTemplateLoaderSetting() ?
1865 null : getDefaultTemplateLoader();
1866 }
1867
1868 /**
1869 * The default value when the {@link #getTemplateResolver() templateResolver} supports this setting (otherwise
1870 * the default is hardwired to be {@code null} and this method isn't called).
1871 */
1872 protected TemplateLoader getDefaultTemplateLoader() {
1873 return null;
1874 }
1875
1876 /**
1877 * Setter pair of {@link Configuration#getTemplateLoader()}. Note that {@code null} is a valid value.
1878 */
1879 public void setTemplateLoader(TemplateLoader templateLoader) {
1880 this.templateLoader = templateLoader;
1881 templateLoaderSet = true;
1882 }
1883
1884 /**
1885 * Fluent API equivalent of {@link #setTemplateLoader(TemplateLoader)}
1886 */
1887 public SelfT templateLoader(TemplateLoader templateLoader) {
1888 setTemplateLoader(templateLoader);
1889 return self();
1890 }
1891
1892 /**
1893 * Resets this setting to its initial state, as if it was never set.
1894 */
1895 public void unsetTemplateLoader() {
1896 templateLoader = null;
1897 templateLoaderSet = false;
1898 }
1899
1900 @Override
1901 public CacheStorage getTemplateCacheStorage() {
1902 return isTemplateCacheStorageSet() ? templateCacheStorage : getDefaultTemplateCacheStorageTRAware();
1903 }
1904
1905 @Override
1906 public boolean isTemplateCacheStorageSet() {
1907 return templateCacheStorageSet;
1908 }
1909
1910 private CacheStorage getDefaultTemplateCacheStorageTRAware() {
1911 return isTemplateResolverSet() && !getTemplateResolver().supportsTemplateCacheStorageSetting() ? null
1912 : getDefaultTemplateCacheStorage();
1913 }
1914
1915 /**
1916 * The default value when the {@link #getTemplateResolver() templateResolver} supports this setting (otherwise
1917 * the default is hardwired to be {@code null} and this method isn't called).
1918 */
1919 protected CacheStorage getDefaultTemplateCacheStorage() {
1920 if (cachedDefaultTemplateCacheStorage == null) {
1921 // If this will depend on incompatibleImprovements, null it out in onIncompatibleImprovementsChanged()!
1922 cachedDefaultTemplateCacheStorage = new DefaultSoftCacheStorage();
1923 }
1924 return cachedDefaultTemplateCacheStorage;
1925 }
1926
1927 /**
1928 * Setter pair of {@link Configuration#getTemplateCacheStorage()}
1929 */
1930 public void setTemplateCacheStorage(CacheStorage templateCacheStorage) {
1931 this.templateCacheStorage = templateCacheStorage;
1932 this.templateCacheStorageSet = true;
1933 cachedDefaultTemplateCacheStorage = null;
1934 }
1935
1936 /**
1937 * Fluent API equivalent of {@link #setTemplateCacheStorage(CacheStorage)}
1938 */
1939 public SelfT templateCacheStorage(CacheStorage templateCacheStorage) {
1940 setTemplateCacheStorage(templateCacheStorage);
1941 return self();
1942 }
1943
1944 /**
1945 * Resets this setting to its initial state, as if it was never set.
1946 */
1947 public void unsetTemplateCacheStorage() {
1948 templateCacheStorage = null;
1949 templateCacheStorageSet = false;
1950 }
1951
1952 @Override
1953 public TemplateLookupStrategy getTemplateLookupStrategy() {
1954 return isTemplateLookupStrategySet() ? templateLookupStrategy : getDefaultTemplateLookupStrategyTRAware();
1955 }
1956
1957 @Override
1958 public boolean isTemplateLookupStrategySet() {
1959 return templateLookupStrategySet;
1960 }
1961
1962 private TemplateLookupStrategy getDefaultTemplateLookupStrategyTRAware() {
1963 return isTemplateResolverSet() && !getTemplateResolver().supportsTemplateLookupStrategySetting() ? null :
1964 getDefaultTemplateLookupStrategy();
1965 }
1966
1967 /**
1968 * The default value when the {@link #getTemplateResolver() templateResolver} supports this setting (otherwise
1969 * the default is hardwired to be {@code null} and this method isn't called).
1970 */
1971 protected TemplateLookupStrategy getDefaultTemplateLookupStrategy() {
1972 return DefaultTemplateLookupStrategy.INSTANCE;
1973 }
1974
1975 /**
1976 * Setter pair of {@link Configuration#getTemplateLookupStrategy()}.
1977 */
1978 public void setTemplateLookupStrategy(TemplateLookupStrategy templateLookupStrategy) {
1979 this.templateLookupStrategy = templateLookupStrategy;
1980 templateLookupStrategySet = true;
1981 }
1982
1983 /**
1984 * Fluent API equivalent of {@link #setTemplateLookupStrategy(TemplateLookupStrategy)}
1985 */
1986 public SelfT templateLookupStrategy(TemplateLookupStrategy templateLookupStrategy) {
1987 setTemplateLookupStrategy(templateLookupStrategy);
1988 return self();
1989 }
1990
1991 /**
1992 * Resets this setting to its initial state, as if it was never set.
1993 */
1994 public void unsetTemplateLookupStrategy() {
1995 templateLookupStrategy = null;
1996 templateLookupStrategySet = false;
1997 }
1998
1999 @Override
2000 public TemplateNameFormat getTemplateNameFormat() {
2001 return isTemplateNameFormatSet() ? templateNameFormat : getDefaultTemplateNameFormatTRAware();
2002 }
2003
2004 /**
2005 * Tells if this setting was explicitly set (if not, the default value of the setting will be used).
2006 */
2007 @Override
2008 public boolean isTemplateNameFormatSet() {
2009 return templateNameFormatSet;
2010 }
2011
2012 private TemplateNameFormat getDefaultTemplateNameFormatTRAware() {
2013 return isTemplateResolverSet() && !getTemplateResolver().supportsTemplateNameFormatSetting() ? null
2014 : getDefaultTemplateNameFormat();
2015 }
2016
2017 /**
2018 * The default value when the {@link #getTemplateResolver() templateResolver} supports this setting (otherwise
2019 * the default is hardwired to be {@code null} and this method isn't called).
2020 */
2021 protected TemplateNameFormat getDefaultTemplateNameFormat() {
2022 return DefaultTemplateNameFormat.INSTANCE;
2023 }
2024
2025 /**
2026 * Setter pair of {@link Configuration#getTemplateNameFormat()}.
2027 */
2028 public void setTemplateNameFormat(TemplateNameFormat templateNameFormat) {
2029 this.templateNameFormat = templateNameFormat;
2030 templateNameFormatSet = true;
2031 }
2032
2033 /**
2034 * Fluent API equivalent of {@link #setTemplateNameFormat(TemplateNameFormat)}
2035 */
2036 public SelfT templateNameFormat(TemplateNameFormat templateNameFormat) {
2037 setTemplateNameFormat(templateNameFormat);
2038 return self();
2039 }
2040
2041 /**
2042 * Resets this setting to its initial state, as if it was never set.
2043 */
2044 public void unsetTemplateNameFormat() {
2045 this.templateNameFormat = null;
2046 templateNameFormatSet = false;
2047 }
2048
2049 @Override
2050 public TemplateConfigurationFactory getTemplateConfigurations() {
2051 return isTemplateConfigurationsSet() ? templateConfigurations : getDefaultTemplateConfigurationsTRAware();
2052 }
2053
2054 @Override
2055 public boolean isTemplateConfigurationsSet() {
2056 return templateConfigurationsSet;
2057 }
2058
2059 private TemplateConfigurationFactory getDefaultTemplateConfigurationsTRAware() {
2060 return isTemplateResolverSet() && !getTemplateResolver().supportsTemplateConfigurationsSetting() ? null
2061 : getDefaultTemplateConfigurations();
2062 }
2063
2064 /**
2065 * The default value when the {@link #getTemplateResolver() templateResolver} supports this setting (otherwise
2066 * the default is hardwired to be {@code null} and this method isn't called).
2067 */
2068 protected TemplateConfigurationFactory getDefaultTemplateConfigurations() {
2069 return null;
2070 }
2071
2072 /**
2073 * The template configurations that will be added to the built {@link Configuration} before the ones
2074 * coming from {@link #setCustomNumberFormats(Map)}}, where addition happens with
2075 * {@link MergingTemplateConfigurationFactory}. When overriding this method, always
2076 * consider adding to the return value of the super method, rather than replacing it.
2077 *
2078 * @return Maybe {@code null}.
2079 */
2080 protected TemplateConfigurationFactory getImpliedTemplateConfigurations() {
2081 return null;
2082 }
2083
2084 /**
2085 * Setter pair of {@link Configuration#getTemplateConfigurations()}.
2086 */
2087 public void setTemplateConfigurations(TemplateConfigurationFactory templateConfigurations) {
2088 this.templateConfigurations = templateConfigurations;
2089 templateConfigurationsSet = true;
2090 }
2091
2092 /**
2093 * Fluent API equivalent of {@link #setTemplateConfigurations(TemplateConfigurationFactory)}
2094 */
2095 public SelfT templateConfigurations(TemplateConfigurationFactory templateConfigurations) {
2096 setTemplateConfigurations(templateConfigurations);
2097 return self();
2098 }
2099
2100 /**
2101 * Resets this setting to its initial state, as if it was never set.
2102 */
2103 public void unsetTemplateConfigurations() {
2104 this.templateConfigurations = null;
2105 templateConfigurationsSet = false;
2106 }
2107
2108 @Override
2109 public Long getTemplateUpdateDelayMilliseconds() {
2110 return isTemplateUpdateDelayMillisecondsSet() ? templateUpdateDelayMilliseconds
2111 : getDefaultTemplateUpdateDelayMillisecondsTRAware();
2112 }
2113
2114 @Override
2115 public boolean isTemplateUpdateDelayMillisecondsSet() {
2116 return templateUpdateDelayMillisecondsSet;
2117 }
2118
2119 private Long getDefaultTemplateUpdateDelayMillisecondsTRAware() {
2120 return isTemplateResolverSet() && !getTemplateResolver().supportsTemplateUpdateDelayMillisecondsSetting()
2121 ? null : getDefaultTemplateUpdateDelayMilliseconds();
2122 }
2123
2124 /**
2125 * The default value when the {@link #getTemplateResolver() templateResolver} supports this setting (otherwise
2126 * the default is hardwired to be {@code null} and this method isn't called).
2127 */
2128 protected Long getDefaultTemplateUpdateDelayMilliseconds() {
2129 return 5000L;
2130 }
2131
2132 /**
2133 * Setter pair of {@link Configuration#getTemplateUpdateDelayMilliseconds()}.
2134 */
2135 public void setTemplateUpdateDelayMilliseconds(Long templateUpdateDelayMilliseconds) {
2136 this.templateUpdateDelayMilliseconds = templateUpdateDelayMilliseconds;
2137 templateUpdateDelayMillisecondsSet = true;
2138 }
2139
2140 /**
2141 * Fluent API equivalent of {@link #setTemplateUpdateDelayMilliseconds(Long)}
2142 */
2143 public SelfT templateUpdateDelayMilliseconds(Long templateUpdateDelayMilliseconds) {
2144 setTemplateUpdateDelayMilliseconds(templateUpdateDelayMilliseconds);
2145 return self();
2146 }
2147
2148 /**
2149 * Resets this setting to its initial state, as if it was never set.
2150 */
2151 public void unsetTemplateUpdateDelayMilliseconds() {
2152 templateUpdateDelayMilliseconds = null;
2153 templateUpdateDelayMillisecondsSet = false;
2154 }
2155
2156 @Override
2157 public Boolean getLocalizedTemplateLookup() {
2158 return isLocalizedTemplateLookupSet() ? localizedTemplateLookup : getDefaultLocalizedTemplateLookupTRAware();
2159 }
2160
2161 @Override
2162 public boolean isLocalizedTemplateLookupSet() {
2163 return localizedTemplateLookupSet;
2164 }
2165
2166 private Boolean getDefaultLocalizedTemplateLookupTRAware() {
2167 return isTemplateResolverSet() && !getTemplateResolver().supportsLocalizedTemplateLookupSetting() ? null
2168 : getDefaultLocalizedTemplateLookup();
2169 }
2170
2171 /**
2172 * The default value when the {@link #getTemplateResolver() templateResolver} supports this setting (otherwise
2173 * the default is hardwired to be {@code null} and this method isn't called).
2174 */
2175 protected Boolean getDefaultLocalizedTemplateLookup() {
2176 return true;
2177 }
2178
2179 /**
2180 * Setter pair of {@link Configuration#getLocalizedTemplateLookup()}.
2181 */
2182 public void setLocalizedTemplateLookup(Boolean localizedTemplateLookup) {
2183 this.localizedTemplateLookup = localizedTemplateLookup;
2184 localizedTemplateLookupSet = true;
2185 }
2186
2187 /**
2188 * Fluent API equivalent of {@link #setLocalizedTemplateLookup(Boolean)}
2189 */
2190 public SelfT localizedTemplateLookup(Boolean localizedTemplateLookup) {
2191 setLocalizedTemplateLookup(localizedTemplateLookup);
2192 return self();
2193 }
2194
2195 /**
2196 * Resets this setting to its initial state, as if it was never set.
2197 */
2198 public void unsetLocalizedTemplateLookup() {
2199 this.localizedTemplateLookup = null;
2200 localizedTemplateLookupSet = false;
2201 }
2202
2203 @Override
2204 public Collection<OutputFormat> getRegisteredCustomOutputFormats() {
2205 return isRegisteredCustomOutputFormatsSet() ? registeredCustomOutputFormats
2206 : getDefaultRegisteredCustomOutputFormats();
2207 }
2208
2209 @Override
2210 public boolean isRegisteredCustomOutputFormatsSet() {
2211 return registeredCustomOutputFormats != null;
2212 }
2213
2214 protected Collection<OutputFormat> getDefaultRegisteredCustomOutputFormats() {
2215 return Collections.emptyList();
2216 }
2217
2218 /**
2219 * The imports that will be added to the built {@link Configuration} before the ones coming from
2220 * {@link #getRegisteredCustomOutputFormats()}. When overriding this method, always consider adding to the
2221 * return value of the super method, rather than replacing it.
2222 *
2223 * @return Immutable {@link Collection}; not {@code null}
2224 */
2225 protected Collection<OutputFormat> getImpliedRegisteredCustomOutputFormats() {
2226 return Collections.emptyList();
2227 }
2228
2229 /**
2230 * Setter pair of {@link Configuration#getRegisteredCustomOutputFormats()}.
2231 */
2232 public void setRegisteredCustomOutputFormats(Collection<OutputFormat> registeredCustomOutputFormats) {
2233 _NullArgumentException.check("registeredCustomOutputFormats", registeredCustomOutputFormats);
2234 this.registeredCustomOutputFormats = Collections.unmodifiableCollection(
2235 new ArrayList<>(registeredCustomOutputFormats));
2236 }
2237
2238 /**
2239 * Fluent API equivalent of {@link #setRegisteredCustomOutputFormats(Collection)}
2240 */
2241 public SelfT registeredCustomOutputFormats(Collection<OutputFormat> registeredCustomOutputFormats) {
2242 setRegisteredCustomOutputFormats(registeredCustomOutputFormats);
2243 return self();
2244 }
2245
2246 /**
2247 * Varargs overload if {@link #registeredCustomOutputFormats(Collection)}.
2248 */
2249 public SelfT registeredCustomOutputFormats(OutputFormat... registeredCustomOutputFormats) {
2250 return registeredCustomOutputFormats(Arrays.asList(registeredCustomOutputFormats));
2251 }
2252
2253 /**
2254 * Resets this setting to its initial state, as if it was never set.
2255 */
2256 public void unsetRegisteredCustomOutputFormats() {
2257 this.registeredCustomOutputFormats = null;
2258 }
2259
2260 @Override
2261 public Map<String, Object> getSharedVariables() {
2262 return isSharedVariablesSet() ? sharedVariables : getDefaultSharedVariables();
2263 }
2264
2265 /**
2266 * Returns the shared variable, or {@code null} if it doesn't exist. If the shared variables weren't set,
2267 * it will also try to find it in the default shared variables (which is an empty map in the standard
2268 * implementation).
2269 */
2270 // TODO [FM3] How the caller can tell if a shared variable exists but null or it's missing?
2271 public Object getSharedVariable(String key) {
2272 return isSharedVariablesSet() ? sharedVariables.get(key) : getDefaultSharedVariables().get(key);
2273 }
2274
2275 @Override
2276 public boolean isSharedVariablesSet() {
2277 return sharedVariables != null;
2278 }
2279
2280 /**
2281 * The {@link Map} to use as shared variables if {@link #isSharedVariablesSet()} is {@code false}.
2282 *
2283 * @see #getImpliedSharedVariables()
2284 */
2285 protected Map<String, Object> getDefaultSharedVariables() {
2286 return Collections.emptyMap();
2287 }
2288
2289 /**
2290 * The shared variables that will be added to the built {@link Configuration} before the ones coming from
2291 * {@link #getSharedVariables()}. When overriding this method, always consider adding to the return value
2292 * of the super method, rather than replacing it.
2293 *
2294 * @return Immutable {@link Map}; not {@code null}
2295 */
2296 protected Map<String, Object> getImpliedSharedVariables() {
2297 return Collections.emptyMap();
2298 }
2299
2300 /**
2301 * Setter pair of {@link Configuration#getSharedVariables()}.
2302 *
2303 * @param sharedVariables
2304 * Will be copied (to prevent aliasing effect); can't be {@code null}; the {@link Map} can't contain
2305 * {@code null} key, but can contain {@code null} value.
2306 */
2307 public void setSharedVariables(Map<String, ?> sharedVariables) {
2308 _NullArgumentException.check("sharedVariables", sharedVariables);
2309 _CollectionUtils.safeCastMap(
2310 "sharedVariables", sharedVariables, String.class, false, Object.class,true);
2311 this.sharedVariables = Collections.unmodifiableMap(new HashMap<>(sharedVariables));
2312 }
2313
2314 /**
2315 * Fluent API equivalent of {@link #setSharedVariables(Map)}
2316 */
2317 public SelfT sharedVariables(Map<String, ?> sharedVariables) {
2318 setSharedVariables(sharedVariables);
2319 return self();
2320 }
2321
2322 public void unsetSharedVariables() {
2323 this.sharedVariables = null;
2324 }
2325
2326 @Override
2327 protected TemplateLanguage getDefaultTemplateLanguage() {
2328 return DefaultTemplateLanguage.F3AC;
2329 }
2330
2331 @Override
2332 public Version getIncompatibleImprovements() {
2333 return incompatibleImprovements;
2334 }
2335
2336 @Override
2337 public boolean isIncompatibleImprovementsSet() {
2338 return true;
2339 }
2340
2341 /**
2342 * Setter pair of {@link Configuration#getIncompatibleImprovements()}.
2343 *
2344 * <p>Do NOT ever use {@link Configuration#getVersion()} to set the "incompatible improvements". Always use
2345 * a fixed value, like {@link #VERSION_3_0_0}. Otherwise your application can break as you upgrade FreeMarker.
2346 *
2347 * <p>This is not called from the {@link ExtendableBuilder#ExtendableBuilder(Version)}; the initial value is set
2348 * without this.
2349 *
2350 * @param incompatibleImprovements
2351 * Not {@code null}. Must be a supported version (for example not a future version).
2352 *
2353 * @throws IllegalArgumentException
2354 * If {@code incompatibleImmprovements} refers to a version that wasn't released yet when the currently
2355 * used FreeMarker version was released, or is less than 3.0.0, or is {@code null}.
2356 */
2357 public void setIncompatibleImprovements(Version incompatibleImprovements) {
2358 _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
2359 Version previousIncompatibleImprovements = this.incompatibleImprovements;
2360 this.incompatibleImprovements = incompatibleImprovements;
2361 if (!incompatibleImprovements.equals(previousIncompatibleImprovements)) {
2362 onIncompatibleImprovementsChanged(previousIncompatibleImprovements);
2363 }
2364 }
2365
2366 /**
2367 * Fluent API equivalent of {@link #setIncompatibleImprovements(Version)}
2368 */
2369 public SelfT incompatibleImprovements(Version incompatibleImprovements) {
2370 setIncompatibleImprovements(incompatibleImprovements);
2371 return self();
2372 }
2373
2374 /**
2375 * Invoked by {@link #setIncompatibleImprovements(Version)} when the value is changed by it. This is not invoked
2376 * when {@linkplain #getIncompatibleImprovements()} incompatibleImprovements} is initialized (i.e., when the
2377 * previous value was {@code null}), nor if {@link #setIncompatibleImprovements(Version)} was called with a
2378 * value that's equivalent with the previous value. When this method is called, {@link
2379 * #getIncompatibleImprovements()} will already return the new value.
2380 * <p>
2381 * This method is typically used to drop cached default values that are {@linkplain
2382 * #getIncompatibleImprovements()} incompatibleImprovements} dependent.
2383 * <p>
2384 * If you override this method, don't forget to call the super method.
2385 *
2386 * @param previousIncompatibleImprovements
2387 * The value of incompatibleImprovements before it was changed. Not {@code null}.
2388 */
2389 protected void onIncompatibleImprovementsChanged(Version previousIncompatibleImprovements) {
2390 cachedDefaultObjectWrapper = null;
2391 }
2392
2393 @Override
2394 protected boolean getDefaultWhitespaceStripping() {
2395 return true;
2396 }
2397
2398 @Override
2399 protected AutoEscapingPolicy getDefaultAutoEscapingPolicy() {
2400 return AutoEscapingPolicy.ENABLE_IF_DEFAULT;
2401 }
2402
2403 @Override
2404 protected OutputFormat getDefaultOutputFormat() {
2405 return UndefinedOutputFormat.INSTANCE;
2406 }
2407
2408 @Override
2409 protected boolean getDefaultRecognizeStandardFileExtensions() {
2410 return true;
2411 }
2412
2413 @Override
2414 protected Charset getDefaultSourceEncoding() {
2415 return StandardCharsets.UTF_8;
2416 }
2417
2418 @Override
2419 protected int getDefaultTabSize() {
2420 return 8;
2421 }
2422
2423 @Override
2424 protected Locale getDefaultLocale() {
2425 return Locale.getDefault();
2426 }
2427
2428 @Override
2429 protected TimeZone getDefaultTimeZone() {
2430 return TimeZone.getDefault();
2431 }
2432
2433 @Override
2434 protected TimeZone getDefaultSQLDateAndTimeTimeZone() {
2435 return TimeZone.getDefault();
2436 }
2437
2438 @Override
2439 protected String getDefaultNumberFormat() {
2440 return "number";
2441 }
2442
2443 @Override
2444 protected Map<String, TemplateNumberFormatFactory> getDefaultCustomNumberFormats() {
2445 return Collections.emptyMap();
2446 }
2447
2448 /**
2449 * {@inheritDoc}
2450 *
2451 * @see #getImpliedCustomNumberFormats()
2452 */
2453 @Override
2454 protected TemplateNumberFormatFactory getDefaultCustomNumberFormat(String name) {
2455 return null;
2456 }
2457
2458 /**
2459 * The custom number formats that will be added to the built {@link Configuration} before the ones coming from
2460 * {@link #getCustomNumberFormats()}. When overriding this method, always consider adding to the return
2461 * value of the super method, rather than replacing it.
2462 *
2463 * @return Immutable {@link Map}; not {@code null}
2464 */
2465 protected Map<String, TemplateNumberFormatFactory> getImpliedCustomNumberFormats() {
2466 return Collections.emptyMap();
2467 }
2468
2469 @Override
2470 protected String getDefaultBooleanFormat() {
2471 return TemplateBooleanFormat.C_TRUE_FALSE;
2472 }
2473
2474 @Override
2475 protected String getDefaultTimeFormat() {
2476 return "";
2477 }
2478
2479 @Override
2480 protected String getDefaultDateFormat() {
2481 return "";
2482 }
2483
2484 @Override
2485 protected String getDefaultDateTimeFormat() {
2486 return "";
2487 }
2488
2489 /**
2490 * {@inheritDoc}
2491 *
2492 * @see #getImpliedCustomDateFormats()
2493 */
2494 @Override
2495 protected Map<String, TemplateDateFormatFactory> getDefaultCustomDateFormats() {
2496 return Collections.emptyMap();
2497 }
2498
2499 /**
2500 * The custom date formats that will be added to the built {@link Configuration} before the ones coming from
2501 * {@link #getCustomDateFormats()}. When overriding this method, always consider adding to the return value
2502 * of the super method, rather than replacing it.
2503 *
2504 * @return Immutable {@link Map}; not {@code null}
2505 */
2506 protected Map<String, TemplateDateFormatFactory> getImpliedCustomDateFormats() {
2507 return Collections.emptyMap();
2508 }
2509
2510 @Override
2511 protected TemplateDateFormatFactory getDefaultCustomDateFormat(String name) {
2512 return null;
2513 }
2514
2515 @Override
2516 protected TemplateExceptionHandler getDefaultTemplateExceptionHandler() {
2517 return TemplateExceptionHandler.RETHROW;
2518 }
2519
2520 @Override
2521 protected AttemptExceptionReporter getDefaultAttemptExceptionReporter() {
2522 return AttemptExceptionReporter.LOG_ERROR;
2523 }
2524
2525 @Override
2526 protected ArithmeticEngine getDefaultArithmeticEngine() {
2527 return BigDecimalArithmeticEngine.INSTANCE;
2528 }
2529
2530 private DefaultObjectWrapper cachedDefaultObjectWrapper;
2531
2532 /**
2533 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
2534 * {@link ProcessingConfiguration}).
2535 */
2536 public void unsetObjectWrapper() {
2537 objectWrapper = null;
2538 }
2539
2540 /**
2541 * Fluent API equivalent of {@link #setObjectWrapper(ObjectWrapper)}
2542 */
2543 public SelfT objectWrapper(ObjectWrapper value) {
2544 setObjectWrapper(value);
2545 return self();
2546 }
2547
2548 @Override
2549 public ObjectWrapper getObjectWrapper() {
2550 return isObjectWrapperSet() ? objectWrapper : getDefaultObjectWrapper();
2551 }
2552
2553 @Override
2554 public boolean isObjectWrapperSet() {
2555 return objectWrapper != null;
2556 }
2557
2558 /**
2559 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
2560 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
2561 */
2562 protected ObjectWrapper getDefaultObjectWrapper() {
2563 if (cachedDefaultObjectWrapper == null) {
2564 // Note: This field is cleared by onIncompatibleImprovementsChanged
2565 Version incompatibleImprovements = getIncompatibleImprovements();
2566 cachedDefaultObjectWrapper = new DefaultObjectWrapper.Builder(incompatibleImprovements).build();
2567 }
2568 return cachedDefaultObjectWrapper;
2569 }
2570
2571 public void setObjectWrapper(ObjectWrapper objectWrapper) {
2572 _NullArgumentException.check("objectWrapper", objectWrapper);
2573 this.objectWrapper = objectWrapper;
2574 if (objectWrapper != cachedDefaultObjectWrapper) {
2575 // Just to make it GC-able
2576 cachedDefaultObjectWrapper = null;
2577 }
2578 }
2579
2580 @Override
2581 protected Charset getDefaultOutputEncoding() {
2582 return null;
2583 }
2584
2585 @Override
2586 protected Charset getDefaultURLEscapingCharset() {
2587 return null;
2588 }
2589
2590 @Override
2591 protected TemplateClassResolver getDefaultNewBuiltinClassResolver() {
2592 return TemplateClassResolver.UNRESTRICTED;
2593 }
2594
2595 @Override
2596 protected boolean getDefaultAutoFlush() {
2597 return true;
2598 }
2599
2600 @Override
2601 protected boolean getDefaultShowErrorTips() {
2602 return true;
2603 }
2604
2605 @Override
2606 protected boolean getDefaultAPIBuiltinEnabled() {
2607 return false;
2608 }
2609
2610 @Override
2611 protected boolean getDefaultLazyImports() {
2612 return false;
2613 }
2614
2615 @Override
2616 @SuppressFBWarnings("NP_BOOLEAN_RETURN_NULL")
2617 protected Boolean getDefaultLazyAutoImports() {
2618 return null;
2619 }
2620
2621 /**
2622 * {@inheritDoc}
2623 *
2624 * @see #getImpliedAutoImports()
2625 */
2626 @Override
2627 protected Map<String, String> getDefaultAutoImports() {
2628 return Collections.emptyMap();
2629 }
2630
2631 /**
2632 * The auto-imports that will be added to the built {@link Configuration} before the ones coming from
2633 * {@link #getSharedVariables()}. When overriding this method, always consider adding to the return value
2634 * of the super method, rather than replacing it.
2635 */
2636 protected Map<String, String> getImpliedAutoImports() {
2637 return Collections.emptyMap();
2638 }
2639
2640 /**
2641 * {@inheritDoc}
2642 *
2643 * @see #getImpliedAutoIncludes()
2644 */
2645 @Override
2646 protected List<String> getDefaultAutoIncludes() {
2647 return Collections.emptyList();
2648 }
2649
2650 /**
2651 * The imports that will be added to the built {@link Configuration} before the ones coming from
2652 * {@link #getAutoIncludes()}. When overriding this method, always consider adding to the return
2653 * value of the super method, rather than replacing it.
2654 *
2655 * @return Immutable {@link List}; not {@code null}
2656 */
2657 protected List<String> getImpliedAutoIncludes() {
2658 return Collections.emptyList();
2659 }
2660
2661 @Override
2662 protected Object getDefaultCustomSetting(Serializable key, Object defaultValue, boolean useDefaultValue) {
2663 if (useDefaultValue) {
2664 return defaultValue;
2665 }
2666 throw new CustomSettingValueNotSetException(key);
2667 }
2668
2669 @Override
2670 protected void collectDefaultCustomSettingsSnapshot(Map<Serializable, Object> target) {
2671 // Doesn't inherit anything
2672 }
2673 }
2674
2675 /**
2676 * Creates a new {@link Configuration}, with the setting specified in this object. Note that {@link Configuration}-s
2677 * are immutable (since FreeMarker 3.0.0), that's why the builder class is needed.
2678 */
2679 public static final class Builder extends ExtendableBuilder<Builder> {
2680
2681 /**
2682 * @param incompatibleImprovements
2683 * Specifies the value of the {@link Configuration#getIncompatibleImprovements()}
2684 * incompatibleImprovements} setting, such as {@link Configuration#VERSION_3_0_0}. This setting can't be
2685 * changed later.
2686 */
2687 public Builder(Version incompatibleImprovements) {
2688 super(incompatibleImprovements);
2689 }
2690
2691 }
2692
2693 }