You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/05/14 10:53:14 UTC
[31/51] [partial] incubator-freemarker git commit: Migrated from Ant
to Gradle, and modularized the project. This is an incomplete migration;
there are some TODO-s in the build scripts, and release related tasks are
still missing. What works: Building th
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
new file mode 100644
index 0000000..a8fc5ae
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
@@ -0,0 +1,991 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core;
+
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+import org.apache.freemarker.core.util.CommonBuilder;
+import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
+
+/**
+ * A partial set of configuration settings used for customizing the {@link Configuration}-level settings for individual
+ * {@link Template}-s (or rather, for a group of templates). That it's partial means that you should call the
+ * corresponding {@code isXxxSet()} before getting a settings, or else you may cause
+ * {@link SettingValueNotSetException}. (The fallback to the {@link Configuration} setting isn't automatic to keep
+ * the dependency graph of configuration related beans non-cyclic. As user code seldom reads settings from here anyway,
+ * this compromise was chosen.)
+ * <p>
+ * Note on the {@code locale} setting: When used with the standard template loading/caching mechanism ({@link
+ * Configuration#getTemplate(String)} and its overloads), localized lookup happens before the {@code locale} specified
+ * here could have effect. The {@code locale} will be only set in the template that the localized lookup has already
+ * found.
+ * <p>
+ * This class is immutable. Use {@link TemplateConfiguration.Builder} to create a new instance.
+ *
+ * @see Template#Template(String, String, Reader, Configuration, TemplateConfiguration, Charset)
+ */
+public final class TemplateConfiguration implements ParsingAndProcessingConfiguration {
+
+ private final Locale locale;
+ private final String numberFormat;
+ private final String timeFormat;
+ private final String dateFormat;
+ private final String dateTimeFormat;
+ private final TimeZone timeZone;
+ private final TimeZone sqlDateAndTimeTimeZone;
+ private final boolean sqlDateAndTimeTimeZoneSet;
+ private final String booleanFormat;
+ private final TemplateExceptionHandler templateExceptionHandler;
+ private final ArithmeticEngine arithmeticEngine;
+ private final ObjectWrapper objectWrapper;
+ private final Charset outputEncoding;
+ private final boolean outputEncodingSet;
+ private final Charset urlEscapingCharset;
+ private final boolean urlEscapingCharsetSet;
+ private final Boolean autoFlush;
+ private final TemplateClassResolver newBuiltinClassResolver;
+ private final Boolean showErrorTips;
+ private final Boolean apiBuiltinEnabled;
+ private final Boolean logTemplateExceptions;
+ private final Map<String, TemplateDateFormatFactory> customDateFormats;
+ private final Map<String, TemplateNumberFormatFactory> customNumberFormats;
+ private final Map<String, String> autoImports;
+ private final List<String> autoIncludes;
+ private final Boolean lazyImports;
+ private final Boolean lazyAutoImports;
+ private final boolean lazyAutoImportsSet;
+ private final Map<Object, Object> customAttributes;
+
+ private final TemplateLanguage templateLanguage;
+ private final Integer tagSyntax;
+ private final Integer namingConvention;
+ private final Boolean whitespaceStripping;
+ private final Integer autoEscapingPolicy;
+ private final Boolean recognizeStandardFileExtensions;
+ private final OutputFormat outputFormat;
+ private final Charset sourceEncoding;
+ private final Integer tabSize;
+
+ private TemplateConfiguration(Builder builder) {
+ locale = builder.isLocaleSet() ? builder.getLocale() : null;
+ numberFormat = builder.isNumberFormatSet() ? builder.getNumberFormat() : null;
+ timeFormat = builder.isTimeFormatSet() ? builder.getTimeFormat() : null;
+ dateFormat = builder.isDateFormatSet() ? builder.getDateFormat() : null;
+ dateTimeFormat = builder.isDateTimeFormatSet() ? builder.getDateTimeFormat() : null;
+ timeZone = builder.isTimeZoneSet() ? builder.getTimeZone() : null;
+ sqlDateAndTimeTimeZoneSet = builder.isSQLDateAndTimeTimeZoneSet();
+ sqlDateAndTimeTimeZone = sqlDateAndTimeTimeZoneSet ? builder.getSQLDateAndTimeTimeZone() : null;
+ booleanFormat = builder.isBooleanFormatSet() ? builder.getBooleanFormat() : null;
+ templateExceptionHandler = builder.isTemplateExceptionHandlerSet() ? builder.getTemplateExceptionHandler() : null;
+ arithmeticEngine = builder.isArithmeticEngineSet() ? builder.getArithmeticEngine() : null;
+ objectWrapper = builder.isObjectWrapperSet() ? builder.getObjectWrapper() : null;
+ outputEncodingSet = builder.isOutputEncodingSet();
+ outputEncoding = outputEncodingSet ? builder.getOutputEncoding() : null;
+ urlEscapingCharsetSet = builder.isURLEscapingCharsetSet();
+ urlEscapingCharset = urlEscapingCharsetSet ? builder.getURLEscapingCharset() : null;
+ autoFlush = builder.isAutoFlushSet() ? builder.getAutoFlush() : null;
+ newBuiltinClassResolver = builder.isNewBuiltinClassResolverSet() ? builder.getNewBuiltinClassResolver() : null;
+ showErrorTips = builder.isShowErrorTipsSet() ? builder.getShowErrorTips() : null;
+ apiBuiltinEnabled = builder.isAPIBuiltinEnabledSet() ? builder.getAPIBuiltinEnabled() : null;
+ logTemplateExceptions = builder.isLogTemplateExceptionsSet() ? builder.getLogTemplateExceptions() : null;
+ customDateFormats = builder.isCustomDateFormatsSet() ? builder.getCustomDateFormats() : null;
+ customNumberFormats = builder.isCustomNumberFormatsSet() ? builder.getCustomNumberFormats() : null;
+ autoImports = builder.isAutoImportsSet() ? builder.getAutoImports() : null;
+ autoIncludes = builder.isAutoIncludesSet() ? builder.getAutoIncludes() : null;
+ lazyImports = builder.isLazyImportsSet() ? builder.getLazyImports() : null;
+ lazyAutoImportsSet = builder.isLazyAutoImportsSet();
+ lazyAutoImports = lazyAutoImportsSet ? builder.getLazyAutoImports() : null;
+ customAttributes = builder.isCustomAttributesSet() ? builder.getCustomAttributes() : null;
+
+ templateLanguage = builder.isTemplateLanguageSet() ? builder.getTemplateLanguage() : null;
+ tagSyntax = builder.isTagSyntaxSet() ? builder.getTagSyntax() : null;
+ namingConvention = builder.isNamingConventionSet() ? builder.getNamingConvention() : null;
+ whitespaceStripping = builder.isWhitespaceStrippingSet() ? builder.getWhitespaceStripping() : null;
+ autoEscapingPolicy = builder.isAutoEscapingPolicySet() ? builder.getAutoEscapingPolicy() : null;
+ recognizeStandardFileExtensions = builder.isRecognizeStandardFileExtensionsSet() ? builder.getRecognizeStandardFileExtensions() : null;
+ outputFormat = builder.isOutputFormatSet() ? builder.getOutputFormat() : null;
+ sourceEncoding = builder.isSourceEncodingSet() ? builder.getSourceEncoding() : null;
+ tabSize = builder.isTabSizeSet() ? builder.getTabSize() : null;
+ }
+
+ private static <K,V> Map<K,V> mergeMaps(Map<K,V> m1, Map<K,V> m2, boolean overwriteUpdatesOrder) {
+ if (m1 == null) return m2;
+ if (m2 == null) return m1;
+ if (m1.isEmpty()) return m2;
+ if (m2.isEmpty()) return m1;
+
+ LinkedHashMap<K, V> mergedM = new LinkedHashMap<>((m1.size() + m2.size()) * 4 / 3 + 1, 0.75f);
+ mergedM.putAll(m1);
+ if (overwriteUpdatesOrder) {
+ for (K m2Key : m2.keySet()) {
+ mergedM.remove(m2Key); // So that duplicate keys are moved after m1 keys
+ }
+ }
+ mergedM.putAll(m2);
+ return mergedM;
+ }
+
+ private static List<String> mergeLists(List<String> list1, List<String> list2) {
+ if (list1 == null) return list2;
+ if (list2 == null) return list1;
+ if (list1.isEmpty()) return list2;
+ if (list2.isEmpty()) return list1;
+
+ ArrayList<String> mergedList = new ArrayList<>(list1.size() + list2.size());
+ mergedList.addAll(list1);
+ mergedList.addAll(list2);
+ return mergedList;
+ }
+
+ /**
+ * For internal usage only, copies the custom attributes set directly on this objects into another
+ * {@link MutableProcessingConfiguration}. The target {@link MutableProcessingConfiguration} is assumed to be not seen be other thread than the current
+ * one yet. (That is, the operation is not synchronized on the target {@link MutableProcessingConfiguration}, only on the source
+ * {@link MutableProcessingConfiguration})
+ *
+ * @since 2.3.24
+ */
+ private void copyDirectCustomAttributes(MutableProcessingConfiguration<?> target, boolean overwriteExisting) {
+ if (customAttributes == null) {
+ return;
+ }
+ for (Map.Entry<?, ?> custAttrEnt : customAttributes.entrySet()) {
+ Object custAttrKey = custAttrEnt.getKey();
+ if (overwriteExisting || !target.isCustomAttributeSet(custAttrKey)) {
+ target.setCustomAttribute(custAttrKey, custAttrEnt.getValue());
+ }
+ }
+ }
+
+ @Override
+ public int getTagSyntax() {
+ if (!isTagSyntaxSet()) {
+ throw new SettingValueNotSetException("tagSyntax");
+ }
+ return tagSyntax;
+ }
+
+ @Override
+ public boolean isTagSyntaxSet() {
+ return tagSyntax != null;
+ }
+
+ @Override
+ public TemplateLanguage getTemplateLanguage() {
+ if (!isTemplateLanguageSet()) {
+ throw new SettingValueNotSetException("templateLanguage");
+ }
+ return templateLanguage;
+ }
+
+ @Override
+ public boolean isTemplateLanguageSet() {
+ return templateLanguage != null;
+ }
+
+ @Override
+ public int getNamingConvention() {
+ if (!isNamingConventionSet()) {
+ throw new SettingValueNotSetException("namingConvention");
+ }
+ return namingConvention;
+ }
+
+ @Override
+ public boolean isNamingConventionSet() {
+ return namingConvention != null;
+ }
+
+ @Override
+ public boolean getWhitespaceStripping() {
+ if (!isWhitespaceStrippingSet()) {
+ throw new SettingValueNotSetException("whitespaceStripping");
+ }
+ return whitespaceStripping;
+ }
+
+ @Override
+ public boolean isWhitespaceStrippingSet() {
+ return whitespaceStripping != null;
+ }
+
+ @Override
+ public int getAutoEscapingPolicy() {
+ if (!isAutoEscapingPolicySet()) {
+ throw new SettingValueNotSetException("autoEscapingPolicy");
+ }
+ return autoEscapingPolicy;
+ }
+
+ @Override
+ public boolean isAutoEscapingPolicySet() {
+ return autoEscapingPolicy != null;
+ }
+
+ @Override
+ public OutputFormat getOutputFormat() {
+ if (!isOutputFormatSet()) {
+ throw new SettingValueNotSetException("outputFormat");
+ }
+ return outputFormat;
+ }
+
+ @Override
+ public ArithmeticEngine getArithmeticEngine() {
+ if (!isArithmeticEngineSet()) {
+ throw new SettingValueNotSetException("arithmeticEngine");
+ }
+ return arithmeticEngine;
+ }
+
+ @Override
+ public boolean isArithmeticEngineSet() {
+ return arithmeticEngine != null;
+ }
+
+ @Override
+ public boolean isOutputFormatSet() {
+ return outputFormat != null;
+ }
+
+ @Override
+ public boolean getRecognizeStandardFileExtensions() {
+ if (!isRecognizeStandardFileExtensionsSet()) {
+ throw new SettingValueNotSetException("recognizeStandardFileExtensions");
+ }
+ return recognizeStandardFileExtensions;
+ }
+
+ @Override
+ public boolean isRecognizeStandardFileExtensionsSet() {
+ return recognizeStandardFileExtensions != null;
+ }
+
+ @Override
+ public Charset getSourceEncoding() {
+ if (!isSourceEncodingSet()) {
+ throw new SettingValueNotSetException("sourceEncoding");
+ }
+ return sourceEncoding;
+ }
+
+ @Override
+ public boolean isSourceEncodingSet() {
+ return sourceEncoding != null;
+ }
+
+ @Override
+ public int getTabSize() {
+ if (!isTabSizeSet()) {
+ throw new SettingValueNotSetException("tabSize");
+ }
+ return tabSize;
+ }
+
+ @Override
+ public boolean isTabSizeSet() {
+ return tabSize != null;
+ }
+
+ /**
+ * Always throws {@link SettingValueNotSetException}, as this can't be set on the {@link TemplateConfiguration}
+ * level.
+ */
+ @Override
+ public Version getIncompatibleImprovements() {
+ throw new SettingValueNotSetException("incompatibleImprovements");
+ }
+
+ @Override
+ public Locale getLocale() {
+ if (!isLocaleSet()) {
+ throw new SettingValueNotSetException("locale");
+ }
+ return locale;
+ }
+
+ @Override
+ public boolean isLocaleSet() {
+ return locale != null;
+ }
+
+ @Override
+ public TimeZone getTimeZone() {
+ if (!isTimeZoneSet()) {
+ throw new SettingValueNotSetException("timeZone");
+ }
+ return timeZone;
+ }
+
+ @Override
+ public boolean isTimeZoneSet() {
+ return timeZone != null;
+ }
+
+ @Override
+ public TimeZone getSQLDateAndTimeTimeZone() {
+ if (!isSQLDateAndTimeTimeZoneSet()) {
+ throw new SettingValueNotSetException("sqlDateAndTimeTimeZone");
+ }
+ return sqlDateAndTimeTimeZone;
+ }
+
+ @Override
+ public boolean isSQLDateAndTimeTimeZoneSet() {
+ return sqlDateAndTimeTimeZoneSet;
+ }
+
+ @Override
+ public String getNumberFormat() {
+ if (!isNumberFormatSet()) {
+ throw new SettingValueNotSetException("numberFormat");
+ }
+ return numberFormat;
+ }
+
+ @Override
+ public boolean isNumberFormatSet() {
+ return numberFormat != null;
+ }
+
+ @Override
+ public Map<String, TemplateNumberFormatFactory> getCustomNumberFormats() {
+ if (!isCustomNumberFormatsSet()) {
+ throw new SettingValueNotSetException("customNumberFormats");
+ }
+ return customNumberFormats;
+ }
+
+ @Override
+ public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
+ return getCustomNumberFormats().get(name);
+ }
+
+ @Override
+ public boolean isCustomNumberFormatsSet() {
+ return customNumberFormats != null;
+ }
+
+ @Override
+ public String getBooleanFormat() {
+ if (!isBooleanFormatSet()) {
+ throw new SettingValueNotSetException("booleanFormat");
+ }
+ return booleanFormat;
+ }
+
+ @Override
+ public boolean isBooleanFormatSet() {
+ return booleanFormat != null;
+ }
+
+ @Override
+ public String getTimeFormat() {
+ if (!isTimeFormatSet()) {
+ throw new SettingValueNotSetException("timeFormat");
+ }
+ return timeFormat;
+ }
+
+ @Override
+ public boolean isTimeFormatSet() {
+ return timeFormat != null;
+ }
+
+ @Override
+ public String getDateFormat() {
+ if (!isDateFormatSet()) {
+ throw new SettingValueNotSetException("dateFormat");
+ }
+ return dateFormat;
+ }
+
+ @Override
+ public boolean isDateFormatSet() {
+ return dateFormat != null;
+ }
+
+ @Override
+ public String getDateTimeFormat() {
+ if (!isDateTimeFormatSet()) {
+ throw new SettingValueNotSetException("dateTimeFormat");
+ }
+ return dateTimeFormat;
+ }
+
+ @Override
+ public boolean isDateTimeFormatSet() {
+ return dateTimeFormat != null;
+ }
+
+ @Override
+ public Map<String, TemplateDateFormatFactory> getCustomDateFormats() {
+ if (!isCustomDateFormatsSet()) {
+ throw new SettingValueNotSetException("customDateFormats");
+ }
+ return customDateFormats;
+ }
+
+ @Override
+ public TemplateDateFormatFactory getCustomDateFormat(String name) {
+ if (isCustomDateFormatsSet()) {
+ TemplateDateFormatFactory format = customDateFormats.get(name);
+ if (format != null) {
+ return format;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isCustomDateFormatsSet() {
+ return customDateFormats != null;
+ }
+
+ @Override
+ public TemplateExceptionHandler getTemplateExceptionHandler() {
+ if (!isTemplateExceptionHandlerSet()) {
+ throw new SettingValueNotSetException("templateExceptionHandler");
+ }
+ return templateExceptionHandler;
+ }
+
+ @Override
+ public boolean isTemplateExceptionHandlerSet() {
+ return templateExceptionHandler != null;
+ }
+
+ @Override
+ public ObjectWrapper getObjectWrapper() {
+ if (!isObjectWrapperSet()) {
+ throw new SettingValueNotSetException("objectWrapper");
+ }
+ return objectWrapper;
+ }
+
+ @Override
+ public boolean isObjectWrapperSet() {
+ return objectWrapper != null;
+ }
+
+ @Override
+ public Charset getOutputEncoding() {
+ if (!isOutputEncodingSet()) {
+ throw new SettingValueNotSetException("");
+ }
+ return outputEncoding;
+ }
+
+ @Override
+ public boolean isOutputEncodingSet() {
+ return outputEncodingSet;
+ }
+
+ @Override
+ public Charset getURLEscapingCharset() {
+ if (!isURLEscapingCharsetSet()) {
+ throw new SettingValueNotSetException("urlEscapingCharset");
+ }
+ return urlEscapingCharset;
+ }
+
+ @Override
+ public boolean isURLEscapingCharsetSet() {
+ return urlEscapingCharsetSet;
+ }
+
+ @Override
+ public TemplateClassResolver getNewBuiltinClassResolver() {
+ if (!isNewBuiltinClassResolverSet()) {
+ throw new SettingValueNotSetException("newBuiltinClassResolver");
+ }
+ return newBuiltinClassResolver;
+ }
+
+ @Override
+ public boolean isNewBuiltinClassResolverSet() {
+ return newBuiltinClassResolver != null;
+ }
+
+ @Override
+ public boolean getAPIBuiltinEnabled() {
+ if (!isAPIBuiltinEnabledSet()) {
+ throw new SettingValueNotSetException("apiBuiltinEnabled");
+ }
+ return apiBuiltinEnabled;
+ }
+
+ @Override
+ public boolean isAPIBuiltinEnabledSet() {
+ return apiBuiltinEnabled != null;
+ }
+
+ @Override
+ public boolean getAutoFlush() {
+ if (!isAutoFlushSet()) {
+ throw new SettingValueNotSetException("autoFlush");
+ }
+ return autoFlush;
+ }
+
+ @Override
+ public boolean isAutoFlushSet() {
+ return autoFlush != null;
+ }
+
+ @Override
+ public boolean getShowErrorTips() {
+ if (!isShowErrorTipsSet()) {
+ throw new SettingValueNotSetException("showErrorTips");
+ }
+ return showErrorTips;
+ }
+
+ @Override
+ public boolean isShowErrorTipsSet() {
+ return showErrorTips != null;
+ }
+
+ @Override
+ public boolean getLogTemplateExceptions() {
+ if (!isLogTemplateExceptionsSet()) {
+ throw new SettingValueNotSetException("logTemplateExceptions");
+ }
+ return logTemplateExceptions;
+ }
+
+ @Override
+ public boolean isLogTemplateExceptionsSet() {
+ return logTemplateExceptions != null;
+ }
+
+ @Override
+ public boolean getLazyImports() {
+ if (!isLazyImportsSet()) {
+ throw new SettingValueNotSetException("lazyImports");
+ }
+ return lazyImports;
+ }
+
+ @Override
+ public boolean isLazyImportsSet() {
+ return lazyImports != null;
+ }
+
+ @Override
+ public Boolean getLazyAutoImports() {
+ if (!isLazyAutoImportsSet()) {
+ throw new SettingValueNotSetException("lazyAutoImports");
+ }
+ return lazyAutoImports;
+ }
+
+ @Override
+ public boolean isLazyAutoImportsSet() {
+ return lazyAutoImportsSet;
+ }
+
+ @Override
+ public Map<String, String> getAutoImports() {
+ if (!isAutoImportsSet()) {
+ throw new SettingValueNotSetException("");
+ }
+ return autoImports;
+ }
+
+ @Override
+ public boolean isAutoImportsSet() {
+ return autoImports != null;
+ }
+
+ @Override
+ public List<String> getAutoIncludes() {
+ if (!isAutoIncludesSet()) {
+ throw new SettingValueNotSetException("autoIncludes");
+ }
+ return autoIncludes;
+ }
+
+ @Override
+ public boolean isAutoIncludesSet() {
+ return autoIncludes != null;
+ }
+
+ @Override
+ public Map<Object, Object> getCustomAttributes() {
+ if (!isCustomAttributesSet()) {
+ throw new SettingValueNotSetException("customAttributes");
+ }
+ return customAttributes;
+ }
+
+ @Override
+ public boolean isCustomAttributesSet() {
+ return customAttributes != null;
+ }
+
+ @Override
+ public Object getCustomAttribute(Object name) {
+ Object attValue;
+ if (isCustomAttributesSet()) {
+ attValue = customAttributes.get(name);
+ if (attValue != null || customAttributes.containsKey(name)) {
+ return attValue;
+ }
+ }
+ return null;
+ }
+
+ public static final class Builder extends MutableParsingAndProcessingConfiguration<Builder>
+ implements CommonBuilder<TemplateConfiguration> {
+
+ public Builder() {
+ super();
+ }
+
+ @Override
+ public TemplateConfiguration build() {
+ return new TemplateConfiguration(this);
+ }
+
+ @Override
+ protected Locale getDefaultLocale() {
+ throw new SettingValueNotSetException("locale");
+ }
+
+ @Override
+ protected TimeZone getDefaultTimeZone() {
+ throw new SettingValueNotSetException("timeZone");
+ }
+
+ @Override
+ protected TimeZone getDefaultSQLDateAndTimeTimeZone() {
+ throw new SettingValueNotSetException("SQLDateAndTimeTimeZone");
+ }
+
+ @Override
+ protected String getDefaultNumberFormat() {
+ throw new SettingValueNotSetException("numberFormat");
+ }
+
+ @Override
+ protected Map<String, TemplateNumberFormatFactory> getDefaultCustomNumberFormats() {
+ throw new SettingValueNotSetException("customNumberFormats");
+ }
+
+ @Override
+ protected TemplateNumberFormatFactory getDefaultCustomNumberFormat(String name) {
+ return null;
+ }
+
+ @Override
+ protected String getDefaultBooleanFormat() {
+ throw new SettingValueNotSetException("booleanFormat");
+ }
+
+ @Override
+ protected String getDefaultTimeFormat() {
+ throw new SettingValueNotSetException("timeFormat");
+ }
+
+ @Override
+ protected String getDefaultDateFormat() {
+ throw new SettingValueNotSetException("dateFormat");
+ }
+
+ @Override
+ protected String getDefaultDateTimeFormat() {
+ throw new SettingValueNotSetException("dateTimeFormat");
+ }
+
+ @Override
+ protected Map<String, TemplateDateFormatFactory> getDefaultCustomDateFormats() {
+ throw new SettingValueNotSetException("customDateFormats");
+ }
+
+ @Override
+ protected TemplateDateFormatFactory getDefaultCustomDateFormat(String name) {
+ throw new SettingValueNotSetException("customDateFormat");
+ }
+
+ @Override
+ protected TemplateExceptionHandler getDefaultTemplateExceptionHandler() {
+ throw new SettingValueNotSetException("templateExceptionHandler");
+ }
+
+ @Override
+ protected ArithmeticEngine getDefaultArithmeticEngine() {
+ throw new SettingValueNotSetException("arithmeticEngine");
+ }
+
+ @Override
+ protected ObjectWrapper getDefaultObjectWrapper() {
+ throw new SettingValueNotSetException("objectWrapper");
+ }
+
+ @Override
+ protected Charset getDefaultOutputEncoding() {
+ throw new SettingValueNotSetException("outputEncoding");
+ }
+
+ @Override
+ protected Charset getDefaultURLEscapingCharset() {
+ throw new SettingValueNotSetException("URLEscapingCharset");
+ }
+
+ @Override
+ protected TemplateClassResolver getDefaultNewBuiltinClassResolver() {
+ throw new SettingValueNotSetException("newBuiltinClassResolver");
+ }
+
+ @Override
+ protected boolean getDefaultAutoFlush() {
+ throw new SettingValueNotSetException("autoFlush");
+ }
+
+ @Override
+ protected boolean getDefaultShowErrorTips() {
+ throw new SettingValueNotSetException("showErrorTips");
+ }
+
+ @Override
+ protected boolean getDefaultAPIBuiltinEnabled() {
+ throw new SettingValueNotSetException("APIBuiltinEnabled");
+ }
+
+ @Override
+ protected boolean getDefaultLogTemplateExceptions() {
+ throw new SettingValueNotSetException("logTemplateExceptions");
+ }
+
+ @Override
+ protected boolean getDefaultLazyImports() {
+ throw new SettingValueNotSetException("lazyImports");
+ }
+
+ @Override
+ protected Boolean getDefaultLazyAutoImports() {
+ throw new SettingValueNotSetException("lazyAutoImports");
+ }
+
+ @Override
+ protected Map<String, String> getDefaultAutoImports() {
+ throw new SettingValueNotSetException("autoImports");
+ }
+
+ @Override
+ protected List<String> getDefaultAutoIncludes() {
+ throw new SettingValueNotSetException("autoIncludes");
+ }
+
+ @Override
+ protected Object getDefaultCustomAttribute(Object name) {
+ return null;
+ }
+
+ @Override
+ protected Map<Object, Object> getDefaultCustomAttributes() {
+ throw new SettingValueNotSetException("customAttributes");
+ }
+
+ /**
+ * Set all settings in this {@link Builder} that were set in the parameter
+ * {@link TemplateConfiguration}, possibly overwriting the earlier value in this object. (A setting is said to be
+ * set in a {@link TemplateConfiguration} if it was explicitly set via a setter method, as opposed to be inherited.)
+ */
+ public void merge(ParsingAndProcessingConfiguration tc) {
+ if (tc.isAPIBuiltinEnabledSet()) {
+ setAPIBuiltinEnabled(tc.getAPIBuiltinEnabled());
+ }
+ if (tc.isArithmeticEngineSet()) {
+ setArithmeticEngine(tc.getArithmeticEngine());
+ }
+ if (tc.isAutoEscapingPolicySet()) {
+ setAutoEscapingPolicy(tc.getAutoEscapingPolicy());
+ }
+ if (tc.isAutoFlushSet()) {
+ setAutoFlush(tc.getAutoFlush());
+ }
+ if (tc.isBooleanFormatSet()) {
+ setBooleanFormat(tc.getBooleanFormat());
+ }
+ if (tc.isCustomDateFormatsSet()) {
+ setCustomDateFormats(mergeMaps(
+ isCustomDateFormatsSet() ? getCustomDateFormats() : null, tc.getCustomDateFormats(), false));
+ }
+ if (tc.isCustomNumberFormatsSet()) {
+ setCustomNumberFormats(mergeMaps(
+ isCustomNumberFormatsSet() ? getCustomNumberFormats() : null, tc.getCustomNumberFormats(), false));
+ }
+ if (tc.isDateFormatSet()) {
+ setDateFormat(tc.getDateFormat());
+ }
+ if (tc.isDateTimeFormatSet()) {
+ setDateTimeFormat(tc.getDateTimeFormat());
+ }
+ if (tc.isSourceEncodingSet()) {
+ setSourceEncoding(tc.getSourceEncoding());
+ }
+ if (tc.isLocaleSet()) {
+ setLocale(tc.getLocale());
+ }
+ if (tc.isLogTemplateExceptionsSet()) {
+ setLogTemplateExceptions(tc.getLogTemplateExceptions());
+ }
+ if (tc.isNamingConventionSet()) {
+ setNamingConvention(tc.getNamingConvention());
+ }
+ if (tc.isNewBuiltinClassResolverSet()) {
+ setNewBuiltinClassResolver(tc.getNewBuiltinClassResolver());
+ }
+ if (tc.isNumberFormatSet()) {
+ setNumberFormat(tc.getNumberFormat());
+ }
+ if (tc.isObjectWrapperSet()) {
+ setObjectWrapper(tc.getObjectWrapper());
+ }
+ if (tc.isOutputEncodingSet()) {
+ setOutputEncoding(tc.getOutputEncoding());
+ }
+ if (tc.isOutputFormatSet()) {
+ setOutputFormat(tc.getOutputFormat());
+ }
+ if (tc.isRecognizeStandardFileExtensionsSet()) {
+ setRecognizeStandardFileExtensions(tc.getRecognizeStandardFileExtensions());
+ }
+ if (tc.isShowErrorTipsSet()) {
+ setShowErrorTips(tc.getShowErrorTips());
+ }
+ if (tc.isSQLDateAndTimeTimeZoneSet()) {
+ setSQLDateAndTimeTimeZone(tc.getSQLDateAndTimeTimeZone());
+ }
+ if (tc.isTagSyntaxSet()) {
+ setTagSyntax(tc.getTagSyntax());
+ }
+ if (tc.isTemplateLanguageSet()) {
+ setTemplateLanguage(tc.getTemplateLanguage());
+ }
+ if (tc.isTemplateExceptionHandlerSet()) {
+ setTemplateExceptionHandler(tc.getTemplateExceptionHandler());
+ }
+ if (tc.isTimeFormatSet()) {
+ setTimeFormat(tc.getTimeFormat());
+ }
+ if (tc.isTimeZoneSet()) {
+ setTimeZone(tc.getTimeZone());
+ }
+ if (tc.isURLEscapingCharsetSet()) {
+ setURLEscapingCharset(tc.getURLEscapingCharset());
+ }
+ if (tc.isWhitespaceStrippingSet()) {
+ setWhitespaceStripping(tc.getWhitespaceStripping());
+ }
+ if (tc.isTabSizeSet()) {
+ setTabSize(tc.getTabSize());
+ }
+ if (tc.isLazyImportsSet()) {
+ setLazyImports(tc.getLazyImports());
+ }
+ if (tc.isLazyAutoImportsSet()) {
+ setLazyAutoImports(tc.getLazyAutoImports());
+ }
+ if (tc.isAutoImportsSet()) {
+ setAutoImports(mergeMaps(
+ isAutoImportsSet() ? getAutoImports() : null,
+ tc.isAutoImportsSet() ? tc.getAutoImports() : null,
+ true));
+ }
+ if (tc.isAutoIncludesSet()) {
+ setAutoIncludes(mergeLists(
+ isAutoIncludesSet() ? getAutoIncludes() : null,
+ tc.isAutoIncludesSet() ? tc.getAutoIncludes() : null));
+ }
+
+ if (tc.isCustomAttributesSet()) {
+ setCustomAttributesWithoutCopying(mergeMaps(
+ isCustomAttributesSet() ? getCustomAttributes() : null,
+ tc.isCustomAttributesSet() ? tc.getCustomAttributes() : null,
+ true));
+ }
+ }
+
+ @Override
+ public Version getIncompatibleImprovements() {
+ throw new SettingValueNotSetException("incompatibleImprovements");
+ }
+
+ @Override
+ protected int getDefaultTagSyntax() {
+ throw new SettingValueNotSetException("tagSyntax");
+ }
+
+ @Override
+ protected TemplateLanguage getDefaultTemplateLanguage() {
+ throw new SettingValueNotSetException("templateLanguage");
+ }
+
+ @Override
+ protected int getDefaultNamingConvention() {
+ throw new SettingValueNotSetException("namingConvention");
+ }
+
+ @Override
+ protected boolean getDefaultWhitespaceStripping() {
+ throw new SettingValueNotSetException("whitespaceStripping");
+ }
+
+ @Override
+ protected int getDefaultAutoEscapingPolicy() {
+ throw new SettingValueNotSetException("autoEscapingPolicy");
+ }
+
+ @Override
+ protected OutputFormat getDefaultOutputFormat() {
+ throw new SettingValueNotSetException("outputFormat");
+ }
+
+ @Override
+ protected boolean getDefaultRecognizeStandardFileExtensions() {
+ throw new SettingValueNotSetException("recognizeStandardFileExtensions");
+ }
+
+ @Override
+ protected Charset getDefaultSourceEncoding() {
+ throw new SettingValueNotSetException("sourceEncoding");
+ }
+
+ @Override
+ protected int getDefaultTabSize() {
+ throw new SettingValueNotSetException("tabSize");
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java
new file mode 100644
index 0000000..f8fe66b
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core;
+
+import org.apache.freemarker.core.util._CollectionUtil;
+
+/**
+ * Holds an buffer (array) of {@link ASTElement}-s with the count of the utilized items in it. The un-utilized tail
+ * of the array must only contain {@code null}-s.
+ *
+ * @since 2.3.24
+ */
+class TemplateElements {
+
+ static final TemplateElements EMPTY = new TemplateElements(null, 0);
+
+ private final ASTElement[] buffer;
+ private final int count;
+
+ /**
+ * @param buffer
+ * The buffer; {@code null} exactly if {@code count} is 0.
+ * @param count
+ * The number of utilized buffer elements; if 0, then {@code null} must be {@code null}.
+ */
+ TemplateElements(ASTElement[] buffer, int count) {
+ /*
+ // Assertion:
+ if (count == 0 && buffer != null) {
+ throw new IllegalArgumentException();
+ }
+ */
+
+ this.buffer = buffer;
+ this.count = count;
+ }
+
+ ASTElement[] getBuffer() {
+ return buffer;
+ }
+
+ int getCount() {
+ return count;
+ }
+
+ ASTElement getFirst() {
+ return buffer != null ? buffer[0] : null;
+ }
+
+ ASTElement getLast() {
+ return buffer != null ? buffer[count - 1] : null;
+ }
+
+ /**
+ * Used for some backward compatibility hacks.
+ */
+ ASTElement asSingleElement() {
+ if (count == 0) {
+ return new ASTStaticText(_CollectionUtil.EMPTY_CHAR_ARRAY, false);
+ } else {
+ ASTElement first = buffer[0];
+ if (count == 1) {
+ return first;
+ } else {
+ ASTImplicitParent mixedContent = new ASTImplicitParent();
+ mixedContent.setChildren(this);
+ mixedContent.setLocation(first.getTemplate(), first, getLast());
+ return mixedContent;
+ }
+ }
+ }
+
+ /**
+ * Used for some backward compatibility hacks.
+ */
+ ASTImplicitParent asMixedContent() {
+ ASTImplicitParent mixedContent = new ASTImplicitParent();
+ if (count != 0) {
+ ASTElement first = buffer[0];
+ mixedContent.setChildren(this);
+ mixedContent.setLocation(first.getTemplate(), first, getLast());
+ }
+ return mixedContent;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java
new file mode 100644
index 0000000..9aaf0c7
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Used as the return value of {@link ASTElement#accept(Environment)} when the invoked element has nested elements
+ * to invoke. It would be more natural to invoke child elements before returning from
+ * {@link ASTElement#accept(Environment)}, however, if there's nothing to do after the child elements were invoked,
+ * that would mean wasting stack space.
+ *
+ * @since 2.3.24
+ */
+class TemplateElementsToVisit {
+
+ private final Collection<ASTElement> templateElements;
+
+ TemplateElementsToVisit(Collection<ASTElement> templateElements) {
+ this.templateElements = null != templateElements ? templateElements : Collections.<ASTElement> emptyList();
+ }
+
+ TemplateElementsToVisit(ASTElement nestedBlock) {
+ this(Collections.singleton(nestedBlock));
+ }
+
+ Collection<ASTElement> getTemplateElements() {
+ return templateElements;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateException.java
new file mode 100644
index 0000000..3ca9914
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateException.java
@@ -0,0 +1,655 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Method;
+
+import org.apache.freemarker.core.util._CollectionUtil;
+
+/**
+ * Runtime exception in a template (as opposed to a parsing-time exception: {@link ParseException}).
+ * It prints a special stack trace that contains the template-language stack trace along the usual Java stack trace.
+ */
+public class TemplateException extends Exception {
+
+ private static final String FTL_INSTRUCTION_STACK_TRACE_TITLE
+ = "FTL stack trace (\"~\" means nesting-related):";
+
+ // Set in constructor:
+ private transient _ErrorDescriptionBuilder descriptionBuilder;
+ private final transient Environment env;
+ private final transient ASTExpression blamedExpression;
+ private transient ASTElement[] ftlInstructionStackSnapshot;
+
+ // Calculated on demand:
+ private String renderedFtlInstructionStackSnapshot; // clalc. from ftlInstructionStackSnapshot
+ private String renderedFtlInstructionStackSnapshotTop; // clalc. from ftlInstructionStackSnapshot
+ private String description; // calc. from descriptionBuilder, or set by the construcor
+ private transient String messageWithoutStackTop;
+ private transient String message;
+ private boolean blamedExpressionStringCalculated;
+ private String blamedExpressionString;
+ private boolean positionsCalculated;
+ private String templateLookupName;
+ private String templateSourceName;
+ private Integer lineNumber;
+ private Integer columnNumber;
+ private Integer endLineNumber;
+ private Integer endColumnNumber;
+
+ // Concurrency:
+ private transient Object lock = new Object();
+ private transient ThreadLocal messageWasAlreadyPrintedForThisTrace;
+
+ /**
+ * Constructs a TemplateException with no specified detail message
+ * or underlying cause.
+ */
+ public TemplateException(Environment env) {
+ this(null, null, env);
+ }
+
+ /**
+ * Constructs a TemplateException with the given detail message,
+ * but no underlying cause exception.
+ *
+ * @param description the description of the error that occurred
+ */
+ public TemplateException(String description, Environment env) {
+ this(description, null, env);
+ }
+
+ /**
+ * The same as {@link #TemplateException(Throwable, Environment)}; it's exists only for binary
+ * backward-compatibility.
+ */
+ public TemplateException(Exception cause, Environment env) {
+ this(null, cause, env);
+ }
+
+ /**
+ * Constructs a TemplateException with the given underlying Exception,
+ * but no detail message.
+ *
+ * @param cause the underlying {@link Exception} that caused this
+ * exception to be raised
+ *
+ * @since 2.3.20
+ */
+ public TemplateException(Throwable cause, Environment env) {
+ this(null, cause, env);
+ }
+
+ /**
+ * The same as {@link #TemplateException(String, Throwable, Environment)}; it's exists only for binary
+ * backward-compatibility.
+ */
+ public TemplateException(String description, Exception cause, Environment env) {
+ this(description, cause, env, null, null);
+ }
+
+ /**
+ * Constructs a TemplateException with both a description of the error
+ * that occurred and the underlying Exception that caused this exception
+ * to be raised.
+ *
+ * @param description the description of the error that occurred
+ * @param cause the underlying {@link Exception} that caused this exception to be raised
+ *
+ * @since 2.3.20
+ */
+ public TemplateException(String description, Throwable cause, Environment env) {
+ this(description, cause, env, null, null);
+ }
+
+ /**
+ * Don't use this; this is to be used internally by FreeMarker. No backward compatibility guarantees.
+ *
+ * @param blamedExpr Maybe {@code null}. The FTL stack in the {@link Environment} only specifies the error location
+ * with "template element" granularity, and this can be used to point to the expression inside the
+ * template element.
+ */
+ protected TemplateException(Throwable cause, Environment env, ASTExpression blamedExpr,
+ _ErrorDescriptionBuilder descriptionBuilder) {
+ this(null, cause, env, blamedExpr, descriptionBuilder);
+ }
+
+ private TemplateException(
+ String renderedDescription,
+ Throwable cause,
+ Environment env, ASTExpression blamedExpression,
+ _ErrorDescriptionBuilder descriptionBuilder) {
+ // Note: Keep this constructor lightweight.
+
+ super(cause); // Message managed locally.
+
+ if (env == null) env = Environment.getCurrentEnvironment();
+ this.env = env;
+
+ this.blamedExpression = blamedExpression;
+
+ this.descriptionBuilder = descriptionBuilder;
+ description = renderedDescription;
+
+ if (env != null) {
+ ftlInstructionStackSnapshot = env.getInstructionStackSnapshot();
+ }
+ }
+
+ private void renderMessages() {
+ String description = getDescription();
+
+ if (description != null && description.length() != 0) {
+ messageWithoutStackTop = description;
+ } else if (getCause() != null) {
+ messageWithoutStackTop = "No error description was specified for this error; low-level message: "
+ + getCause().getClass().getName() + ": " + getCause().getMessage();
+ } else {
+ messageWithoutStackTop = "[No error description was available.]";
+ }
+
+ String stackTopFew = getFTLInstructionStackTopFew();
+ if (stackTopFew != null) {
+ message = messageWithoutStackTop + "\n\n"
+ + MessageUtil.ERROR_MESSAGE_HR + "\n"
+ + FTL_INSTRUCTION_STACK_TRACE_TITLE + "\n"
+ + stackTopFew
+ + MessageUtil.ERROR_MESSAGE_HR;
+ messageWithoutStackTop = message.substring(0, messageWithoutStackTop.length()); // to reuse backing char[]
+ } else {
+ message = messageWithoutStackTop;
+ }
+ }
+
+ private void calculatePosition() {
+ synchronized (lock) {
+ if (!positionsCalculated) {
+ // The expressions is the argument of the template element, so we prefer it as it's more specific.
+ ASTNode templateObject = blamedExpression != null
+ ? blamedExpression
+ : (
+ ftlInstructionStackSnapshot != null && ftlInstructionStackSnapshot.length != 0
+ ? ftlInstructionStackSnapshot[0] : null);
+ // Line number blow 0 means no info, negative means position in ?eval-ed value that we won't use here.
+ if (templateObject != null && templateObject.getBeginLine() > 0) {
+ final Template template = templateObject.getTemplate();
+ templateLookupName = template.getLookupName();
+ templateSourceName = template.getSourceName();
+ lineNumber = Integer.valueOf(templateObject.getBeginLine());
+ columnNumber = Integer.valueOf(templateObject.getBeginColumn());
+ endLineNumber = Integer.valueOf(templateObject.getEndLine());
+ endColumnNumber = Integer.valueOf(templateObject.getEndColumn());
+ }
+ positionsCalculated = true;
+ deleteFTLInstructionStackSnapshotIfNotNeeded();
+ }
+ }
+ }
+
+ /**
+ * Returns the snapshot of the FTL stack trace at the time this exception was created.
+ */
+ public String getFTLInstructionStack() {
+ synchronized (lock) {
+ if (ftlInstructionStackSnapshot != null || renderedFtlInstructionStackSnapshot != null) {
+ if (renderedFtlInstructionStackSnapshot == null) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ Environment.outputInstructionStack(ftlInstructionStackSnapshot, false, pw);
+ pw.close();
+ if (renderedFtlInstructionStackSnapshot == null) {
+ renderedFtlInstructionStackSnapshot = sw.toString();
+ deleteFTLInstructionStackSnapshotIfNotNeeded();
+ }
+ }
+ return renderedFtlInstructionStackSnapshot;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private String getFTLInstructionStackTopFew() {
+ synchronized (lock) {
+ if (ftlInstructionStackSnapshot != null || renderedFtlInstructionStackSnapshotTop != null) {
+ if (renderedFtlInstructionStackSnapshotTop == null) {
+ int stackSize = ftlInstructionStackSnapshot.length;
+ String s;
+ if (stackSize == 0) {
+ s = "";
+ } else {
+ StringWriter sw = new StringWriter();
+ Environment.outputInstructionStack(ftlInstructionStackSnapshot, true, sw);
+ s = sw.toString();
+ }
+ if (renderedFtlInstructionStackSnapshotTop == null) {
+ renderedFtlInstructionStackSnapshotTop = s;
+ deleteFTLInstructionStackSnapshotIfNotNeeded();
+ }
+ }
+ return renderedFtlInstructionStackSnapshotTop.length() != 0
+ ? renderedFtlInstructionStackSnapshotTop : null;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private void deleteFTLInstructionStackSnapshotIfNotNeeded() {
+ if (renderedFtlInstructionStackSnapshot != null && renderedFtlInstructionStackSnapshotTop != null
+ && (positionsCalculated || blamedExpression != null)) {
+ ftlInstructionStackSnapshot = null;
+ }
+
+ }
+
+ private String getDescription() {
+ synchronized (lock) {
+ if (description == null && descriptionBuilder != null) {
+ description = descriptionBuilder.toString(
+ getFailingInstruction(),
+ env != null ? env.getShowErrorTips() : true);
+ descriptionBuilder = null;
+ }
+ return description;
+ }
+ }
+
+ private ASTElement getFailingInstruction() {
+ if (ftlInstructionStackSnapshot != null && ftlInstructionStackSnapshot.length > 0) {
+ return ftlInstructionStackSnapshot[0];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @return the execution environment in which the exception occurred.
+ * {@code null} if the exception was deserialized.
+ */
+ public Environment getEnvironment() {
+ return env;
+ }
+
+ /**
+ * Overrides {@link Throwable#printStackTrace(PrintStream)} so that it will include the FTL stack trace.
+ */
+ @Override
+ public void printStackTrace(PrintStream out) {
+ printStackTrace(out, true, true, true);
+ }
+
+ /**
+ * Overrides {@link Throwable#printStackTrace(PrintWriter)} so that it will include the FTL stack trace.
+ */
+ @Override
+ public void printStackTrace(PrintWriter out) {
+ printStackTrace(out, true, true, true);
+ }
+
+ /**
+ * @param heading should the heading at the top be printed
+ * @param ftlStackTrace should the FTL stack trace be printed
+ * @param javaStackTrace should the Java stack trace be printed
+ *
+ * @since 2.3.20
+ */
+ public void printStackTrace(PrintWriter out, boolean heading, boolean ftlStackTrace, boolean javaStackTrace) {
+ synchronized (out) {
+ printStackTrace(new PrintWriterStackTraceWriter(out), heading, ftlStackTrace, javaStackTrace);
+ }
+ }
+
+ /**
+ * @param heading should the heading at the top be printed
+ * @param ftlStackTrace should the FTL stack trace be printed
+ * @param javaStackTrace should the Java stack trace be printed
+ *
+ * @since 2.3.20
+ */
+ public void printStackTrace(PrintStream out, boolean heading, boolean ftlStackTrace, boolean javaStackTrace) {
+ synchronized (out) {
+ printStackTrace(new PrintStreamStackTraceWriter(out), heading, ftlStackTrace, javaStackTrace);
+ }
+ }
+
+ private void printStackTrace(StackTraceWriter out, boolean heading, boolean ftlStackTrace, boolean javaStackTrace) {
+ synchronized (out) {
+ if (heading) {
+ out.println("FreeMarker template error:");
+ }
+
+ if (ftlStackTrace) {
+ String stackTrace = getFTLInstructionStack();
+ if (stackTrace != null) {
+ out.println(getMessageWithoutStackTop()); // Not getMessage()!
+ out.println();
+ out.println(MessageUtil.ERROR_MESSAGE_HR);
+ out.println(FTL_INSTRUCTION_STACK_TRACE_TITLE);
+ out.print(stackTrace);
+ out.println(MessageUtil.ERROR_MESSAGE_HR);
+ } else {
+ ftlStackTrace = false;
+ javaStackTrace = true;
+ }
+ }
+
+ if (javaStackTrace) {
+ if (ftlStackTrace) { // We are after an FTL stack trace
+ out.println();
+ out.println("Java stack trace (for programmers):");
+ out.println(MessageUtil.ERROR_MESSAGE_HR);
+ synchronized (lock) {
+ if (messageWasAlreadyPrintedForThisTrace == null) {
+ messageWasAlreadyPrintedForThisTrace = new ThreadLocal();
+ }
+ messageWasAlreadyPrintedForThisTrace.set(Boolean.TRUE);
+ }
+
+ try {
+ out.printStandardStackTrace(this);
+ } finally {
+ messageWasAlreadyPrintedForThisTrace.set(Boolean.FALSE);
+ }
+ } else { // javaStackTrace only
+ out.printStandardStackTrace(this);
+ }
+
+ if (getCause() != null) {
+ // Dirty hack to fight with ServletException class whose getCause() method doesn't work properly:
+ Throwable causeCause = getCause().getCause();
+ if (causeCause == null) {
+ try {
+ // Reflection is used to prevent dependency on Servlet classes.
+ Method m = getCause().getClass().getMethod("getRootCause", _CollectionUtil.EMPTY_CLASS_ARRAY);
+ Throwable rootCause = (Throwable) m.invoke(getCause(), _CollectionUtil.EMPTY_OBJECT_ARRAY);
+ if (rootCause != null) {
+ out.println("ServletException root cause: ");
+ out.printStandardStackTrace(rootCause);
+ }
+ } catch (Throwable exc) {
+ // ignore
+ }
+ }
+ }
+ } // if (javaStackTrace)
+ }
+ }
+
+ /**
+ * Prints the stack trace as if wasn't overridden by {@link TemplateException}.
+ * @since 2.3.20
+ */
+ public void printStandardStackTrace(PrintStream ps) {
+ super.printStackTrace(ps);
+ }
+
+ /**
+ * Prints the stack trace as if wasn't overridden by {@link TemplateException}.
+ * @since 2.3.20
+ */
+ public void printStandardStackTrace(PrintWriter pw) {
+ super.printStackTrace(pw);
+ }
+
+ @Override
+ public String getMessage() {
+ if (messageWasAlreadyPrintedForThisTrace != null
+ && messageWasAlreadyPrintedForThisTrace.get() == Boolean.TRUE) {
+ return "[... Exception message was already printed; see it above ...]";
+ } else {
+ synchronized (lock) {
+ if (message == null) renderMessages();
+ return message;
+ }
+ }
+ }
+
+ /**
+ * Similar to {@link #getMessage()}, but it doesn't contain the position of the failing instruction at then end
+ * of the text. It might contains the position of the failing <em>expression</em> though as part of the expression
+ * quotation, as that's the part of the description.
+ */
+ public String getMessageWithoutStackTop() {
+ synchronized (lock) {
+ if (messageWithoutStackTop == null) renderMessages();
+ return messageWithoutStackTop;
+ }
+ }
+
+ /**
+ * 1-based line number of the failing section, or {@code null} if the information is not available.
+ *
+ * @since 2.3.21
+ */
+ public Integer getLineNumber() {
+ synchronized (lock) {
+ if (!positionsCalculated) {
+ calculatePosition();
+ }
+ return lineNumber;
+ }
+ }
+
+ /**
+ * Returns the {@linkplain Template#getSourceName() source name} of the template where the error has occurred, or
+ * {@code null} if the information isn't available. This is what should be used for showing the error position.
+ *
+ * @since 2.3.22
+ */
+ public String getTemplateSourceName() {
+ synchronized (lock) {
+ if (!positionsCalculated) {
+ calculatePosition();
+ }
+ return templateSourceName;
+ }
+ }
+
+ /**
+ * Returns the {@linkplain Template#getLookupName()} () lookup name} of the template where the error has
+ * occurred, or {@code null} if the information isn't available. Do not use this for showing the error position;
+ * use {@link #getTemplateSourceName()}.
+ */
+ public String getTemplateLookupName() {
+ synchronized (lock) {
+ if (!positionsCalculated) {
+ calculatePosition();
+ }
+ return templateLookupName;
+ }
+ }
+
+ /**
+ * Returns the {@linkplain #getTemplateSourceName() template source name}, or if that's {@code null} then the
+ * {@linkplain #getTemplateLookupName() template lookup name}. This name is primarily meant to be used in error
+ * messages.
+ */
+ public String getTemplateSourceOrLookupName() {
+ return getTemplateSourceName() != null ? getTemplateSourceName() : getTemplateLookupName();
+ }
+
+ /**
+ * 1-based column number of the failing section, or {@code null} if the information is not available.
+ *
+ * @since 2.3.21
+ */
+ public Integer getColumnNumber() {
+ synchronized (lock) {
+ if (!positionsCalculated) {
+ calculatePosition();
+ }
+ return columnNumber;
+ }
+ }
+
+ /**
+ * 1-based line number of the last line that contains the failing section, or {@code null} if the information is not
+ * available.
+ *
+ * @since 2.3.21
+ */
+ public Integer getEndLineNumber() {
+ synchronized (lock) {
+ if (!positionsCalculated) {
+ calculatePosition();
+ }
+ return endLineNumber;
+ }
+ }
+
+ /**
+ * 1-based column number of the last character of the failing template section, or {@code null} if the information
+ * is not available. Note that unlike with Java string API-s, this column number is inclusive.
+ *
+ * @since 2.3.21
+ */
+ public Integer getEndColumnNumber() {
+ synchronized (lock) {
+ if (!positionsCalculated) {
+ calculatePosition();
+ }
+ return endColumnNumber;
+ }
+ }
+
+ /**
+ * If there was a blamed expression attached to this exception, it returns its canonical form, otherwise it returns
+ * {@code null}. This expression should always be inside the failing FTL instruction.
+ *
+ * <p>The typical application of this is getting the undefined expression from {@link InvalidReferenceException}-s.
+ *
+ * @since 2.3.21
+ */
+ public String getBlamedExpressionString() {
+ synchronized (lock) {
+ if (!blamedExpressionStringCalculated) {
+ if (blamedExpression != null) {
+ blamedExpressionString = blamedExpression.getCanonicalForm();
+ }
+ blamedExpressionStringCalculated = true;
+ }
+ return blamedExpressionString;
+ }
+ }
+
+ ASTExpression getBlamedExpression() {
+ return blamedExpression;
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException, ClassNotFoundException {
+ // These are calculated from transient fields, so this is the last chance to calculate them:
+ getFTLInstructionStack();
+ getFTLInstructionStackTopFew();
+ getDescription();
+ calculatePosition();
+ getBlamedExpressionString();
+
+ out.defaultWriteObject();
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ lock = new Object();
+ in.defaultReadObject();
+ }
+
+ /** Delegate to a {@link PrintWriter} or to a {@link PrintStream}. */
+ private interface StackTraceWriter {
+ void print(Object obj);
+ void println(Object obj);
+ void println();
+ void printStandardStackTrace(Throwable exception);
+ }
+
+ private static class PrintStreamStackTraceWriter implements StackTraceWriter {
+
+ private final PrintStream out;
+
+ PrintStreamStackTraceWriter(PrintStream out) {
+ this.out = out;
+ }
+
+ @Override
+ public void print(Object obj) {
+ out.print(obj);
+ }
+
+ @Override
+ public void println(Object obj) {
+ out.println(obj);
+ }
+
+ @Override
+ public void println() {
+ out.println();
+ }
+
+ @Override
+ public void printStandardStackTrace(Throwable exception) {
+ if (exception instanceof TemplateException) {
+ ((TemplateException) exception).printStandardStackTrace(out);
+ } else {
+ exception.printStackTrace(out);
+ }
+ }
+
+ }
+
+ private static class PrintWriterStackTraceWriter implements StackTraceWriter {
+
+ private final PrintWriter out;
+
+ PrintWriterStackTraceWriter(PrintWriter out) {
+ this.out = out;
+ }
+
+ @Override
+ public void print(Object obj) {
+ out.print(obj);
+ }
+
+ @Override
+ public void println(Object obj) {
+ out.println(obj);
+ }
+
+ @Override
+ public void println() {
+ out.println();
+ }
+
+ @Override
+ public void printStandardStackTrace(Throwable exception) {
+ if (exception instanceof TemplateException) {
+ ((TemplateException) exception).printStandardStackTrace(out);
+ } else {
+ exception.printStackTrace(out);
+ }
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java
new file mode 100644
index 0000000..8270740
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Used for the {@code template_exception_handler} configuration setting;
+ * see {@link MutableProcessingConfiguration#setTemplateExceptionHandler(TemplateExceptionHandler)} for more.
+ */
+public interface TemplateExceptionHandler {
+
+ /**
+ * Method called after a {@link TemplateException} was raised inside a template. The exception should be re-thrown
+ * unless you want to suppress the exception.
+ *
+ * <p>Note that you can check with {@link Environment#isInAttemptBlock()} if you are inside a {@code #attempt}
+ * block, which then will handle handle this exception and roll back the output generated inside it.
+ *
+ * <p>Note that {@link StopException}-s (raised by {@code #stop}) won't be captured.
+ *
+ * <p>Note that you shouldn't log the exception in this method unless you suppress it. If there's a concern that the
+ * exception might won't be logged after it bubbles up from {@link Template#process(Object, Writer)}, simply
+ * ensure that {@link Configuration#getLogTemplateExceptions()} is {@code true}.
+ *
+ * @param te The exception that occurred; don't forget to re-throw it unless you want to suppress it
+ * @param env The runtime environment of the template
+ * @param out This is where the output of the template is written
+ */
+ void handleTemplateException(TemplateException te, Environment env, Writer out) throws TemplateException;
+
+ /**
+ * {@link TemplateExceptionHandler} that simply skips the failing instructions, letting the template continue
+ * executing. It does nothing to handle the event. Note that the exception is still logged, as with all
+ * other {@link TemplateExceptionHandler}-s.
+ */
+ TemplateExceptionHandler IGNORE_HANDLER = new TemplateExceptionHandler() {
+ @Override
+ public void handleTemplateException(TemplateException te, Environment env, Writer out) {
+ // Do nothing
+ }
+ };
+
+ /**
+ * {@link TemplateExceptionHandler} that simply re-throws the exception; this should be used in most production
+ * systems.
+ */
+ TemplateExceptionHandler RETHROW_HANDLER = new TemplateExceptionHandler() {
+ @Override
+ public void handleTemplateException(TemplateException te, Environment env, Writer out)
+ throws TemplateException {
+ throw te;
+ }
+ };
+
+ /**
+ * {@link TemplateExceptionHandler} useful when you developing non-HTML templates. This handler
+ * outputs the stack trace information to the client and then re-throws the exception.
+ */
+ TemplateExceptionHandler DEBUG_HANDLER = new TemplateExceptionHandler() {
+ @Override
+ public void handleTemplateException(TemplateException te, Environment env, Writer out)
+ throws TemplateException {
+ if (!env.isInAttemptBlock()) {
+ PrintWriter pw = (out instanceof PrintWriter) ? (PrintWriter) out : new PrintWriter(out);
+ pw.print("FreeMarker template error (DEBUG mode; use RETHROW in production!):\n");
+ te.printStackTrace(pw, false, true, true);
+
+ pw.flush(); // To commit the HTTP response
+ }
+ throw te;
+ }
+ };
+
+ /**
+ * {@link TemplateExceptionHandler} useful when you developing HTML templates. This handler
+ * outputs the stack trace information to the client, formatting it so that it will be usually well readable
+ * in the browser, and then re-throws the exception.
+ */
+ TemplateExceptionHandler HTML_DEBUG_HANDLER = new TemplateExceptionHandler() {
+ @Override
+ public void handleTemplateException(TemplateException te, Environment env, Writer out)
+ throws TemplateException {
+ if (!env.isInAttemptBlock()) {
+ boolean externalPw = out instanceof PrintWriter;
+ PrintWriter pw = externalPw ? (PrintWriter) out : new PrintWriter(out);
+ try {
+ pw.print("<!-- FREEMARKER ERROR MESSAGE STARTS HERE -->"
+ + "<!-- ]]> -->"
+ + "<script language=javascript>//\"></script>"
+ + "<script language=javascript>//'></script>"
+ + "<script language=javascript>//\"></script>"
+ + "<script language=javascript>//'></script>"
+ + "</title></xmp></script></noscript></style></object>"
+ + "</head></pre></table>"
+ + "</form></table></table></table></a></u></i></b>"
+ + "<div align='left' "
+ + "style='background-color:#FFFF7C; "
+ + "display:block; border-top:double; padding:4px; margin:0; "
+ + "font-family:Arial,sans-serif; ");
+ pw.print(FONT_RESET_CSS);
+ pw.print("'>"
+ + "<b style='font-size:12px; font-style:normal; font-weight:bold; "
+ + "text-decoration:none; text-transform: none;'>FreeMarker template error "
+ + " (HTML_DEBUG mode; use RETHROW in production!)</b>"
+ + "<pre style='display:block; background: none; border: 0; margin:0; padding: 0;"
+ + "font-family:monospace; ");
+ pw.print(FONT_RESET_CSS);
+ pw.println("; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; "
+ + "white-space: -o-pre-wrap; word-wrap: break-word;'>");
+
+ StringWriter stackTraceSW = new StringWriter();
+ PrintWriter stackPW = new PrintWriter(stackTraceSW);
+ te.printStackTrace(stackPW, false, true, true);
+ stackPW.close();
+ pw.println();
+ pw.println(_StringUtil.XMLEncNQG(stackTraceSW.toString()));
+
+ pw.println("</pre></div></html>");
+ pw.flush(); // To commit the HTTP response
+ } finally {
+ if (!externalPw) pw.close();
+ }
+ } // if (!env.isInAttemptBlock())
+
+ throw te;
+ }
+
+ private static final String FONT_RESET_CSS =
+ "color:#A80000; font-size:12px; font-style:normal; font-variant:normal; "
+ + "font-weight:normal; text-decoration:none; text-transform: none";
+
+ };
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
new file mode 100644
index 0000000..205fa8c
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.charset.Charset;
+
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Represents a template language. Currently this class is not mature, so it can't be implemented outside FreeMarker,
+ * also its methods shouldn't be called from outside FreeMarker.
+ */
+// [FM3] Make this mature, or hide its somehow. Actually, parse can't be hidden because custom TemplateResolver-s need
+// to call it.
+public abstract class TemplateLanguage {
+
+ // FIXME [FM3] If we leave this here, FTL will be a required dependency of core (which is not nice if
+ // template languages will be pluggable).
+ public static final TemplateLanguage FTL = new TemplateLanguage("FreeMarker Template Language") {
+ @Override
+ public boolean getCanSpecifyCharsetInContent() {
+ return true;
+ }
+
+ @Override
+ public Template parse(String name, String sourceName, Reader reader, Configuration cfg,
+ TemplateConfiguration templateConfiguration, Charset encoding,
+ InputStream streamToUnmarkWhenEncEstabd) throws
+ IOException, ParseException {
+ return new Template(name, sourceName, reader, cfg, templateConfiguration,
+ encoding, streamToUnmarkWhenEncEstabd);
+ }
+ };
+
+ public static final TemplateLanguage STATIC_TEXT = new TemplateLanguage("Static text") {
+ @Override
+ public boolean getCanSpecifyCharsetInContent() {
+ return false;
+ }
+
+ @Override
+ public Template parse(String name, String sourceName, Reader reader, Configuration cfg,
+ TemplateConfiguration templateConfiguration, Charset sourceEncoding,
+ InputStream streamToUnmarkWhenEncEstabd)
+ throws IOException, ParseException {
+ // Read the contents into a StringWriter, then construct a single-text-block template from it.
+ final StringBuilder sb = new StringBuilder();
+ final char[] buf = new char[4096];
+ int charsRead;
+ while ((charsRead = reader.read(buf)) > 0) {
+ sb.append(buf, 0, charsRead);
+ }
+ return Template.createPlainTextTemplate(name, sourceName, sb.toString(), cfg,
+ sourceEncoding);
+ }
+ };
+
+ private final String name;
+
+ // Package visibility to prevent user implementations until this API is mature.
+ TemplateLanguage(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns if the template can specify its own charset inside the template. If so, {@link #parse(String, String,
+ * Reader, Configuration, TemplateConfiguration, Charset, InputStream)} can throw
+ * {@link WrongTemplateCharsetException}, and it might gets a non-{@code null} for the {@link InputStream}
+ * parameter.
+ */
+ public abstract boolean getCanSpecifyCharsetInContent();
+
+ /**
+ * See {@link Template#Template(String, String, Reader, Configuration, TemplateConfiguration, Charset,
+ * InputStream)}.
+ */
+ public abstract Template parse(String name, String sourceName, Reader reader,
+ Configuration cfg, TemplateConfiguration templateConfiguration,
+ Charset encoding, InputStream streamToUnmarkWhenEncEstabd)
+ throws IOException, ParseException;
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public final String toString() {
+ return "TemplateLanguage(" + _StringUtil.jQuote(name) + ")";
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateNotFoundException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateNotFoundException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateNotFoundException.java
new file mode 100644
index 0000000..37ba911
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateNotFoundException.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.FileNotFoundException;
+import java.io.Serializable;
+
+import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
+
+/**
+ * Thrown when {@link Configuration#getTemplate(String)} (or similar) doesn't find a template.
+ * This extends {@link FileNotFoundException} for backward compatibility, but in fact has nothing to do with files, as
+ * FreeMarker can load templates from many other sources.
+ *
+ * @since 2.3.22
+ *
+ * @see MalformedTemplateNameException
+ * @see Configuration#getTemplate(String)
+ */
+public final class TemplateNotFoundException extends FileNotFoundException {
+
+ private final String templateName;
+ private final Object customLookupCondition;
+
+ public TemplateNotFoundException(String templateName, Object customLookupCondition, String message) {
+ super(message);
+ this.templateName = templateName;
+ this.customLookupCondition = customLookupCondition;
+ }
+
+ /**
+ * The name (path) of the template that wasn't found.
+ */
+ public String getTemplateName() {
+ return templateName;
+ }
+
+ /**
+ * The custom lookup condition with which the template was requested, or {@code null} if there's no such condition.
+ * See the {@code customLookupCondition} parameter of
+ * {@link Configuration#getTemplate(String, java.util.Locale, Serializable, boolean)}.
+ */
+ public Object getCustomLookupCondition() {
+ return customLookupCondition;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateParsingConfigurationWithFallback.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateParsingConfigurationWithFallback.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateParsingConfigurationWithFallback.java
new file mode 100644
index 0000000..93a5840
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateParsingConfigurationWithFallback.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.nio.charset.Charset;
+
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+
+/**
+ * Adds {@link Configuration} fallback to the {@link ParsingConfiguration} part of a {@link TemplateConfiguration}.
+ */
+final class TemplateParsingConfigurationWithFallback implements ParsingConfiguration {
+
+ private final Configuration cfg;
+ private final TemplateConfiguration tCfg;
+
+ TemplateParsingConfigurationWithFallback(Configuration cfg, TemplateConfiguration tCfg) {
+ this.cfg = cfg;
+ this.tCfg = tCfg;
+ }
+
+ @Override
+ public TemplateLanguage getTemplateLanguage() {
+ return tCfg.isTemplateLanguageSet() ? tCfg.getTemplateLanguage() : cfg.getTemplateLanguage();
+ }
+
+ @Override
+ public boolean isTemplateLanguageSet() {
+ return true;
+ }
+
+ @Override
+ public int getTagSyntax() {
+ return tCfg.isTagSyntaxSet() ? tCfg.getTagSyntax() : cfg.getTagSyntax();
+ }
+
+ @Override
+ public boolean isTagSyntaxSet() {
+ return true;
+ }
+
+ @Override
+ public int getNamingConvention() {
+ return tCfg.isNamingConventionSet() ? tCfg.getNamingConvention() : cfg.getNamingConvention();
+ }
+
+ @Override
+ public boolean isNamingConventionSet() {
+ return true;
+ }
+
+ @Override
+ public boolean getWhitespaceStripping() {
+ return tCfg.isWhitespaceStrippingSet() ? tCfg.getWhitespaceStripping() : cfg.getWhitespaceStripping();
+ }
+
+ @Override
+ public boolean isWhitespaceStrippingSet() {
+ return true;
+ }
+
+ @Override
+ public ArithmeticEngine getArithmeticEngine() {
+ return tCfg.isArithmeticEngineSet() ? tCfg.getArithmeticEngine() : cfg.getArithmeticEngine();
+ }
+
+ @Override
+ public boolean isArithmeticEngineSet() {
+ return true;
+ }
+
+ @Override
+ public int getAutoEscapingPolicy() {
+ return tCfg.isAutoEscapingPolicySet() ? tCfg.getAutoEscapingPolicy() : cfg.getAutoEscapingPolicy();
+ }
+
+ @Override
+ public boolean isAutoEscapingPolicySet() {
+ return true;
+ }
+
+ @Override
+ public OutputFormat getOutputFormat() {
+ return tCfg.isOutputFormatSet() ? tCfg.getOutputFormat() : cfg.getOutputFormat();
+ }
+
+ @Override
+ public boolean isOutputFormatSet() {
+ return true;
+ }
+
+ @Override
+ public boolean getRecognizeStandardFileExtensions() {
+ return tCfg.isRecognizeStandardFileExtensionsSet() ? tCfg.getRecognizeStandardFileExtensions()
+ : cfg.getRecognizeStandardFileExtensions();
+ }
+
+ @Override
+ public boolean isRecognizeStandardFileExtensionsSet() {
+ return true;
+ }
+
+ @Override
+ public Version getIncompatibleImprovements() {
+ // This can be only set on the Configuration-level
+ return cfg.getIncompatibleImprovements();
+ }
+
+ @Override
+ public int getTabSize() {
+ return tCfg.isTabSizeSet() ? tCfg.getTabSize() : cfg.getTabSize();
+ }
+
+ @Override
+ public boolean isTabSizeSet() {
+ return true;
+ }
+
+ @Override
+ public Charset getSourceEncoding() {
+ return tCfg.isSourceEncodingSet() ? tCfg.getSourceEncoding() : cfg.getSourceEncoding();
+ }
+
+ @Override
+ public boolean isSourceEncodingSet() {
+ return true;
+ }
+}