Continued work on TemplateLanguage configuration and new file extensions: Renamed...
[freemarker.git] / freemarker-core / src / main / java / org / apache / freemarker / core / MutableProcessingConfiguration.java
1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 package org.apache.freemarker.core;
21
22 import java.io.Serializable;
23 import java.nio.charset.Charset;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.LinkedHashMap;
31 import java.util.LinkedHashSet;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Properties;
36 import java.util.Set;
37 import java.util.TimeZone;
38
39 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
40 import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
41 import org.apache.freemarker.core.arithmetic.impl.ConservativeArithmeticEngine;
42 import org.apache.freemarker.core.model.ObjectWrapper;
43 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
44 import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
45 import org.apache.freemarker.core.outputformat.OutputFormat;
46 import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
47 import org.apache.freemarker.core.outputformat.impl.PlainTextOutputFormat;
48 import org.apache.freemarker.core.outputformat.impl.RTFOutputFormat;
49 import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
50 import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
51 import org.apache.freemarker.core.templateresolver.AndMatcher;
52 import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
53 import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
54 import org.apache.freemarker.core.templateresolver.FirstMatchTemplateConfigurationFactory;
55 import org.apache.freemarker.core.templateresolver.MergingTemplateConfigurationFactory;
56 import org.apache.freemarker.core.templateresolver.NotMatcher;
57 import org.apache.freemarker.core.templateresolver.OrMatcher;
58 import org.apache.freemarker.core.templateresolver.PathGlobMatcher;
59 import org.apache.freemarker.core.templateresolver.PathRegexMatcher;
60 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
61 import org.apache.freemarker.core.util.GenericParseException;
62 import org.apache.freemarker.core.util.OptInTemplateClassResolver;
63 import org.apache.freemarker.core.util.TemplateLanguageUtils;
64 import org.apache.freemarker.core.util._ClassUtils;
65 import org.apache.freemarker.core.util._CollectionUtils;
66 import org.apache.freemarker.core.util._KeyValuePair;
67 import org.apache.freemarker.core.util._NullArgumentException;
68 import org.apache.freemarker.core.util._SortedArraySet;
69 import org.apache.freemarker.core.util._StringUtils;
70 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
71 import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
72
73 /**
74 * Extended by FreeMarker core classes (not by you) that support specifying {@link ProcessingConfiguration} setting
75 * values. <b>New abstract methods may be added any time in future FreeMarker versions, so don't try to implement this
76 * interface yourself!</b>
77 */
78 public abstract class MutableProcessingConfiguration<SelfT extends MutableProcessingConfiguration<SelfT>>
79 implements ProcessingConfiguration {
80 public static final String NULL_VALUE = "null";
81 public static final String DEFAULT_VALUE = "default";
82 public static final String JVM_DEFAULT_VALUE = "JVM default";
83
84 public static final String LOCALE_KEY = "locale";
85 public static final String NUMBER_FORMAT_KEY = "numberFormat";
86 public static final String CUSTOM_NUMBER_FORMATS_KEY = "customNumberFormats";
87 public static final String TIME_FORMAT_KEY = "timeFormat";
88 public static final String DATE_FORMAT_KEY = "dateFormat";
89 public static final String DATE_TIME_FORMAT_KEY = "dateTimeFormat";
90 public static final String CUSTOM_DATE_FORMATS_KEY = "customDateFormats";
91 public static final String TIME_ZONE_KEY = "timeZone";
92 public static final String SQL_DATE_AND_TIME_TIME_ZONE_KEY = "sqlDateAndTimeTimeZone";
93 public static final String TEMPLATE_EXCEPTION_HANDLER_KEY = "templateExceptionHandler";
94 public static final String ATTEMPT_EXCEPTION_REPORTER_KEY = "attemptExceptionReporter";
95 public static final String ARITHMETIC_ENGINE_KEY = "arithmeticEngine";
96 public static final String BOOLEAN_FORMAT_KEY = "booleanFormat";
97 public static final String OUTPUT_ENCODING_KEY = "outputEncoding";
98 public static final String URL_ESCAPING_CHARSET_KEY = "urlEscapingCharset";
99 public static final String AUTO_FLUSH_KEY = "autoFlush";
100 public static final String NEW_BUILTIN_CLASS_RESOLVER_KEY = "newBuiltinClassResolver";
101 public static final String SHOW_ERROR_TIPS_KEY = "showErrorTips";
102 public static final String API_BUILTIN_ENABLED_KEY = "apiBuiltinEnabled";
103 public static final String LAZY_IMPORTS_KEY = "lazyImports";
104 public static final String LAZY_AUTO_IMPORTS_KEY = "lazyAutoImports";
105 public static final String AUTO_IMPORTS_KEY = "autoImports";
106 public static final String AUTO_INCLUDES_KEY = "autoIncludes";
107
108 private static final Set<String> SETTING_NAMES = new _SortedArraySet<>(
109 // Must be sorted alphabetically!
110 API_BUILTIN_ENABLED_KEY,
111 ARITHMETIC_ENGINE_KEY,
112 ATTEMPT_EXCEPTION_REPORTER_KEY,
113 AUTO_FLUSH_KEY,
114 AUTO_IMPORTS_KEY,
115 AUTO_INCLUDES_KEY,
116 BOOLEAN_FORMAT_KEY,
117 CUSTOM_DATE_FORMATS_KEY,
118 CUSTOM_NUMBER_FORMATS_KEY,
119 DATE_FORMAT_KEY,
120 DATE_TIME_FORMAT_KEY,
121 LAZY_AUTO_IMPORTS_KEY,
122 LAZY_IMPORTS_KEY,
123 LOCALE_KEY,
124 NEW_BUILTIN_CLASS_RESOLVER_KEY,
125 NUMBER_FORMAT_KEY,
126 OUTPUT_ENCODING_KEY,
127 SHOW_ERROR_TIPS_KEY,
128 SQL_DATE_AND_TIME_TIME_ZONE_KEY,
129 TEMPLATE_EXCEPTION_HANDLER_KEY,
130 TIME_FORMAT_KEY,
131 TIME_ZONE_KEY,
132 URL_ESCAPING_CHARSET_KEY
133 );
134
135 private Locale locale;
136 private String numberFormat;
137 private String timeFormat;
138 private String dateFormat;
139 private String dateTimeFormat;
140 private TimeZone timeZone;
141 private TimeZone sqlDateAndTimeTimeZone;
142 private boolean sqlDateAndTimeTimeZoneSet;
143 private String booleanFormat;
144 private TemplateExceptionHandler templateExceptionHandler;
145 private AttemptExceptionReporter attemptExceptionReporter;
146 private ArithmeticEngine arithmeticEngine;
147 private Charset outputEncoding;
148 private boolean outputEncodingSet;
149 private Charset urlEscapingCharset;
150 private boolean urlEscapingCharsetSet;
151 private Boolean autoFlush;
152 private TemplateClassResolver newBuiltinClassResolver;
153 private Boolean showErrorTips;
154 private Boolean apiBuiltinEnabled;
155 private Map<String, TemplateDateFormatFactory> customDateFormats;
156 private Map<String, TemplateNumberFormatFactory> customNumberFormats;
157 private Map<String, String> autoImports;
158 private List<String> autoIncludes;
159 private Boolean lazyImports;
160 private Boolean lazyAutoImports;
161 private boolean lazyAutoImportsSet;
162 private Map<Serializable, Object> customSettings = Collections.emptyMap();
163 /** If {@code false}, we must use copy-on-write behavior for {@link #customSettings}. */
164 private boolean customSettingsModifiable;
165
166 /**
167 * Creates a new instance. Normally you do not need to use this constructor,
168 * as you don't use <code>MutableProcessingConfiguration</code> directly, but its subclasses.
169 */
170 protected MutableProcessingConfiguration() {
171 // Empty
172 }
173
174 @Override
175 public Locale getLocale() {
176 return isLocaleSet() ? locale : getDefaultLocale();
177 }
178
179 /**
180 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
181 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
182 */
183 protected abstract Locale getDefaultLocale();
184
185 @Override
186 public boolean isLocaleSet() {
187 return locale != null;
188 }
189
190 /**
191 * Setter pair of {@link ProcessingConfiguration#getLocale()}.
192 */
193 public void setLocale(Locale locale) {
194 _NullArgumentException.check("locale", locale);
195 this.locale = locale;
196 }
197
198 /**
199 * Fluent API equivalent of {@link #setLocale(Locale)}
200 */
201 public SelfT locale(Locale value) {
202 setLocale(value);
203 return self();
204 }
205
206 /**
207 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
208 * {@link ProcessingConfiguration}).
209 */
210 public void unsetLocale() {
211 locale = null;
212 }
213
214 /**
215 * Setter pair of {@link ProcessingConfiguration#getTimeZone()}.
216 */
217 public void setTimeZone(TimeZone timeZone) {
218 _NullArgumentException.check("timeZone", timeZone);
219 this.timeZone = timeZone;
220 }
221
222 /**
223 * Fluent API equivalent of {@link #setTimeZone(TimeZone)}
224 */
225 public SelfT timeZone(TimeZone value) {
226 setTimeZone(value);
227 return self();
228 }
229 /**
230 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
231 * {@link ProcessingConfiguration}).
232 */
233 public void unsetTimeZone() {
234 this.timeZone = null;
235 }
236
237 @Override
238 public TimeZone getTimeZone() {
239 return isTimeZoneSet() ? timeZone : getDefaultTimeZone();
240 }
241
242 /**
243 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
244 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
245 */
246 protected abstract TimeZone getDefaultTimeZone();
247
248 @Override
249 public boolean isTimeZoneSet() {
250 return timeZone != null;
251 }
252
253 /**
254 * Setter pair of {@link ProcessingConfiguration#getSQLDateAndTimeTimeZone()}.
255 */
256 public void setSQLDateAndTimeTimeZone(TimeZone tz) {
257 sqlDateAndTimeTimeZone = tz;
258 sqlDateAndTimeTimeZoneSet = true;
259 }
260
261 /**
262 * Fluent API equivalent of {@link #setSQLDateAndTimeTimeZone(TimeZone)}
263 */
264 public SelfT sqlDateAndTimeTimeZone(TimeZone value) {
265 setSQLDateAndTimeTimeZone(value);
266 return self();
267 }
268
269 /**
270 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
271 * {@link ProcessingConfiguration}).
272 */
273 public void unsetSQLDateAndTimeTimeZone() {
274 sqlDateAndTimeTimeZone = null;
275 sqlDateAndTimeTimeZoneSet = false;
276 }
277
278 @Override
279 public TimeZone getSQLDateAndTimeTimeZone() {
280 return sqlDateAndTimeTimeZoneSet
281 ? sqlDateAndTimeTimeZone
282 : getDefaultSQLDateAndTimeTimeZone();
283 }
284
285 /**
286 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
287 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
288 */
289 protected abstract TimeZone getDefaultSQLDateAndTimeTimeZone();
290
291 @Override
292 public boolean isSQLDateAndTimeTimeZoneSet() {
293 return sqlDateAndTimeTimeZoneSet;
294 }
295
296 /**
297 * Setter pair of {@link #getNumberFormat()}
298 */
299 public void setNumberFormat(String numberFormat) {
300 _NullArgumentException.check("numberFormat", numberFormat);
301 this.numberFormat = numberFormat;
302 }
303
304 /**
305 * Fluent API equivalent of {@link #setNumberFormat(String)}
306 */
307 public SelfT numberFormat(String value) {
308 setNumberFormat(value);
309 return self();
310 }
311
312 /**
313 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
314 * {@link ProcessingConfiguration}).
315 */
316 public void unsetNumberFormat() {
317 numberFormat = null;
318 }
319
320 @Override
321 public String getNumberFormat() {
322 return isNumberFormatSet() ? numberFormat : getDefaultNumberFormat();
323 }
324
325 /**
326 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
327 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
328 */
329 protected abstract String getDefaultNumberFormat();
330
331 @Override
332 public boolean isNumberFormatSet() {
333 return numberFormat != null;
334 }
335
336 @Override
337 public Map<String, TemplateNumberFormatFactory> getCustomNumberFormats() {
338 return isCustomNumberFormatsSet() ? customNumberFormats : getDefaultCustomNumberFormats();
339 }
340
341 protected abstract Map<String, TemplateNumberFormatFactory> getDefaultCustomNumberFormats();
342
343 /**
344 * Setter pair of {@link #getCustomNumberFormats()}. Note that custom number formats are get through
345 * {@link #getCustomNumberFormat(String)}, not directly though this {@link Map}, so number formats from
346 * {@link ProcessingConfiguration}-s on less specific levels are inherited without being present in this
347 * {@link Map}.
348 *
349 * @param customNumberFormats
350 * Not {@code null}; will be copied (to prevent aliasing effect); keys must conform to format name
351 * syntactical restrictions (see in {@link #getCustomNumberFormats()})
352 */
353 public void setCustomNumberFormats(Map<String, ? extends TemplateNumberFormatFactory> customNumberFormats) {
354 setCustomNumberFormats(customNumberFormats, false);
355 }
356
357 /**
358 * @param validatedImmutableUnchanging
359 * {@code true} if we know that the 1st argument is already validated, immutable, and unchanging (means,
360 * won't change later because of aliasing).
361 */
362 @SuppressWarnings({ "unchecked", "rawtypes" })
363 void setCustomNumberFormats(Map<String, ? extends TemplateNumberFormatFactory> customNumberFormats,
364 boolean validatedImmutableUnchanging) {
365 _NullArgumentException.check("customNumberFormats", customNumberFormats);
366 if (!validatedImmutableUnchanging) {
367 if (customNumberFormats == this.customNumberFormats) {
368 return;
369 }
370 _CollectionUtils.safeCastMap("customNumberFormats", customNumberFormats,
371 String.class, false,
372 TemplateNumberFormatFactory.class, false);
373 validateFormatNames(customNumberFormats.keySet());
374 this.customNumberFormats = Collections.unmodifiableMap(new HashMap<>(customNumberFormats));
375 } else {
376 this.customNumberFormats = (Map) customNumberFormats;
377 }
378 }
379
380 /**
381 * Fluent API equivalent of {@link #setCustomNumberFormats(Map)}
382 */
383 public SelfT customNumberFormats(Map<String, ? extends TemplateNumberFormatFactory> value) {
384 setCustomNumberFormats(value);
385 return self();
386 }
387
388 /**
389 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
390 * {@link ProcessingConfiguration}).
391 */
392 public void unsetCustomNumberFormats() {
393 customNumberFormats = null;
394 }
395
396 private void validateFormatNames(Set<String> keySet) {
397 for (String name : keySet) {
398 if (name.length() == 0) {
399 throw new IllegalArgumentException("Format names can't be 0 length");
400 }
401 char firstChar = name.charAt(0);
402 if (firstChar == '@') {
403 throw new IllegalArgumentException(
404 "Format names can't start with '@'. '@' is only used when referring to them from format "
405 + "strings. In: " + name);
406 }
407 if (!Character.isLetter(firstChar)) {
408 throw new IllegalArgumentException("Format name must start with letter: " + name);
409 }
410 for (int i = 1; i < name.length(); i++) {
411 // Note that we deliberately don't allow "_" here.
412 if (!Character.isLetterOrDigit(name.charAt(i))) {
413 throw new IllegalArgumentException("Format name can only contain letters and digits: " + name);
414 }
415 }
416 }
417 }
418
419 @Override
420 public boolean isCustomNumberFormatsSet() {
421 return customNumberFormats != null;
422 }
423
424 @Override
425 public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
426 TemplateNumberFormatFactory r;
427 if (customNumberFormats != null) {
428 r = customNumberFormats.get(name);
429 if (r != null) {
430 return r;
431 }
432 }
433 return getDefaultCustomNumberFormat(name);
434 }
435
436 protected abstract TemplateNumberFormatFactory getDefaultCustomNumberFormat(String name);
437
438 /**
439 * Setter pair of {@link #getBooleanFormat()}.
440 */
441 public void setBooleanFormat(String booleanFormat) {
442 _NullArgumentException.check("booleanFormat", booleanFormat);
443
444 int commaIdx = booleanFormat.indexOf(',');
445 if (commaIdx == -1) {
446 throw new IllegalArgumentException(
447 "Setting value must be a string that contains two comma-separated values for true and false, " +
448 "respectively.");
449 }
450
451 this.booleanFormat = booleanFormat;
452 }
453
454 /**
455 * Fluent API equivalent of {@link #setBooleanFormat(String)}
456 */
457 public SelfT booleanFormat(String value) {
458 setBooleanFormat(value);
459 return self();
460 }
461
462 /**
463 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
464 * {@link ProcessingConfiguration}).
465 */
466 public void unsetBooleanFormat() {
467 booleanFormat = null;
468 }
469
470 @Override
471 public String getBooleanFormat() {
472 return isBooleanFormatSet() ? booleanFormat : getDefaultBooleanFormat();
473 }
474
475 /**
476 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
477 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
478 */
479 protected abstract String getDefaultBooleanFormat();
480
481 @Override
482 public boolean isBooleanFormatSet() {
483 return booleanFormat != null;
484 }
485
486 /**
487 * Setter pair of {@link #getTimeFormat()}
488 */
489 public void setTimeFormat(String timeFormat) {
490 _NullArgumentException.check("timeFormat", timeFormat);
491 this.timeFormat = timeFormat;
492 }
493
494 /**
495 * Fluent API equivalent of {@link #setTimeFormat(String)}
496 */
497 public SelfT timeFormat(String value) {
498 setTimeFormat(value);
499 return self();
500 }
501
502 /**
503 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
504 * {@link ProcessingConfiguration}).
505 */
506 public void unsetTimeFormat() {
507 timeFormat = null;
508 }
509
510 @Override
511 public String getTimeFormat() {
512 return isTimeFormatSet() ? timeFormat : getDefaultTimeFormat();
513 }
514
515 /**
516 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
517 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
518 */
519 protected abstract String getDefaultTimeFormat();
520
521 @Override
522 public boolean isTimeFormatSet() {
523 return timeFormat != null;
524 }
525
526 /**
527 * Setter pair of {@link #getDateFormat()}.
528 */
529 public void setDateFormat(String dateFormat) {
530 _NullArgumentException.check("dateFormat", dateFormat);
531 this.dateFormat = dateFormat;
532 }
533
534 /**
535 * Fluent API equivalent of {@link #setDateFormat(String)}
536 */
537 public SelfT dateFormat(String value) {
538 setDateFormat(value);
539 return self();
540 }
541
542 /**
543 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
544 * {@link ProcessingConfiguration}).
545 */
546 public void unsetDateFormat() {
547 dateFormat = null;
548 }
549
550 @Override
551 public String getDateFormat() {
552 return isDateFormatSet() ? dateFormat : getDefaultDateFormat();
553 }
554
555 /**
556 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
557 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
558 */
559 protected abstract String getDefaultDateFormat();
560
561 @Override
562 public boolean isDateFormatSet() {
563 return dateFormat != null;
564 }
565
566 /**
567 * Setter pair of {@link #getDateTimeFormat()}
568 */
569 public void setDateTimeFormat(String dateTimeFormat) {
570 _NullArgumentException.check("dateTimeFormat", dateTimeFormat);
571 this.dateTimeFormat = dateTimeFormat;
572 }
573
574 /**
575 * Fluent API equivalent of {@link #setDateTimeFormat(String)}
576 */
577 public SelfT dateTimeFormat(String value) {
578 setDateTimeFormat(value);
579 return self();
580 }
581
582 /**
583 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
584 * {@link ProcessingConfiguration}).
585 */
586 public void unsetDateTimeFormat() {
587 this.dateTimeFormat = null;
588 }
589
590 @Override
591 public String getDateTimeFormat() {
592 return isDateTimeFormatSet() ? dateTimeFormat : getDefaultDateTimeFormat();
593 }
594
595 /**
596 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
597 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
598 */
599 protected abstract String getDefaultDateTimeFormat();
600
601 @Override
602 public boolean isDateTimeFormatSet() {
603 return dateTimeFormat != null;
604 }
605
606 @Override
607 public Map<String, TemplateDateFormatFactory> getCustomDateFormats() {
608 return isCustomDateFormatsSet() ? customDateFormats : getDefaultCustomDateFormats();
609 }
610
611 protected abstract Map<String, TemplateDateFormatFactory> getDefaultCustomDateFormats();
612
613 /**
614 * Setter pair of {@link #getCustomDateFormat(String)}. Note that custom date formats are get through
615 * {@link #getCustomNumberFormat(String)}, not directly though this {@link Map}, so date formats from
616 * {@link ProcessingConfiguration}-s on less specific levels are inherited without being present in this
617 * {@link Map}.
618 *
619 * @param customDateFormats
620 * Not {@code null}; will be copied (to prevent aliasing effect); keys must conform to format name
621 * syntactical restrictions (see in {@link #getCustomDateFormats()})
622 */
623 public void setCustomDateFormats(Map<String, ? extends TemplateDateFormatFactory> customDateFormats) {
624 setCustomDateFormats(customDateFormats, false);
625 }
626
627 /**
628 * @param validatedImmutableUnchanging
629 * {@code true} if we know that the 1st argument is already validated, immutable, and unchanging (means,
630 * won't change later because of aliasing).
631 */
632 @SuppressWarnings({ "unchecked", "rawtypes" })
633 void setCustomDateFormats(
634 Map<String, ? extends TemplateDateFormatFactory> customDateFormats,
635 boolean validatedImmutableUnchanging) {
636 _NullArgumentException.check("customDateFormats", customDateFormats);
637 if (!validatedImmutableUnchanging) {
638 if (customDateFormats == this.customDateFormats) {
639 return;
640 }
641 _CollectionUtils.safeCastMap("customDateFormats", customDateFormats,
642 String.class, false,
643 TemplateDateFormatFactory.class, false);
644 validateFormatNames(customDateFormats.keySet());
645 this.customDateFormats = Collections.unmodifiableMap(new HashMap<>(customDateFormats));
646 } else {
647 this.customDateFormats = (Map) customDateFormats;
648 }
649 }
650
651 /**
652 * Fluent API equivalent of {@link #setCustomDateFormats(Map)}
653 */
654 public SelfT customDateFormats(Map<String, ? extends TemplateDateFormatFactory> value) {
655 setCustomDateFormats(value);
656 return self();
657 }
658
659 /**
660 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
661 * {@link ProcessingConfiguration}).
662 */
663 public void unsetCustomDateFormats() {
664 this.customDateFormats = null;
665 }
666
667 @Override
668 public boolean isCustomDateFormatsSet() {
669 return customDateFormats != null;
670 }
671
672 @Override
673 public TemplateDateFormatFactory getCustomDateFormat(String name) {
674 TemplateDateFormatFactory r;
675 if (customDateFormats != null) {
676 r = customDateFormats.get(name);
677 if (r != null) {
678 return r;
679 }
680 }
681 return getDefaultCustomDateFormat(name);
682 }
683
684 protected abstract TemplateDateFormatFactory getDefaultCustomDateFormat(String name);
685
686 /**
687 * Setter pair of {@link #getTemplateExceptionHandler()}
688 */
689 public void setTemplateExceptionHandler(TemplateExceptionHandler templateExceptionHandler) {
690 _NullArgumentException.check("templateExceptionHandler", templateExceptionHandler);
691 this.templateExceptionHandler = templateExceptionHandler;
692 }
693
694 /**
695 * Fluent API equivalent of {@link #setTemplateExceptionHandler(TemplateExceptionHandler)}
696 */
697 public SelfT templateExceptionHandler(TemplateExceptionHandler value) {
698 setTemplateExceptionHandler(value);
699 return self();
700 }
701
702 /**
703 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
704 * {@link ProcessingConfiguration}).
705 */
706 public void unsetTemplateExceptionHandler() {
707 templateExceptionHandler = null;
708 }
709
710 @Override
711 public TemplateExceptionHandler getTemplateExceptionHandler() {
712 return isTemplateExceptionHandlerSet()
713 ? templateExceptionHandler : getDefaultTemplateExceptionHandler();
714 }
715
716 /**
717 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
718 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
719 */
720 protected abstract TemplateExceptionHandler getDefaultTemplateExceptionHandler();
721
722 @Override
723 public boolean isTemplateExceptionHandlerSet() {
724 return templateExceptionHandler != null;
725 }
726
727 /**
728 * Setter pair of {@link #getAttemptExceptionReporter()}
729 */
730 public void setAttemptExceptionReporter(AttemptExceptionReporter attemptExceptionReporter) {
731 _NullArgumentException.check("attemptExceptionReporter", attemptExceptionReporter);
732 this.attemptExceptionReporter = attemptExceptionReporter;
733 }
734
735 /**
736 * Fluent API equivalent of {@link #setAttemptExceptionReporter(AttemptExceptionReporter)}
737 */
738 public SelfT attemptExceptionReporter(AttemptExceptionReporter value) {
739 setAttemptExceptionReporter(value);
740 return self();
741 }
742
743 /**
744 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
745 * {@link ProcessingConfiguration}).
746 */
747 public void unsetAttemptExceptionReporter() {
748 attemptExceptionReporter = null;
749 }
750
751 @Override
752 public AttemptExceptionReporter getAttemptExceptionReporter() {
753 return isAttemptExceptionReporterSet()
754 ? attemptExceptionReporter : getDefaultAttemptExceptionReporter();
755 }
756
757 /**
758 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
759 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
760 */
761 protected abstract AttemptExceptionReporter getDefaultAttemptExceptionReporter();
762
763 @Override
764 public boolean isAttemptExceptionReporterSet() {
765 return attemptExceptionReporter != null;
766 }
767
768 /**
769 * Setter pair of {@link #getArithmeticEngine()}
770 */
771 public void setArithmeticEngine(ArithmeticEngine arithmeticEngine) {
772 _NullArgumentException.check("arithmeticEngine", arithmeticEngine);
773 this.arithmeticEngine = arithmeticEngine;
774 }
775
776 /**
777 * Fluent API equivalent of {@link #setArithmeticEngine(ArithmeticEngine)}
778 */
779 public SelfT arithmeticEngine(ArithmeticEngine value) {
780 setArithmeticEngine(value);
781 return self();
782 }
783
784 /**
785 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
786 * {@link ProcessingConfiguration}).
787 */
788 public void unsetArithmeticEngine() {
789 this.arithmeticEngine = null;
790 }
791
792 @Override
793 public ArithmeticEngine getArithmeticEngine() {
794 return isArithmeticEngineSet() ? arithmeticEngine : getDefaultArithmeticEngine();
795 }
796
797 /**
798 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
799 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
800 */
801 protected abstract ArithmeticEngine getDefaultArithmeticEngine();
802
803 @Override
804 public boolean isArithmeticEngineSet() {
805 return arithmeticEngine != null;
806 }
807
808 /**
809 * The setter pair of {@link #getOutputEncoding()}
810 */
811 public void setOutputEncoding(Charset outputEncoding) {
812 this.outputEncoding = outputEncoding;
813 outputEncodingSet = true;
814 }
815
816 /**
817 * Fluent API equivalent of {@link #setOutputEncoding(Charset)}
818 */
819 public SelfT outputEncoding(Charset value) {
820 setOutputEncoding(value);
821 return self();
822 }
823
824 /**
825 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
826 * {@link ProcessingConfiguration}).
827 */
828 public void unsetOutputEncoding() {
829 this.outputEncoding = null;
830 outputEncodingSet = false;
831 }
832
833 @Override
834 public Charset getOutputEncoding() {
835 return isOutputEncodingSet()
836 ? outputEncoding
837 : getDefaultOutputEncoding();
838 }
839
840 /**
841 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
842 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
843 */
844 protected abstract Charset getDefaultOutputEncoding();
845
846 @Override
847 public boolean isOutputEncodingSet() {
848 return outputEncodingSet;
849 }
850
851 /**
852 * The setter pair of {@link #getURLEscapingCharset()}.
853 */
854 public void setURLEscapingCharset(Charset urlEscapingCharset) {
855 this.urlEscapingCharset = urlEscapingCharset;
856 urlEscapingCharsetSet = true;
857 }
858
859 /**
860 * Fluent API equivalent of {@link #setURLEscapingCharset(Charset)}
861 */
862 public SelfT urlEscapingCharset(Charset value) {
863 setURLEscapingCharset(value);
864 return self();
865 }
866
867 /**
868 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
869 * {@link ProcessingConfiguration}).
870 */
871 public void unsetURLEscapingCharset() {
872 this.urlEscapingCharset = null;
873 urlEscapingCharsetSet = false;
874 }
875
876 @Override
877 public Charset getURLEscapingCharset() {
878 return isURLEscapingCharsetSet() ? urlEscapingCharset : getDefaultURLEscapingCharset();
879 }
880
881 /**
882 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
883 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
884 */
885 protected abstract Charset getDefaultURLEscapingCharset();
886
887 @Override
888 public boolean isURLEscapingCharsetSet() {
889 return urlEscapingCharsetSet;
890 }
891
892 /**
893 * Setter pair of {@link #getNewBuiltinClassResolver()}
894 */
895 public void setNewBuiltinClassResolver(TemplateClassResolver newBuiltinClassResolver) {
896 _NullArgumentException.check("newBuiltinClassResolver", newBuiltinClassResolver);
897 this.newBuiltinClassResolver = newBuiltinClassResolver;
898 }
899
900 /**
901 * Fluent API equivalent of {@link #setNewBuiltinClassResolver(TemplateClassResolver)}
902 */
903 public SelfT newBuiltinClassResolver(TemplateClassResolver value) {
904 setNewBuiltinClassResolver(value);
905 return self();
906 }
907
908 /**
909 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
910 * {@link ProcessingConfiguration}).
911 */
912 public void unsetNewBuiltinClassResolver() {
913 this.newBuiltinClassResolver = null;
914 }
915
916 @Override
917 public TemplateClassResolver getNewBuiltinClassResolver() {
918 return isNewBuiltinClassResolverSet()
919 ? newBuiltinClassResolver : getDefaultNewBuiltinClassResolver();
920 }
921
922 /**
923 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
924 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
925 */
926 protected abstract TemplateClassResolver getDefaultNewBuiltinClassResolver();
927
928 @Override
929 public boolean isNewBuiltinClassResolverSet() {
930 return newBuiltinClassResolver != null;
931 }
932
933 /**
934 * Setter pair of {@link #getAutoFlush()}
935 */
936 public void setAutoFlush(boolean autoFlush) {
937 this.autoFlush = autoFlush;
938 }
939
940 /**
941 * Fluent API equivalent of {@link #setAutoFlush(boolean)}
942 */
943 public SelfT autoFlush(boolean value) {
944 setAutoFlush(value);
945 return self();
946 }
947
948 /**
949 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
950 * {@link ProcessingConfiguration}).
951 */
952 public void unsetAutoFlush() {
953 this.autoFlush = null;
954 }
955
956 @Override
957 public boolean getAutoFlush() {
958 return isAutoFlushSet() ? autoFlush : getDefaultAutoFlush();
959 }
960
961 /**
962 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
963 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
964 */
965 protected abstract boolean getDefaultAutoFlush();
966
967 @Override
968 public boolean isAutoFlushSet() {
969 return autoFlush != null;
970 }
971
972 /**
973 * Setter pair of {@link #getShowErrorTips()}
974 */
975 public void setShowErrorTips(boolean showTips) {
976 showErrorTips = showTips;
977 }
978
979 /**
980 * Fluent API equivalent of {@link #setShowErrorTips(boolean)}
981 */
982 public SelfT showErrorTips(boolean value) {
983 setShowErrorTips(value);
984 return self();
985 }
986
987 /**
988 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
989 * {@link ProcessingConfiguration}).
990 */
991 public void unsetShowErrorTips() {
992 showErrorTips = null;
993 }
994
995 @Override
996 public boolean getShowErrorTips() {
997 return isShowErrorTipsSet() ? showErrorTips : getDefaultShowErrorTips();
998 }
999
1000 /**
1001 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
1002 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
1003 */
1004 protected abstract boolean getDefaultShowErrorTips();
1005
1006 @Override
1007 public boolean isShowErrorTipsSet() {
1008 return showErrorTips != null;
1009 }
1010
1011 /**
1012 * Setter pair of {@link #getAPIBuiltinEnabled()}
1013 */
1014 public void setAPIBuiltinEnabled(boolean value) {
1015 apiBuiltinEnabled = Boolean.valueOf(value);
1016 }
1017
1018 /**
1019 * Fluent API equivalent of {@link #setAPIBuiltinEnabled(boolean)}
1020 */
1021 public SelfT apiBuiltinEnabled(boolean value) {
1022 setAPIBuiltinEnabled(value);
1023 return self();
1024 }
1025
1026 /**
1027 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
1028 * {@link ProcessingConfiguration}).
1029 */
1030 public void unsetAPIBuiltinEnabled() {
1031 apiBuiltinEnabled = null;
1032 }
1033
1034 @Override
1035 public boolean getAPIBuiltinEnabled() {
1036 return isAPIBuiltinEnabledSet() ? apiBuiltinEnabled : getDefaultAPIBuiltinEnabled();
1037 }
1038
1039 /**
1040 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
1041 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
1042 */
1043 protected abstract boolean getDefaultAPIBuiltinEnabled();
1044
1045 @Override
1046 public boolean isAPIBuiltinEnabledSet() {
1047 return apiBuiltinEnabled != null;
1048 }
1049
1050 @Override
1051 public boolean getLazyImports() {
1052 return isLazyImportsSet() ? lazyImports : getDefaultLazyImports();
1053 }
1054
1055 /**
1056 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
1057 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
1058 */
1059 protected abstract boolean getDefaultLazyImports();
1060
1061 /**
1062 * Setter pair of {@link #getLazyImports()}
1063 */
1064 public void setLazyImports(boolean lazyImports) {
1065 this.lazyImports = lazyImports;
1066 }
1067
1068 /**
1069 * Fluent API equivalent of {@link #setLazyImports(boolean)}
1070 */
1071 public SelfT lazyImports(boolean lazyImports) {
1072 setLazyImports(lazyImports);
1073 return self();
1074 }
1075
1076 /**
1077 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
1078 * {@link ProcessingConfiguration}).
1079 */
1080 public void unsetLazyImports() {
1081 this.lazyImports = null;
1082 }
1083
1084 @Override
1085 public boolean isLazyImportsSet() {
1086 return lazyImports != null;
1087 }
1088
1089 @Override
1090 public Boolean getLazyAutoImports() {
1091 return isLazyAutoImportsSet() ? lazyAutoImports : getDefaultLazyAutoImports();
1092 }
1093
1094 /**
1095 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
1096 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
1097 */
1098 protected abstract Boolean getDefaultLazyAutoImports();
1099
1100 /**
1101 * Setter pair of {@link #getLazyAutoImports()}
1102 */
1103 public void setLazyAutoImports(Boolean lazyAutoImports) {
1104 this.lazyAutoImports = lazyAutoImports;
1105 lazyAutoImportsSet = true;
1106 }
1107
1108 /**
1109 * Fluent API equivalent of {@link #setLazyAutoImports(Boolean)}
1110 */
1111 public SelfT lazyAutoImports(Boolean lazyAutoImports) {
1112 setLazyAutoImports(lazyAutoImports);
1113 return self();
1114 }
1115
1116 /**
1117 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
1118 * {@link ProcessingConfiguration}).
1119 */
1120 public void unsetLazyAutoImports() {
1121 lazyAutoImports = null;
1122 lazyAutoImportsSet = false;
1123 }
1124
1125 @Override
1126 public boolean isLazyAutoImportsSet() {
1127 return lazyAutoImportsSet;
1128 }
1129
1130 /**
1131 * Setter pair of {@link #getAutoImports()}.
1132 *
1133 * @param autoImports
1134 * Maps the namespace variable names to the template names; not {@code null}, and can't contain {@code
1135 * null} keys of values. The content of the {@link Map} is copied into another {@link Map}, to avoid
1136 * aliasing problems. The iteration order of the original {@link Map} entries is kept.
1137 */
1138 public void setAutoImports(Map<String, String> autoImports) {
1139 setAutoImports(autoImports, false);
1140 }
1141
1142 /**
1143 * @param validatedImmutableUnchanging
1144 * {@code true} if we know that the 1st argument is already validated, immutable, and unchanging (means,
1145 * won't change later because of aliasing).
1146 */
1147 void setAutoImports(Map<String, String> autoImports, boolean validatedImmutableUnchanging) {
1148 _NullArgumentException.check("autoImports", autoImports);
1149 if (!validatedImmutableUnchanging) {
1150 if (autoImports == this.autoImports) {
1151 return;
1152 }
1153 _CollectionUtils.safeCastMap("autoImports", autoImports, String.class, false, String.class, false);
1154 this.autoImports = Collections.unmodifiableMap(new LinkedHashMap<>(autoImports));
1155 } else {
1156 this.autoImports = autoImports;
1157 }
1158 }
1159
1160 /**
1161 * Fluent API equivalent of {@link #setAutoImports(Map)}
1162 */
1163 public SelfT autoImports(Map<String, String> map) {
1164 setAutoImports(map);
1165 return self();
1166 }
1167
1168 /**
1169 * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
1170 * {@link ProcessingConfiguration}).
1171 */
1172 public void unsetAutoImports() {
1173 autoImports = null;
1174 }
1175
1176 @Override
1177 public Map<String, String> getAutoImports() {
1178 return isAutoImportsSet() ? autoImports : getDefaultAutoImports();
1179 }
1180
1181 /**
1182 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
1183 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
1184 */
1185 protected abstract Map<String,String> getDefaultAutoImports();
1186
1187 @Override
1188 public boolean isAutoImportsSet() {
1189 return autoImports != null;
1190 }
1191
1192 /**
1193 * Setter pair of {@link #getAutoIncludes()}
1194 *
1195 * @param autoIncludes Not {@code null}. The {@link List} will be copied to avoid aliasing problems.
1196 */
1197 public void setAutoIncludes(List<String> autoIncludes) {
1198 setAutoIncludes(autoIncludes, false);
1199 }
1200
1201 /**
1202 * @param validatedImmutableUnchanging
1203 * {@code true} if we know that the 1st argument is already validated, immutable, and unchanging (means,
1204 * won't change later because of aliasing).
1205 */
1206 void setAutoIncludes(List<String> autoIncludes, boolean validatedImmutableUnchanging) {
1207 _NullArgumentException.check("autoIncludes", autoIncludes);
1208 if (!validatedImmutableUnchanging) {
1209 if (autoIncludes == this.autoIncludes) {
1210 return;
1211 }
1212 _CollectionUtils.safeCastList("autoIncludes", autoIncludes, String.class, false);
1213 Set<String> uniqueItems = new LinkedHashSet<>(autoIncludes.size() * 4 / 3, 1f);
1214 for (String templateName : autoIncludes) {
1215 if (!uniqueItems.add(templateName)) {
1216 // Move clashing item at the end of the collection
1217 uniqueItems.remove(templateName);
1218 uniqueItems.add(templateName);
1219 }
1220 }
1221 this.autoIncludes = Collections.<String>unmodifiableList(new ArrayList<>(uniqueItems));
1222 } else {
1223 this.autoIncludes = autoIncludes;
1224 }
1225 }
1226
1227 /**
1228 * Fluent API equivalent of {@link #setAutoIncludes(List)}
1229 */
1230 public SelfT autoIncludes(List<String> templateNames) {
1231 setAutoIncludes(templateNames);
1232 return self();
1233 }
1234
1235 /**
1236 * Varargs overload of {@link #autoIncludes(List)}.
1237 */
1238 public SelfT autoIncludes(String... templateNames) {
1239 setAutoIncludes(Arrays.asList(templateNames));
1240 return self();
1241 }
1242
1243 @Override
1244 public List<String> getAutoIncludes() {
1245 return isAutoIncludesSet() ? autoIncludes : getDefaultAutoIncludes();
1246 }
1247
1248 /**
1249 * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
1250 * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
1251 */
1252 protected abstract List<String> getDefaultAutoIncludes();
1253
1254 @Override
1255 public boolean isAutoIncludesSet() {
1256 return autoIncludes != null;
1257 }
1258
1259 private static final String ALLOWED_CLASSES = "allowedClasses";
1260 private static final String TRUSTED_TEMPLATES = "trustedTemplates";
1261
1262 /**
1263 * Sets a FreeMarker setting by a name and string value. If you can configure FreeMarker directly with Java (or
1264 * other programming language), you should use the dedicated setter methods instead (like
1265 * {@link #setTimeZone(TimeZone)}. This meant to be used only when you get settings from somewhere
1266 * as {@link String}-{@link String} name-value pairs (typically, as a {@link Properties} object). Below you find an
1267 * overview of the settings available.
1268 *
1269 * <p>Note: As of FreeMarker 2.3.23, setting names can be written in camel case too. For example, instead of
1270 * {@code dateFormat} you can also use {@code dateFormat}. It's likely that camel case will become to the
1271 * recommended convention in the future.
1272 *
1273 * <p>The list of settings commonly supported in all {@link MutableProcessingConfiguration} subclasses:
1274 * <ul>
1275 * <li><p>{@code "locale"}:
1276 * See {@link #setLocale(Locale)}.
1277 * <br>String value: local codes with the usual format in Java, such as {@code "en_US"}, or
1278 * "JVM default" (ignoring case) to use the default locale of the Java environment.
1279 *
1280 * <li><p>{@code "customNumberFormat"}: See {@link #setCustomNumberFormats(Map)}.
1281 * <br>String value: Interpreted as an <a href="#fm_obe">object builder expression</a>.
1282 * <br>Example: <code>{ "hex": com.example.HexTemplateNumberFormatFactory,
1283 * "gps": com.example.GPSTemplateNumberFormatFactory }</code>
1284 *
1285 * <li><p>{@code "customDateFormat"}: See {@link #setCustomDateFormats(Map)}.
1286 * <br>String value: Interpreted as an <a href="#fm_obe">object builder expression</a>.
1287 * <br>Example: <code>{ "trade": com.example.TradeTemplateDateFormatFactory,
1288 * "log": com.example.LogTemplateDateFormatFactory }</code>
1289 *
1290 * <li><p>{@code "templateExceptionHandler"}:
1291 * See {@link #setTemplateExceptionHandler(TemplateExceptionHandler)}.
1292 * <br>String value: If the value contains dot, then it's interpreted as an <a href="#fm_obe">object builder
1293 * expression</a>.
1294 * If the value does not contain dot, then it must be one of these predefined values (case insensitive):
1295 * {@code "rethrow"} (means {@link TemplateExceptionHandler#RETHROW}),
1296 * {@code "debug"} (means {@link TemplateExceptionHandler#DEBUG}),
1297 * {@code "htmlDebug"} (means {@link TemplateExceptionHandler#HTML_DEBUG}),
1298 * {@code "ignore"} (means {@link TemplateExceptionHandler#IGNORE}), or
1299 * {@code "default"} (only allowed for {@link Configuration} instances) for the default.
1300 *
1301 * <li><p>{@code "attemptExceptionReporter"}:
1302 * See {@link #setAttemptExceptionReporter(AttemptExceptionReporter)}.
1303 * <br>String value: If the value contains dot, then it's interpreted as an <a href="#fm_obe">object builder
1304 * expression</a>.
1305 * If the value does not contain dot, then it must be one of these predefined values (case insensitive):
1306 * {@code "logError"} (means {@link AttemptExceptionReporter#LOG_ERROR}),
1307 * {@code "logWarn"} (means {@link AttemptExceptionReporter#LOG_WARN}), or
1308 * {@code "default"} (only allowed for {@link Configuration} instances) for the default value.
1309 *
1310 * <li><p>{@code "arithmeticEngine"}:
1311 * See {@link #setArithmeticEngine(ArithmeticEngine)}.
1312 * <br>String value: If the value contains dot, then it's interpreted as an <a href="#fm_obe">object builder
1313 * expression</a>.
1314 * If the value does not contain dot,
1315 * then it must be one of these special values (case insensitive):
1316 * {@code "bigdecimal"}, {@code "conservative"}.
1317 *
1318 * <li><p>{@code "objectWrapper"}:
1319 * See {@link Configuration.Builder#setObjectWrapper(ObjectWrapper)}.
1320 * <br>String value: If the value contains dot, then it's interpreted as an <a href="#fm_obe">object builder
1321 * expression</a>, with the addition that {@link DefaultObjectWrapper}, {@link DefaultObjectWrapper} and
1322 * {@link RestrictedObjectWrapper} can be referred without package name. For example, these strings are valid
1323 * values: {@code "DefaultObjectWrapper(3.0.0)"}, {@code "RestrictedObjectWrapper(3.0.0)"}.
1324 * {@code "com.example.MyObjectWrapper(1, 2, someProperty=true, otherProperty=false)"}.
1325 * <br>It also accepts the special value (case insensitive) {@code "default"}.
1326 * <br>It also accepts the special value (case insensitive) {@code "default"}.
1327 *
1328 * <li><p>{@code "numberFormat"}: See {@link #setNumberFormat(String)}.
1329 *
1330 * <li><p>{@code "booleanFormat"}: See {@link #setBooleanFormat(String)} .
1331 *
1332 * <li><p>{@code "dateFormat", "dateFormat", "dateTimeFormat"}:
1333 * See {@link #setDateFormat(String)}, {@link #setTimeFormat(String)}, {@link #setDateTimeFormat(String)}.
1334 *
1335 * <li><p>{@code "timeZone"}:
1336 * See {@link #setTimeZone(TimeZone)}.
1337 * <br>String value: With the format as {@link TimeZone#getTimeZone} defines it. Also, since 2.3.21
1338 * {@code "JVM default"} can be used that will be replaced with the actual JVM default time zone when
1339 * {@link #setSetting(String, String)} is called.
1340 * For example {@code "GMT-8:00"} or {@code "America/Los_Angeles"}
1341 * <br>If you set this setting, consider setting {@code sqlDateAndTimeTimeZone}
1342 * too (see below)!
1343 *
1344 * <li><p>{@code sqlDateAndTimeTimeZone}:
1345 * See {@link #setSQLDateAndTimeTimeZone(TimeZone)}.
1346 * Since 2.3.21.
1347 * <br>String value: With the format as {@link TimeZone#getTimeZone} defines it. Also, {@code "JVM default"}
1348 * can be used that will be replaced with the actual JVM default time zone when
1349 * {@link #setSetting(String, String)} is called. Also {@code "null"} can be used, which has the same effect
1350 * as {@link #setSQLDateAndTimeTimeZone(TimeZone) setSQLDateAndTimeTimeZone(null)}.
1351 *
1352 * <li><p>{@code "outputEncoding"}:
1353 * See {@link #setOutputEncoding(Charset)}.
1354 *
1355 * <li><p>{@code "urlEscapingCharset"}:
1356 * See {@link #setURLEscapingCharset(Charset)}.
1357 *
1358 * <li><p>{@code "autoFlush"}:
1359 * See {@link #setAutoFlush(boolean)}.
1360 * Since 2.3.17.
1361 * <br>String value: {@code "true"}, {@code "false"}, {@code "y"}, etc.
1362 *
1363 * <li><p>{@code "autoImports"}:
1364 * See {@link Configuration#getAutoImports()}
1365 * <br>String value is something like:
1366 * <br>{@code /lib/form.f3ah as f, /lib/widget as w, "/lib/odd name.f3ah" as odd}
1367 *
1368 * <li><p>{@code "autoInclude"}: Sets the list of auto-includes.
1369 * See {@link Configuration#getAutoIncludes()}
1370 * <br>String value is something like:
1371 * <br>{@code /include/common.f3ah, "/include/evil name.f3ah"}
1372 *
1373 * <li><p>{@code "lazyAutoImports"}:
1374 * See {@link Configuration#getLazyAutoImports()}.
1375 * <br>String value: {@code "true"}, {@code "false"} (also the equivalents: {@code "yes"}, {@code "no"},
1376 * {@code "t"}, {@code "f"}, {@code "y"}, {@code "n"}), case insensitive. Also can be {@code "null"}.
1377
1378 * <li><p>{@code "lazyImports"}:
1379 * See {@link Configuration#getLazyImports()}.
1380 * <br>String value: {@code "true"}, {@code "false"} (also the equivalents: {@code "yes"}, {@code "no"},
1381 * {@code "t"}, {@code "f"}, {@code "y"}, {@code "n"}), case insensitive.
1382 *
1383 * <li><p>{@code "newBuiltinClassResolver"}:
1384 * See {@link #setNewBuiltinClassResolver(TemplateClassResolver)}.
1385 * Since 2.3.17.
1386 * The value must be one of these (ignore the quotation marks):
1387 * <ol>
1388 * <li><p>{@code "unrestricted"}:
1389 * Use {@link TemplateClassResolver#UNRESTRICTED}
1390 * <li><p>{@code "allowNothing"}:
1391 * Use {@link TemplateClassResolver#ALLOW_NOTHING}
1392 * <li><p>Something that contains colon will use
1393 * {@link OptInTemplateClassResolver} and is expected to
1394 * store comma separated values (possibly quoted) segmented
1395 * with {@code "allowedClasses:"} and/or
1396 * {@code "trustedTemplates:"}. Examples of valid values:
1397 *
1398 * <table style="width: auto; border-collapse: collapse" border="1"
1399 * summary="trustedTemplate value examples">
1400 * <tr>
1401 * <th>Setting value
1402 * <th>Meaning
1403 * <tr>
1404 * <td>
1405 * {@code allowedClasses: com.example.C1, com.example.C2,
1406 * trustedTemplates: lib/*, safe.f3ah}
1407 * <td>
1408 * Only allow instantiating the {@code com.example.C1} and
1409 * {@code com.example.C2} classes. But, allow templates
1410 * within the {@code lib/} directory (like
1411 * {@code lib/foo/bar.f3ah}) and template {@code safe.f3ah}
1412 * (that does not match {@code foo/safe.f3ah}, only
1413 * exactly {@code safe.f3ah}) to instantiate anything
1414 * that {@link TemplateClassResolver#UNRESTRICTED} allows.
1415 * <tr>
1416 * <td>
1417 * {@code allowedClasses: com.example.C1, com.example.C2}
1418 * <td>Only allow instantiating the {@code com.example.C1} and
1419 * {@code com.example.C2} classes. There are no
1420 * trusted templates.
1421 * <tr>
1422 * <td>
1423 {@code trustedTemplates: lib/*, safe.f3ah}
1424 * <td>
1425 * Do not allow instantiating any classes, except in
1426 * templates inside {@code lib/} or in template
1427 * {@code safe.f3ah}.
1428 * </table>
1429 *
1430 * <p>For more details see {@link OptInTemplateClassResolver}.
1431 *
1432 * <li><p>Otherwise if the value contains dot, it's interpreted as an <a href="#fm_obe">object builder
1433 * expression</a>.
1434 * </ol>
1435 * Note that the {@code safer} option was removed in FreeMarker 3.0.0, as it has become equivalent with
1436 * {@code "unrestricted"}, as the classes it has blocked were removed from FreeMarker.
1437 * <li><p>{@code "showErrorTips"}:
1438 * See {@link #setShowErrorTips(boolean)}.
1439 * Since 2.3.21.
1440 * <br>String value: {@code "true"}, {@code "false"}, {@code "y"}, etc.
1441 *
1442 * <li><p>{@code apiBuiltinEnabled}:
1443 * See {@link #setAPIBuiltinEnabled(boolean)}.
1444 * Since 2.3.22.
1445 * <br>String value: {@code "true"}, {@code "false"}, {@code "y"}, etc.
1446 *
1447 * </ul>
1448 *
1449 * <p>{@link Configuration} (a subclass of {@link MutableProcessingConfiguration}) also understands these:</p>
1450 * <ul>
1451 * <li><p>{@code "auto_escaping"}:
1452 * See {@link Configuration#getAutoEscapingPolicy()}
1453 * <br>String value: {@code "enableIfDefault"} or {@code "enableIfDefault"} for
1454 * {@link AutoEscapingPolicy#ENABLE_IF_DEFAULT},
1455 * {@code "enableIfDefault"} or {@code "enableIfSupported"} for
1456 * {@link AutoEscapingPolicy#ENABLE_IF_SUPPORTED}
1457 * {@code "disable"} for {@link AutoEscapingPolicy#DISABLE}.
1458 *
1459 * <li><p>{@code "sourceEncoding"}:
1460 * See {@link Configuration#getSourceEncoding()}; since 2.3.26 also accepts value "JVM default"
1461 * (not case sensitive) to set the Java environment default value.
1462 * <br>As the default value is the system default, which can change
1463 * from one server to another, <b>you should always set this!</b>
1464 *
1465 * <li><p>{@code "localizedTemplateLookup"}:
1466 * See {@link Configuration#getLocalizedTemplateLookup()}.
1467 * <br>String value: {@code "true"}, {@code "false"} (also the equivalents: {@code "yes"}, {@code "no"},
1468 * {@code "t"}, {@code "f"}, {@code "y"}, {@code "n"}).
1469 * ASTDirCase insensitive.
1470 *
1471 * <li><p>{@code "outputFormat"}:
1472 * See {@link ParsingConfiguration#getOutputFormat()}.
1473 * <br>String value: {@code "default"} (case insensitive) for the default, or an
1474 * <a href="#fm_obe">object builder expression</a> that gives an {@link OutputFormat}, for example
1475 * {@code HTMLOutputFormat} or {@code XMLOutputFormat}.
1476 *
1477 * <li><p>{@code "registeredCustomOutputFormats"}:
1478 * See {@link Configuration#getRegisteredCustomOutputFormats()}.
1479 * <br>String value: an <a href="#fm_obe">object builder expression</a> that gives a {@link List} of
1480 * {@link OutputFormat}-s.
1481 * Example: {@code [com.example.MyOutputFormat(), com.example.MyOtherOutputFormat()]}
1482 *
1483 * <li><p>{@code "whitespaceStripping"}:
1484 * See {@link ParsingConfiguration#getWhitespaceStripping()}.
1485 * <br>String value: {@code "true"}, {@code "false"}, {@code yes}, etc.
1486 *
1487 * <li><p>{@code "templateCacheStorage"}:
1488 * See {@link Configuration#getTemplateCacheStorage()}.
1489 * <br>String value: If the value contains dot, then it's interpreted as an <a href="#fm_obe">object builder
1490 * expression</a>.
1491 * If the value does not contain dot,
1492 * then a {@link org.apache.freemarker.core.templateresolver.impl.MruCacheStorage} will be used with the
1493 * maximum strong and soft sizes specified with the setting value. Examples
1494 * of valid setting values:
1495 *
1496 * <table style="width: auto; border-collapse: collapse" border="1" summary="templateCacheStorage value
1497 * examples">
1498 * <tr><th>Setting value<th>max. strong size<th>max. soft size
1499 * <tr><td>{@code "strong:50, soft:500"}<td>50<td>500
1500 * <tr><td>{@code "strong:100, soft"}<td>100<td>{@code Integer.MAX_VALUE}
1501 * <tr><td>{@code "strong:100"}<td>100<td>0
1502 * <tr><td>{@code "soft:100"}<td>0<td>100
1503 * <tr><td>{@code "strong"}<td>{@code Integer.MAX_VALUE}<td>0
1504 * <tr><td>{@code "soft"}<td>0<td>{@code Integer.MAX_VALUE}
1505 * </table>
1506 *
1507 * <p>The value is not case sensitive. The order of <tt>soft</tt> and <tt>strong</tt>
1508 * entries is not significant.
1509 *
1510 * <li><p>{@code "templateUpdateDelay"}:
1511 * Template update delay in <b>seconds</b> (not in milliseconds) if no unit is specified; see
1512 * {@link Configuration#getTemplateUpdateDelayMilliseconds()} for more.
1513 * <br>String value: Valid positive integer, optionally followed by a time unit (recommended). The default
1514 * unit is seconds. It's strongly recommended to specify the unit for clarity, like in "500 ms" or "30 s".
1515 * Supported units are: "s" (seconds), "ms" (milliseconds), "m" (minutes), "h" (hours). The whitespace between
1516 * the unit and the number is optional. Units are only supported since 2.3.23.
1517 *
1518 * <li><p>{@code "incompatibleImprovements"}:
1519 * See {@link Configuration#getIncompatibleImprovements()}.
1520 * <br>String value: version number like {@code 2.3.20}.
1521 *
1522 * <li><p>{@code "recognizeStandardFileExtensions"}:
1523 * See {@link Configuration#getRecognizeStandardFileExtensions()}.
1524 * <br>String value: {@code "default"} (case insensitive) for the default, or {@code "true"}, {@code "false"},
1525 * {@code yes}, etc.
1526 *
1527 * <li><p>{@code "templateConfigurations"}:
1528 * See: {@link Configuration#getTemplateConfigurations()}.
1529 * <br>String value: Interpreted as an <a href="#fm_obe">object builder expression</a>,
1530 * can be {@code null}.
1531 *
1532 * <li><p>{@code "templateLoader"}:
1533 * See: {@link Configuration#getTemplateLoader()}.
1534 * <br>String value: {@code "default"} (case insensitive) for the default, or else interpreted as an
1535 * <a href="#fm_obe">object builder expression</a>. {@code "null"} is also allowed.
1536 *
1537 * <li><p>{@code "templateLookupStrategy"}:
1538 * See: {@link Configuration#getTemplateLookupStrategy()}.
1539 * <br>String value: {@code "default"} (case insensitive) for the default, or else interpreted as an
1540 * <a href="#fm_obe">object builder expression</a>.
1541 *
1542 * <li><p>{@code "templateNameFormat"}:
1543 * See: {@link Configuration#getTemplateNameFormat()}.
1544 * <br>String value: {@code "default"} (case insensitive) for the default,
1545 * {@link DefaultTemplateNameFormat#INSTANCE}.
1546 * </ul>
1547 *
1548 * <p><a name="fm_obe"></a>Regarding <em>object builder expressions</em> (used by the setting values where it was
1549 * indicated):
1550 * <ul>
1551 * <li><p>Before FreeMarker 2.3.21 it had to be a fully qualified class name, and nothing else.</li>
1552 * <li><p>Since 2.3.21, the generic syntax is:
1553 * <tt><i>className</i>(<i>constrArg1</i>, <i>constrArg2</i>, ... <i>constrArgN</i>,
1554 * <i>propName1</i>=<i>propValue1</i>, <i>propName2</i>=<i>propValue2</i>, ...
1555 * <i>propNameN</i>=<i>propValueN</i>)</tt>,
1556 * where
1557 * <tt><i>className</i></tt> is the fully qualified class name of the instance to invoke (except if we have
1558 * builder class or <tt>INSTANCE</tt> field around, but see that later),
1559 * <tt><i>constrArg</i></tt>-s are the values of constructor arguments,
1560 * and <tt><i>propName</i>=<i>propValue</i></tt>-s set JavaBean properties (like <tt>x=1</tt> means
1561 * <tt>setX(1)</tt>) on the created instance. You can have any number of constructor arguments and property
1562 * setters, including 0. Constructor arguments must precede any property setters.
1563 * </li>
1564 * <li>
1565 * Example: <tt>com.example.MyObjectWrapper(1, 2, exposeFields=true, cacheSize=5000)</tt> is nearly
1566 * equivalent with this Java code:
1567 * <tt>obj = new com.example.MyObjectWrapper(1, 2); obj.setExposeFields(true); obj.setCacheSize(5000);</tt>
1568 * </li>
1569 * <li>
1570 * <p>If you have no constructor arguments and property setters, and the <tt><i>className</i></tt> class has
1571 * a public static {@code INSTANCE} field, the value of that filed will be the value of the expression, and
1572 * the constructor won't be called.
1573 * </li>
1574 * <li>
1575 * <p>If there exists a class named <tt><i>className</i>Builder</tt>, then that class will be instantiated
1576 * instead with the given constructor arguments, and the JavaBean properties of that builder instance will be
1577 * set. After that, the public <tt>build()</tt> method of the instance will be called, whose return value
1578 * will be the value of the whole expression. (The builder class and the <tt>build()</tt> method is simply
1579 * found by name, there's no special interface to implement.)Note that if you have a builder class, you don't
1580 * actually need a <tt><i>className</i></tt> class (since 2.3.24); after all,
1581 * <tt><i>className</i>Builder.build()</tt> can return any kind of object.
1582 * </li>
1583 * <li>
1584 * <p>Currently, the values of arguments and properties can only be one of these:
1585 * <ul>
1586 * <li>A numerical literal, like {@code 123} or {@code -1.5}. The value will be automatically converted to
1587 * the type of the target (just like in FTL). However, a target type is only available if the number will
1588 * be a parameter to a method or constructor, not when it's a value (or key) in a {@code List} or
1589 * {@code Map} literal. Thus in the last case the type of number will be like in Java language, like
1590 * {@code 1} is an {@code int}, and {@code 1.0} is a {@code double}, and {@code 1.0f} is a {@code float},
1591 * etc. In all cases, the standard Java type postfixes can be used ("f", "d", "l"), plus "bd" for
1592 * {@code BigDecimal} and "bi" for {@code BigInteger}.</li>
1593 * <li>A boolean literal: {@code true} or {@code false}
1594 * <li>The null literal: {@code null}
1595 * <li>A string literal with FTL syntax, except that it can't contain <tt>${...}</tt>-s.
1596 * Examples: {@code "Line 1\nLine 2"} or {@code r"C:\temp"}.
1597 * <li>A list literal (since 2.3.24) with FTL-like syntax, for example {@code [ 'foo', 2, true ]}.
1598 * If the parameter is expected to be array, the list will be automatically converted to array.
1599 * The list items can be any kind of expression, like even object builder expressions.
1600 * <li>A map literal (since 2.3.24) with FTL-like syntax, for example <code>{ 'foo': 2, 'bar': true }</code>.
1601 * The keys and values can be any kind of expression, like even object builder expressions.
1602 * The resulting Java object will be a {@link Map} that keeps the item order ({@link LinkedHashMap} as
1603 * of this writing).
1604 * <li>A reference to a public static filed, like {@code Configuration.AUTO_DETECT} or
1605 * {@code com.example.MyClass.MY_CONSTANT}.
1606 * <li>An object builder expression. That is, object builder expressions can be nested into each other.
1607 * </ul>
1608 * </li>
1609 * <li>
1610 * The same kind of expression as for parameters can also be used as top-level expressions (though it's
1611 * rarely useful, apart from using {@code null}).
1612 * </li>
1613 * <li>
1614 * <p>The top-level object builder expressions may omit {@code ()}.
1615 * </li>
1616 * <li>
1617 * <p>The following classes can be referred to with simple (unqualified) name instead of fully qualified name:
1618 * {@link DefaultObjectWrapper}, {@link DefaultObjectWrapper}, {@link RestrictedObjectWrapper}, {@link Locale},
1619 * {@link TemplateConfiguration}, {@link PathGlobMatcher}, {@link FileNameGlobMatcher}, {@link PathRegexMatcher},
1620 * {@link AndMatcher}, {@link OrMatcher}, {@link NotMatcher}, {@link ConditionalTemplateConfigurationFactory},
1621 * {@link MergingTemplateConfigurationFactory}, {@link FirstMatchTemplateConfigurationFactory},
1622 * {@link HTMLOutputFormat}, {@link XMLOutputFormat}, {@link RTFOutputFormat}, {@link PlainTextOutputFormat},
1623 * {@link UndefinedOutputFormat}, {@link Configuration}, {@link TemplateLanguage}, {@link TagSyntax}.
1624 * </li>
1625 * <li>
1626 * <p>{@link TimeZone} objects can be created like {@code TimeZone("UTC")}, despite that there's no a such
1627 * constructor.
1628 * </li>
1629 * <li>
1630 * <p>{@link Charset} objects can be created like {@code Charset("ISO-8859-5")}, despite that there's no a such
1631 * constructor.
1632 * </li>
1633 * <li>
1634 * <p>The classes and methods that the expression meant to access must be all public.
1635 * </li>
1636 * </ul>
1637 *
1638 * @param name the name of the setting.
1639 * @param value the string that describes the new value of the setting.
1640 *
1641 * @throws InvalidSettingNameException if the name is wrong.
1642 * @throws InvalidSettingValueException if the new value of the setting can't be set for any other reasons.
1643 */
1644 public void setSetting(String name, String value) throws ConfigurationException {
1645 boolean unknown = false;
1646 try {
1647 if (LOCALE_KEY.equals(name)) {
1648 if (JVM_DEFAULT_VALUE.equalsIgnoreCase(value)) {
1649 setLocale(Locale.getDefault());
1650 } else {
1651 setLocale(_StringUtils.deduceLocale(value));
1652 }
1653 } else if (NUMBER_FORMAT_KEY.equals(name)) {
1654 setNumberFormat(value);
1655 } else if (CUSTOM_NUMBER_FORMATS_KEY.equals(name)) {
1656 Map map = (Map) _ObjectBuilderSettingEvaluator.eval(
1657 value, Map.class, false, _SettingEvaluationEnvironment.getCurrent());
1658 checkSettingValueItemsType("Map keys", String.class, map.keySet());
1659 checkSettingValueItemsType("Map values", TemplateNumberFormatFactory.class, map.values());
1660 setCustomNumberFormats(map);
1661 } else if (TIME_FORMAT_KEY.equals(name)) {
1662 setTimeFormat(value);
1663 } else if (DATE_FORMAT_KEY.equals(name)) {
1664 setDateFormat(value);
1665 } else if (DATE_TIME_FORMAT_KEY.equals(name)) {
1666 setDateTimeFormat(value);
1667 } else if (CUSTOM_DATE_FORMATS_KEY.equals(name)) {
1668 Map map = (Map) _ObjectBuilderSettingEvaluator.eval(
1669 value, Map.class, false, _SettingEvaluationEnvironment.getCurrent());
1670 checkSettingValueItemsType("Map keys", String.class, map.keySet());
1671 checkSettingValueItemsType("Map values", TemplateDateFormatFactory.class, map.values());
1672 setCustomDateFormats(map);
1673 } else if (TIME_ZONE_KEY.equals(name)) {
1674 setTimeZone(parseTimeZoneSettingValue(value));
1675 } else if (SQL_DATE_AND_TIME_TIME_ZONE_KEY.equals(name)) {
1676 setSQLDateAndTimeTimeZone(value.equals("null") ? null : parseTimeZoneSettingValue(value));
1677 } else if (TEMPLATE_EXCEPTION_HANDLER_KEY.equals(name)) {
1678 if (value.indexOf('.') == -1) {
1679 if ("debug".equalsIgnoreCase(value)) {
1680 setTemplateExceptionHandler(
1681 TemplateExceptionHandler.DEBUG);
1682 } else if ("htmlDebug".equals(value)) {
1683 setTemplateExceptionHandler(
1684 TemplateExceptionHandler.HTML_DEBUG);
1685 } else if ("ignore".equalsIgnoreCase(value)) {
1686 setTemplateExceptionHandler(
1687 TemplateExceptionHandler.IGNORE);
1688 } else if ("rethrow".equalsIgnoreCase(value)) {
1689 setTemplateExceptionHandler(
1690 TemplateExceptionHandler.RETHROW);
1691 } else if (DEFAULT_VALUE.equalsIgnoreCase(value)
1692 && this instanceof Configuration.ExtendableBuilder) {
1693 unsetTemplateExceptionHandler();
1694 } else {
1695 throw new InvalidSettingValueException(
1696 name, value,
1697 value.equalsIgnoreCase("html_debug")
1698 ? "The correct value would be: htmlDebug"
1699 : "No such predefined template exception handler name");
1700 }
1701 } else {
1702 setTemplateExceptionHandler((TemplateExceptionHandler) _ObjectBuilderSettingEvaluator.eval(
1703 value, TemplateExceptionHandler.class, false, _SettingEvaluationEnvironment.getCurrent()));
1704 }
1705 } else if (ATTEMPT_EXCEPTION_REPORTER_KEY.equals(name)) {
1706 if (value.indexOf('.') == -1) {
1707 if ("logError".equals(value)) {
1708 setAttemptExceptionReporter(
1709 AttemptExceptionReporter.LOG_ERROR);
1710 } else if ("logWarn".equals(value)) {
1711 setAttemptExceptionReporter(
1712 AttemptExceptionReporter.LOG_WARN);
1713 } else if (DEFAULT_VALUE.equalsIgnoreCase(value)
1714 && this instanceof Configuration.ExtendableBuilder) {
1715 unsetAttemptExceptionReporter();
1716 } else {
1717 throw new InvalidSettingValueException(
1718 name, value,
1719 value.equalsIgnoreCase("log_error") ? "The correct value would be: "
1720 + "logError"
1721 : value.equalsIgnoreCase("log_wran") ? "The correct value would be: "
1722 + "logWarn"
1723 : "No such predefined template exception handler name");
1724 }
1725 } else {
1726 setTemplateExceptionHandler((TemplateExceptionHandler) _ObjectBuilderSettingEvaluator.eval(
1727 value, TemplateExceptionHandler.class, false, _SettingEvaluationEnvironment.getCurrent()));
1728 }
1729 } else if (ARITHMETIC_ENGINE_KEY.equals(name)) {
1730 if (value.indexOf('.') == -1) {
1731 if ("bigdecimal".equalsIgnoreCase(value)) {
1732 setArithmeticEngine(BigDecimalArithmeticEngine.INSTANCE);
1733 } else if ("conservative".equalsIgnoreCase(value)) {
1734 setArithmeticEngine(ConservativeArithmeticEngine.INSTANCE);
1735 } else {
1736 throw new InvalidSettingValueException(
1737 name, value, "No such predefined arithmetical engine name");
1738 }
1739 } else {
1740 setArithmeticEngine((ArithmeticEngine) _ObjectBuilderSettingEvaluator.eval(
1741 value, ArithmeticEngine.class, false, _SettingEvaluationEnvironment.getCurrent()));
1742 }
1743 } else if (BOOLEAN_FORMAT_KEY.equals(name)) {
1744 setBooleanFormat(value);
1745 } else if (OUTPUT_ENCODING_KEY.equals(name)) {
1746 setOutputEncoding(Charset.forName(value));
1747 } else if (URL_ESCAPING_CHARSET_KEY.equals(name)) {
1748 setURLEscapingCharset(Charset.forName(value));
1749 } else if (AUTO_FLUSH_KEY.equals(name)) {
1750 setAutoFlush(_StringUtils.getYesNo(value));
1751 } else if (SHOW_ERROR_TIPS_KEY.equals(name)) {
1752 setShowErrorTips(_StringUtils.getYesNo(value));
1753 } else if (API_BUILTIN_ENABLED_KEY.equals(name)) {
1754 setAPIBuiltinEnabled(_StringUtils.getYesNo(value));
1755 } else if (NEW_BUILTIN_CLASS_RESOLVER_KEY.equals(name)) {
1756 if ("unrestricted".equals(value)) {
1757 setNewBuiltinClassResolver(TemplateClassResolver.UNRESTRICTED);
1758 } else if ("allowNothing".equals(value)) {
1759 setNewBuiltinClassResolver(TemplateClassResolver.ALLOW_NOTHING);
1760 } else if (value.indexOf(":") != -1) {
1761 List<_KeyValuePair<String, List<String>>> segments = parseAsSegmentedList(value);
1762 Set allowedClasses = null;
1763 List<String> trustedTemplates = null;
1764 for (_KeyValuePair<String, List<String>> segment : segments) {
1765 String segmentKey = segment.getKey();
1766 List<String> segmentValue = segment.getValue();
1767 if (segmentKey.equals(ALLOWED_CLASSES)) {
1768 allowedClasses = new HashSet(segmentValue);
1769 } else if (segmentKey.equals(TRUSTED_TEMPLATES)) {
1770 trustedTemplates = segmentValue;
1771 } else {
1772 throw new InvalidSettingValueException(name, value,
1773 "Unrecognized list segment key: " + _StringUtils.jQuote(segmentKey) +
1774 ". Supported keys are: \"" + ALLOWED_CLASSES + "\", \"" +
1775 TRUSTED_TEMPLATES + "\"");
1776 }
1777 }
1778 setNewBuiltinClassResolver(
1779 new OptInTemplateClassResolver(allowedClasses, trustedTemplates));
1780 } else if ("allowsNothing".equals(value) || "allows_nothing".equals(value)) {
1781 throw new InvalidSettingValueException(
1782 name, value, "The correct value would be: allowNothing");
1783 } else if (value.indexOf('.') != -1) {
1784 setNewBuiltinClassResolver((TemplateClassResolver) _ObjectBuilderSettingEvaluator.eval(
1785 value, TemplateClassResolver.class, false,
1786 _SettingEvaluationEnvironment.getCurrent()));
1787 } else {
1788 throw new InvalidSettingValueException(
1789 name, value,
1790 "Not predefined class resolved name, nor follows class resolver definition syntax, nor "
1791 + "looks like class name");
1792 }
1793 } else if (LAZY_AUTO_IMPORTS_KEY.equals(name)) {
1794 setLazyAutoImports(value.equals(NULL_VALUE) ? null : Boolean.valueOf(_StringUtils.getYesNo(value)));
1795 } else if (LAZY_IMPORTS_KEY.equals(name)) {
1796 setLazyImports(_StringUtils.getYesNo(value));
1797 } else if (AUTO_INCLUDES_KEY.equals(name)) {
1798 setAutoIncludes(parseAsList(value));
1799 } else if (AUTO_IMPORTS_KEY.equals(name)) {
1800 setAutoImports(parseAsImportList(value));
1801 } else {
1802 unknown = true;
1803 }
1804 } catch (InvalidSettingValueException e) {
1805 throw e;
1806 } catch (Exception e) {
1807 throw new InvalidSettingValueException(name, value, e);
1808 }
1809 if (unknown) {
1810 throw unknownSettingException(name);
1811 }
1812 }
1813
1814 /**
1815 * Fluent API equivalent of {@link #setSetting(String, String)}.
1816 */
1817 public SelfT setting(String name, String value) throws ConfigurationException {
1818 setSetting(name, value);
1819 return self();
1820 }
1821
1822 /**
1823 * @throws IllegalArgumentException
1824 * if the type of the some of the values isn't as expected
1825 */
1826 private void checkSettingValueItemsType(String somethingsSentenceStart, Class<?> expectedClass,
1827 Collection<?> values) {
1828 if (values == null) return;
1829 for (Object value : values) {
1830 if (!expectedClass.isInstance(value)) {
1831 throw new IllegalArgumentException(somethingsSentenceStart + " must be instances of "
1832 + _ClassUtils.getShortClassName(expectedClass) + ", but one of them was a(n) "
1833 + _ClassUtils.getShortClassNameOfObject(value) + ".");
1834 }
1835 }
1836 }
1837
1838 /**
1839 * Returns the valid setting names for a {@link ProcessingConfiguration}.
1840 *
1841 * @see Configuration.ExtendableBuilder#getSettingNames()
1842 */
1843 public static Set<String> getSettingNames() {
1844 return SETTING_NAMES;
1845 }
1846
1847 private TimeZone parseTimeZoneSettingValue(String value) {
1848 TimeZone tz;
1849 if (JVM_DEFAULT_VALUE.equalsIgnoreCase(value)) {
1850 tz = TimeZone.getDefault();
1851 } else {
1852 tz = TimeZone.getTimeZone(value);
1853 }
1854 return tz;
1855 }
1856
1857 /**
1858 * Creates the exception that should be thrown when a setting name isn't recognized.
1859 */
1860 protected final InvalidSettingNameException unknownSettingException(String name) {
1861 Version removalVersion = getRemovalVersionForUnknownSetting(name);
1862 return removalVersion != null
1863 ? new InvalidSettingNameException(name, removalVersion)
1864 : new InvalidSettingNameException(name, getCorrectedNameForUnknownSetting(name));
1865 }
1866
1867 /**
1868 * If a setting name is unknown because it was removed over time (not just renamed), then returns the version where
1869 * it was removed, otherwise returns {@code null}.
1870 */
1871 protected Version getRemovalVersionForUnknownSetting(String name) {
1872 if (name.equals("classic_compatible") || name.equals("classicCompatible")
1873 || name.equals("tag_syntax") || name.equals("tagSyntax")
1874 || name.equals("interpolation_syntax") || name.equals("interpolationSyntax")) {
1875 return Configuration.VERSION_3_0_0;
1876 }
1877 return null;
1878 }
1879
1880 /**
1881 * @param name The wrong name
1882 * @return The corrected name, or {@code null} if there's no known correction
1883 */
1884 protected String getCorrectedNameForUnknownSetting(String name) {
1885 switch(name.toLowerCase()) {
1886 case "autoinclude":
1887 case "auto_include":
1888 case "auto_includes":
1889 return AUTO_INCLUDES_KEY;
1890 case "autoimport":
1891 case "auto_import":
1892 case "auto_imports":
1893 return AUTO_IMPORTS_KEY;
1894 case "datetimeformat":
1895 case "datetime_format":
1896 case "date_time_format":
1897 return DATE_TIME_FORMAT_KEY;
1898 default:
1899 return null;
1900 }
1901 }
1902
1903 /**
1904 * Set the settings stored in a <code>Properties</code> object.
1905 *
1906 * @throws ConfigurationException if the <code>Properties</code> object contains
1907 * invalid keys, or invalid setting values, or any other error occurs
1908 * while changing the settings.
1909 */
1910 public void setSettings(Properties props) throws ConfigurationException {
1911 final _SettingEvaluationEnvironment prevEnv = _SettingEvaluationEnvironment.startScope();
1912 try {
1913 for (String key : props.stringPropertyNames()) {
1914 setSetting(key, props.getProperty(key).trim());
1915 }
1916 } finally {
1917 _SettingEvaluationEnvironment.endScope(prevEnv);
1918 }
1919 }
1920
1921 /**
1922 * Fluent API equivalent of {@link #setSettings(Properties)}.
1923 */
1924 public SelfT settings(Properties props) {
1925 setSettings(props);
1926 return self();
1927 }
1928
1929 /**
1930 * Setter pair of {@link #getCustomSetting(Serializable)}.
1931 *
1932 * @param key
1933 * The identifier of the the custom setting; not {@code null}. Usually an enum or a {@link String}. Must
1934 * be usable as {@link HashMap} key.
1935 * @param value
1936 * The value of the custom setting. {@code null} is a legal attribute value. Thus, setting the value to
1937 * {@code null} doesn't unset (remove) the attribute; use {@link #unsetCustomSetting(Serializable)} for
1938 * that. Also, {@link #MISSING_VALUE_MARKER} is not an allowed value.
1939 * The content of the object shouldn't be changed after it was added as an attribute (ideally, it should
1940 * be a true immutable object); if you need to change the content, certainly you should use the
1941 * {@link CustomStateScope} API.
1942 */
1943 public void setCustomSetting(Serializable key, Object value) {
1944 _NullArgumentException.check("key", key);
1945 if (value == MISSING_VALUE_MARKER) {
1946 throw new IllegalArgumentException("MISSING_VALUE_MARKER can't be used as custom setting value");
1947 }
1948 ensureCustomSettingsModifiable();
1949 customSettings.put(key, value);
1950 }
1951
1952 /**
1953 * Fluent API equivalent of {@link #setCustomSetting(Serializable, Object)}
1954 */
1955 public SelfT customSetting(Serializable key, Object value) {
1956 setCustomSetting(key, value);
1957 return self();
1958 }
1959
1960 @Override
1961 public boolean isCustomSettingSet(Serializable key) {
1962 return customSettings.containsKey(key);
1963 }
1964
1965 /**
1966 * Unset the custom setting for this {@link ProcessingConfiguration} (but not from the parent
1967 * {@link ProcessingConfiguration}, from where it will be possibly inherited after this), as if
1968 * {@link #setCustomSetting(Serializable, Object)} was never called for it on this
1969 * {@link ProcessingConfiguration}. Note that this is different than setting the custom setting value to {@code
1970 * null}, as then {@link #getCustomSetting(Serializable)} will just return that {@code null}, and won't look for the
1971 * attribute in the parent {@link ProcessingConfiguration}.
1972 *
1973 * @param key As in {@link #getCustomSetting(Serializable)}
1974 */
1975 public void unsetCustomSetting(Serializable key) {
1976 if (customSettingsModifiable) {
1977 customSettings.remove(key);
1978 } else if (customSettings.containsKey(key)) {
1979 ensureCustomSettingsModifiable();
1980 customSettings.remove(key);
1981 }
1982 }
1983
1984 @Override
1985 public Object getCustomSetting(Serializable key) throws CustomSettingValueNotSetException {
1986 return getCustomSetting(key, null, false);
1987 }
1988
1989 @Override
1990 public Object getCustomSetting(Serializable key, Object defaultValue) {
1991 return getCustomSetting(key, defaultValue, true);
1992 }
1993
1994 private Object getCustomSetting(Serializable key, Object defaultValue, boolean useDefaultValue) {
1995 Object value = customSettings.get(key);
1996 if (value != null || customSettings.containsKey(key)) {
1997 return value;
1998 }
1999 return getDefaultCustomSetting(key, defaultValue, useDefaultValue);
2000 }
2001
2002 @Override
2003 public Map<Serializable, Object> getCustomSettings(boolean includeInherited) {
2004 if (includeInherited) {
2005 LinkedHashMap<Serializable, Object> result = new LinkedHashMap<>();
2006 collectDefaultCustomSettingsSnapshot(result);
2007 if (!result.isEmpty()) {
2008 if (customSettings != null) {
2009 result.putAll(customSettings);
2010 }
2011 return Collections.unmodifiableMap(result);
2012 }
2013 }
2014
2015 // When there's no need for inheritance:
2016 customSettingsModifiable = false; // Copy-on-write on next modification
2017 return _CollectionUtils.unmodifiableMap(customSettings);
2018 }
2019
2020 /**
2021 * Called from {@link #getCustomSettings(boolean)}, adds the default (such as inherited) custom settings
2022 * to the argument {@link Map}.
2023 */
2024 protected abstract void collectDefaultCustomSettingsSnapshot(Map<Serializable, Object> target);
2025
2026 private void ensureCustomSettingsModifiable() {
2027 if (!customSettingsModifiable) {
2028 customSettings = new LinkedHashMap<>(customSettings);
2029 customSettingsModifiable = true;
2030 }
2031 }
2032
2033 /**
2034 * Called be {@link #getCustomSetting(Serializable)} and {@link #getCustomSetting(Serializable, Object)} if the
2035 * attribute wasn't set in the current {@link ProcessingConfiguration}.
2036 *
2037 * @param useDefaultValue
2038 * If {@code true}, and the attribute is missing, then return {@code defaultValue}, otherwise throw {@link
2039 * CustomSettingValueNotSetException}.
2040 *
2041 * @throws CustomSettingValueNotSetException
2042 * if the attribute wasn't set in the parents, or has no default otherwise, and {@code useDefaultValue} was
2043 * {@code false}.
2044 */
2045 protected abstract Object getDefaultCustomSetting(
2046 Serializable key, Object defaultValue, boolean useDefaultValue) throws CustomSettingValueNotSetException;
2047
2048 /**
2049 * Convenience method for calling {@link #setCustomSetting(Serializable, Object)} for each {@link Map} entry.
2050 * Note that it won't remove the already existing custom settings.
2051 */
2052 public void setCustomSettings(Map<? extends Serializable, ?> customSettings) {
2053 _NullArgumentException.check("customSettings", customSettings);
2054 for (Object value : customSettings.values()) {
2055 if (value == MISSING_VALUE_MARKER) {
2056 throw new IllegalArgumentException("MISSING_VALUE_MARKER can't be used as attribute value");
2057 }
2058 }
2059
2060 ensureCustomSettingsModifiable();
2061 this.customSettings.putAll(customSettings);
2062 customSettingsModifiable = true;
2063 }
2064
2065 /**
2066 * Fluent API equivalent of {@link #setCustomSettings(Map)}
2067 */
2068 public SelfT customSettings(Map<Serializable, Object> customSettings) {
2069 setCustomSettings(customSettings);
2070 return self();
2071 }
2072
2073 /**
2074 * Used internally to avoid copying the {@link Map} when we know that its content won't change anymore.
2075 */
2076 void setCustomSettingsMap(Map<Serializable, Object> customSettings) {
2077 _NullArgumentException.check("customSettings", customSettings);
2078 this.customSettings = customSettings;
2079 this.customSettingsModifiable = false;
2080 }
2081
2082 /**
2083 * Unsets all custom settings which were set in this {@link ProcessingConfiguration} (but doesn't unset
2084 * those inherited from a parent {@link ProcessingConfiguration}).
2085 */
2086 public void unsetAllCustomSettings() {
2087 customSettings = Collections.emptyMap();
2088 customSettingsModifiable = false;
2089 }
2090
2091 protected final List<String> parseAsList(String text) throws GenericParseException {
2092 return new SettingStringParser(text).parseAsList();
2093 }
2094
2095 protected final List<_KeyValuePair<String, List<String>>> parseAsSegmentedList(String text)
2096 throws GenericParseException {
2097 return new SettingStringParser(text).parseAsSegmentedList();
2098 }
2099
2100 private final HashMap parseAsImportList(String text) throws GenericParseException {
2101 return new SettingStringParser(text).parseAsImportList();
2102 }
2103
2104 @SuppressWarnings("unchecked")
2105 protected SelfT self() {
2106 return (SelfT) this;
2107 }
2108
2109 /**
2110 * Helper class for parsing setting values given with string.
2111 */
2112 private static class SettingStringParser {
2113 private String text;
2114 private int p;
2115 private int ln;
2116
2117 private SettingStringParser(String text) {
2118 this.text = text;
2119 p = 0;
2120 ln = text.length();
2121 }
2122
2123 List<_KeyValuePair<String, List<String>>> parseAsSegmentedList() throws GenericParseException {
2124 List<_KeyValuePair<String, List<String>>> segments = new ArrayList();
2125 List<String> currentSegment = null;
2126
2127 char c;
2128 while (true) {
2129 c = skipWS();
2130 if (c == ' ') break;
2131 String item = fetchStringValue();
2132 c = skipWS();
2133
2134 if (c == ':') {
2135 currentSegment = new ArrayList();
2136 segments.add(new _KeyValuePair<>(item, currentSegment));
2137 } else {
2138 if (currentSegment == null) {
2139 throw new GenericParseException(
2140 "The very first list item must be followed by \":\" so " +
2141 "it will be the key for the following sub-list.");
2142 }
2143 currentSegment.add(item);
2144 }
2145
2146 if (c == ' ') break;
2147 if (c != ',' && c != ':') throw new GenericParseException(
2148 "Expected \",\" or \":\" or the end of text but " +
2149 "found \"" + c + "\"");
2150 p++;
2151 }
2152 return segments;
2153 }
2154
2155 ArrayList parseAsList() throws GenericParseException {
2156 char c;
2157 ArrayList seq = new ArrayList();
2158 while (true) {
2159 c = skipWS();
2160 if (c == ' ') break;
2161 seq.add(fetchStringValue());
2162 c = skipWS();
2163 if (c == ' ') break;
2164 if (c != ',') throw new GenericParseException(
2165 "Expected \",\" or the end of text but " +
2166 "found \"" + c + "\"");
2167 p++;
2168 }
2169 return seq;
2170 }
2171
2172 HashMap parseAsImportList() throws GenericParseException {
2173 char c;
2174 HashMap map = new HashMap();
2175 while (true) {
2176 c = skipWS();
2177 if (c == ' ') break;
2178 String lib = fetchStringValue();
2179
2180 c = skipWS();
2181 if (c == ' ') throw new GenericParseException(
2182 "Unexpected end of text: expected \"as\"");
2183 String s = fetchKeyword();
2184 if (!s.equalsIgnoreCase("as")) throw new GenericParseException(
2185 "Expected \"as\", but found " + _StringUtils.jQuote(s));
2186
2187 c = skipWS();
2188 if (c == ' ') throw new GenericParseException(
2189 "Unexpected end of text: expected gate hash name");
2190 String ns = fetchStringValue();
2191
2192 map.put(ns, lib);
2193
2194 c = skipWS();
2195 if (c == ' ') break;
2196 if (c != ',') throw new GenericParseException(
2197 "Expected \",\" or the end of text but "
2198 + "found \"" + c + "\"");
2199 p++;
2200 }
2201 return map;
2202 }
2203
2204 String fetchStringValue() throws GenericParseException {
2205 String w = fetchWord();
2206 if (w.startsWith("'") || w.startsWith("\"")) {
2207 w = w.substring(1, w.length() - 1);
2208 }
2209 return TemplateLanguageUtils.unescapeStringLiteralPart(w);
2210 }
2211
2212 String fetchKeyword() throws GenericParseException {
2213 String w = fetchWord();
2214 if (w.startsWith("'") || w.startsWith("\"")) {
2215 throw new GenericParseException(
2216 "Keyword expected, but a string value found: " + w);
2217 }
2218 return w;
2219 }
2220
2221 char skipWS() {
2222 char c;
2223 while (p < ln) {
2224 c = text.charAt(p);
2225 if (!Character.isWhitespace(c)) return c;
2226 p++;
2227 }
2228 return ' ';
2229 }
2230
2231 private String fetchWord() throws GenericParseException {
2232 if (p == ln) throw new GenericParseException(
2233 "Unexpeced end of text");
2234
2235 char c = text.charAt(p);
2236 int b = p;
2237 if (c == '\'' || c == '"') {
2238 boolean escaped = false;
2239 char q = c;
2240 p++;
2241 while (p < ln) {
2242 c = text.charAt(p);
2243 if (!escaped) {
2244 if (c == '\\') {
2245 escaped = true;
2246 } else if (c == q) {
2247 break;
2248 }
2249 } else {
2250 escaped = false;
2251 }
2252 p++;
2253 }
2254 if (p == ln) {
2255 throw new GenericParseException("Missing " + q);
2256 }
2257 p++;
2258 return text.substring(b, p);
2259 } else {
2260 do {
2261 c = text.charAt(p);
2262 if (!(Character.isLetterOrDigit(c)
2263 || c == '/' || c == '\\' || c == '_'
2264 || c == '.' || c == '-' || c == '!'
2265 || c == '*' || c == '?')) break;
2266 p++;
2267 } while (p < ln);
2268 if (b == p) {
2269 throw new GenericParseException("Unexpected character: " + c);
2270 } else {
2271 return text.substring(b, p);
2272 }
2273 }
2274 }
2275 }
2276
2277 }