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/03/27 11:48:53 UTC
[4/6] incubator-freemarker git commit: Various refactorings of
Configurable and its subclasses. This is part of the preparation for making
such classes immutable, and offer builders to create them.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Configuration.java b/src/main/java/org/apache/freemarker/core/Configuration.java
index 256b4c1..0e76d09 100644
--- a/src/main/java/org/apache/freemarker/core/Configuration.java
+++ b/src/main/java/org/apache/freemarker/core/Configuration.java
@@ -39,6 +39,7 @@ import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
import org.apache.freemarker.core.model.ObjectWrapper;
import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
@@ -125,7 +126,7 @@ import org.apache.freemarker.core.util._UnmodifiableCompositeSet;
* useless. (For the most common cases you can use the convenience methods,
* {@link #setDirectoryForTemplateLoading(File)} and {@link #setClassForTemplateLoading(Class, String)} and
* {@link #setClassLoaderForTemplateLoading(ClassLoader, String)} too.)
- * <li>{@link #setDefaultEncoding(String) default_encoding}: The default value is system dependent, which makes it
+ * <li>{@link #setEncoding(String) encoding}: The default value is system dependent, which makes it
* fragile on servers, so it should be set explicitly, like to "UTF-8" nowadays.
* <li>{@link #setTemplateExceptionHandler(TemplateExceptionHandler) template_exception_handler}: For developing
* HTML pages, the most convenient value is {@link TemplateExceptionHandler#HTML_DEBUG_HANDLER}. For production,
@@ -139,16 +140,17 @@ import org.apache.freemarker.core.util._UnmodifiableCompositeSet;
* anymore, so then it's safe to make it accessible (again, via a "safe publication" technique) from multiple threads.
* The methods that aren't for modifying settings, like {@link #getTemplate(String)}, are thread-safe.
*/
-public class Configuration extends Configurable implements Cloneable, ParserConfiguration {
+public final class Configuration extends MutableProcessingConfiguration<Configuration>
+ implements Cloneable, ParserConfiguration, ProcessingConfiguration, CustomStateScope {
private static final String VERSION_PROPERTIES_PATH = "org/apache/freemarker/core/version.properties";
/** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
- public static final String DEFAULT_ENCODING_KEY_SNAKE_CASE = "default_encoding";
+ public static final String ENCODING_KEY_SNAKE_CASE = "encoding";
/** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
- public static final String DEFAULT_ENCODING_KEY_CAMEL_CASE = "defaultEncoding";
+ public static final String ENCODING_KEY_CAMEL_CASE = "encoding";
/** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
- public static final String DEFAULT_ENCODING_KEY = DEFAULT_ENCODING_KEY_SNAKE_CASE;
+ public static final String ENCODING_KEY = ENCODING_KEY_SNAKE_CASE;
/** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
public static final String LOCALIZED_LOOKUP_KEY_SNAKE_CASE = "localized_lookup";
@@ -281,7 +283,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
// Must be sorted alphabetically!
AUTO_ESCAPING_POLICY_KEY_SNAKE_CASE,
CACHE_STORAGE_KEY_SNAKE_CASE,
- DEFAULT_ENCODING_KEY_SNAKE_CASE,
+ ENCODING_KEY_SNAKE_CASE,
INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE,
LOCALIZED_LOOKUP_KEY_SNAKE_CASE,
NAMING_CONVENTION_KEY_SNAKE_CASE,
@@ -303,7 +305,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
// Must be sorted alphabetically!
AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE,
CACHE_STORAGE_KEY_CAMEL_CASE,
- DEFAULT_ENCODING_KEY_CAMEL_CASE,
+ ENCODING_KEY_CAMEL_CASE,
INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE,
LOCALIZED_LOOKUP_KEY_CAMEL_CASE,
NAMING_CONVENTION_KEY_CAMEL_CASE,
@@ -430,6 +432,9 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
private HashMap/*<String, TemplateModel>*/ sharedVariables = new HashMap();
+ private final ConcurrentHashMap<CustomStateKey, Object> customStateMap = new ConcurrentHashMap<>(0);
+ private final Object customStateMapLock = new Object();
+
/**
* Needed so that it doesn't mater in what order do you call {@link #setSharedVaribles(Map)}
* and {@link #setObjectWrapper(ObjectWrapper)}. When the user configures FreeMarker from Spring XML, he has no
@@ -437,7 +442,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
*/
private HashMap<String, Object> rewrappableSharedVariables = null;
- private String defaultEncoding = getDefaultDefaultEncoding();
+ private String encoding = getDefaultEncoding();
/**
* @deprecated Use {@link #Configuration(Version)} instead. Note that the version can be still modified later with
@@ -560,7 +565,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
}
return new DefaultSoftCacheStorage();
}
-
+
private static class DefaultSoftCacheStorage extends SoftCacheStorage {
// Nothing to override
}
@@ -1236,6 +1241,11 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
return whitespaceStripping;
}
+ @Override
+ public boolean isWhitespaceStrippingSet() {
+ return true;
+ }
+
/**
* Sets when auto-escaping should be enabled depending on the current {@linkplain OutputFormat output format};
* default is {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY}. Note that the default output format,
@@ -1312,7 +1322,12 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
public int getAutoEscapingPolicy() {
return autoEscapingPolicy;
}
-
+
+ @Override
+ public boolean isAutoEscapingPolicySet() {
+ return true;
+ }
+
/**
* Sets the default output format. Usually, you should leave this on its default, which is
* {@link UndefinedOutputFormat#INSTANCE}, and then use standard file extensions like "ftlh" (for HTML) or "ftlx"
@@ -1358,7 +1373,12 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
public OutputFormat getOutputFormat() {
return outputFormat;
}
-
+
+ @Override
+ public boolean isOutputFormatSet() {
+ return true;
+ }
+
/**
* Tells if {@link #setOutputFormat(OutputFormat)} (or equivalent) was already called on this instance.
*
@@ -1613,6 +1633,11 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
: recognizeStandardFileExtensions.booleanValue();
}
+ @Override
+ public boolean isRecognizeStandardFileExtensionsSet() {
+ return true;
+ }
+
/**
* Getter pair of {@link #setTemplateLanguage(TemplateLanguage)}.
*/
@@ -1677,6 +1702,11 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
return tagSyntax;
}
+ @Override
+ public boolean isTagSyntaxSet() {
+ return true;
+ }
+
/**
* Sets the naming convention used for the identifiers that are part of the template language. The available naming
* conventions are legacy (directive (tag) names are all-lower-case {@code likethis}, others are snake case
@@ -1747,7 +1777,12 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
public int getNamingConvention() {
return namingConvention;
}
-
+
+ @Override
+ public boolean isNamingConventionSet() {
+ return true;
+ }
+
/**
* Sets the assumed display width of the tab character (ASCII 9), which influences the column number shown in error
* messages (or the column number you get through other API-s). So for example if the users edit templates in an
@@ -1780,6 +1815,31 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
public int getTabSize() {
return tabSize;
}
+
+ @Override
+ public boolean isTabSizeSet() {
+ return true;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T getCustomState(CustomStateKey<T> customStateKey) {
+ T customState = (T) customStateMap.get(customStateKey);
+ if (customState == null) {
+ synchronized (customStateMapLock) {
+ customState = (T) customStateMap.get(customStateKey);
+ if (customState == null) {
+ customState = customStateKey.create();
+ if (customState == null) {
+ throw new IllegalStateException("CustomStateKey.create() must not return null (for key: "
+ + customStateKey + ")");
+ }
+ customStateMap.put(customStateKey, customState);
+ }
+ }
+ }
+ return customState;
+ }
/**
* Retrieves the template with the given name from the template templateResolver, loading it into the templateResolver first if it's
@@ -1982,18 +2042,14 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
*
* @param encoding The name of the charset, such as {@code "UTF-8"} or {@code "ISO-8859-1"}
*/
- public void setDefaultEncoding(String encoding) {
- defaultEncoding = encoding;
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
defaultEncodingExplicitlySet = true;
}
- /**
- * Gets the default encoding for converting bytes to characters when
- * reading template files in a locale for which no explicit encoding
- * was specified. Defaults to the default system encoding.
- */
- public String getDefaultEncoding() {
- return defaultEncoding;
+ @Override
+ public String getEncoding() {
+ return encoding;
}
/**
@@ -2003,13 +2059,13 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
*/
public void unsetDefaultEncoding() {
if (defaultEncodingExplicitlySet) {
- setDefaultEncoding(getDefaultDefaultEncoding());
+ setEncoding(getDefaultEncoding());
defaultEncodingExplicitlySet = false;
}
}
/**
- * Tells if {@link #setDefaultEncoding(String)} (or equivalent) was already called on this instance, or it just holds the
+ * Tells if {@link #setEncoding(String)} (or equivalent) was already called on this instance, or it just holds the
* default value.
*
* @since 2.3.26
@@ -2018,7 +2074,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
return defaultEncodingExplicitlySet;
}
- static private String getDefaultDefaultEncoding() {
+ static private String getDefaultEncoding() {
return getJVMDefaultEncoding();
}
@@ -2063,7 +2119,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
}
/**
- * Adds shared variable to the configuration; It uses {@link Configurable#getObjectWrapper()} to wrap the
+ * Adds shared variable to the configuration; It uses {@link MutableProcessingConfiguration#getObjectWrapper()} to wrap the
* {@code value}, so it's important that the object wrapper is set before this.
*
* <p>This method is <b>not</b> thread safe; use it with the same restrictions as those that modify setting values.
@@ -2245,14 +2301,14 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
if ("TemplateUpdateInterval".equalsIgnoreCase(name)) {
name = TEMPLATE_UPDATE_DELAY_KEY;
} else if ("DefaultEncoding".equalsIgnoreCase(name)) {
- name = DEFAULT_ENCODING_KEY;
+ name = ENCODING_KEY;
}
- if (DEFAULT_ENCODING_KEY_SNAKE_CASE.equals(name) || DEFAULT_ENCODING_KEY_CAMEL_CASE.equals(name)) {
- if (JVM_DEFAULT.equalsIgnoreCase(value)) {
- setDefaultEncoding(getJVMDefaultEncoding());
+ if (ENCODING_KEY_SNAKE_CASE.equals(name) || ENCODING_KEY_CAMEL_CASE.equals(name)) {
+ if (JVM_DEFAULT_VALUE.equalsIgnoreCase(value)) {
+ setEncoding(getJVMDefaultEncoding());
} else {
- setDefaultEncoding(value);
+ setEncoding(value);
}
} else if (LOCALIZED_LOOKUP_KEY_SNAKE_CASE.equals(name) || LOCALIZED_LOOKUP_KEY_CAMEL_CASE.equals(name)) {
setLocalizedLookup(_StringUtil.getYesNo(value));
@@ -2270,7 +2326,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
throw invalidSettingValueException(name, value);
}
} else if (OUTPUT_FORMAT_KEY_SNAKE_CASE.equals(name) || OUTPUT_FORMAT_KEY_CAMEL_CASE.equals(name)) {
- if (value.equalsIgnoreCase(DEFAULT)) {
+ if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
unsetOutputFormat();
} else {
setOutputFormat((OutputFormat) _ObjectBuilderSettingEvaluator.eval(
@@ -2290,13 +2346,13 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
setRegisteredCustomOutputFormats(list);
} else if (RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_SNAKE_CASE.equals(name)
|| RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE.equals(name)) {
- if (value.equalsIgnoreCase(DEFAULT)) {
+ if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
unsetRecognizeStandardFileExtensions();
} else {
setRecognizeStandardFileExtensions(_StringUtil.getYesNo(value));
}
} else if (CACHE_STORAGE_KEY_SNAKE_CASE.equals(name) || CACHE_STORAGE_KEY_CAMEL_CASE.equals(name)) {
- if (value.equalsIgnoreCase(DEFAULT)) {
+ if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
unsetCacheStorage();
} if (value.indexOf('.') == -1) {
int strongSize = 0;
@@ -2397,7 +2453,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
|| INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE.equals(name)) {
setIncompatibleImprovements(new Version(value));
} else if (TEMPLATE_LOADER_KEY_SNAKE_CASE.equals(name) || TEMPLATE_LOADER_KEY_CAMEL_CASE.equals(name)) {
- if (value.equalsIgnoreCase(DEFAULT)) {
+ if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
unsetTemplateLoader();
} else {
setTemplateLoader((TemplateLoader) _ObjectBuilderSettingEvaluator.eval(
@@ -2405,7 +2461,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
}
} else if (TEMPLATE_LOOKUP_STRATEGY_KEY_SNAKE_CASE.equals(name)
|| TEMPLATE_LOOKUP_STRATEGY_KEY_CAMEL_CASE.equals(name)) {
- if (value.equalsIgnoreCase(DEFAULT)) {
+ if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
unsetTemplateLookupStrategy();
} else {
setTemplateLookupStrategy((TemplateLookupStrategy) _ObjectBuilderSettingEvaluator.eval(
@@ -2413,7 +2469,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
}
} else if (TEMPLATE_NAME_FORMAT_KEY_SNAKE_CASE.equals(name)
|| TEMPLATE_NAME_FORMAT_KEY_CAMEL_CASE.equals(name)) {
- if (value.equalsIgnoreCase(DEFAULT)) {
+ if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
unsetTemplateNameFormat();
} else if (value.equalsIgnoreCase("default_2_3_0")) {
setTemplateNameFormat(DefaultTemplateNameFormatFM2.INSTANCE);
@@ -2424,7 +2480,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
}
} else if (TEMPLATE_CONFIGURATIONS_KEY_SNAKE_CASE.equals(name)
|| TEMPLATE_CONFIGURATIONS_KEY_CAMEL_CASE.equals(name)) {
- if (value.equals(NULL)) {
+ if (value.equals(NULL_VALUE)) {
setTemplateConfigurations(null);
} else {
setTemplateConfigurations((TemplateConfigurationFactory) _ObjectBuilderSettingEvaluator.eval(
@@ -2450,14 +2506,14 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
}
/**
- * Returns the valid {@link Configuration} setting names. Naturally, this includes the {@link Configurable} setting
+ * Returns the valid {@link Configuration} setting names. Naturally, this includes the {@link MutableProcessingConfiguration} setting
* names too.
*
* @param camelCase
* If we want the setting names with camel case naming convention, or with snake case (legacy) naming
* convention.
*
- * @see Configurable#getSettingNames(boolean)
+ * @see MutableProcessingConfiguration#getSettingNames(boolean)
*
* @since 2.3.24
*/
@@ -2480,10 +2536,10 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
protected String getCorrectedNameForUnknownSetting(String name) {
if ("encoding".equals(name) || "charset".equals(name) || "default_charset".equals(name)) {
// [2.4] Default might changes to camel-case
- return DEFAULT_ENCODING_KEY;
+ return ENCODING_KEY;
}
if ("defaultCharset".equals(name)) {
- return DEFAULT_ENCODING_KEY_CAMEL_CASE;
+ return ENCODING_KEY_CAMEL_CASE;
}
if (name.equals("incompatible_enhancements")) {
return INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE;
@@ -2503,13 +2559,12 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
}
private void doAutoImports(Environment env, Template t) throws IOException, TemplateException {
- Map<String, String> envAutoImports = env.getAutoImportsWithoutFallback();
- Map<String, String> tAutoImports = t.getAutoImportsWithoutFallback();
+ Map<String, String> envAutoImports = env.isAutoImportsSet() ? env.getAutoImports() : null;
+ Map<String, String> tAutoImports = t.isAutoImportsSet() ? t.getAutoImports() : null;
- boolean lazyAutoImports = env.getLazyAutoImports() != null ? env.getLazyAutoImports().booleanValue()
- : env.getLazyImports();
+ boolean lazyAutoImports = env.getLazyAutoImports() != null ? env.getLazyAutoImports() : env.getLazyImports();
- for (Map.Entry<String, String> autoImport : getAutoImportsWithoutFallback().entrySet()) {
+ for (Map.Entry<String, String> autoImport : getAutoImports().entrySet()) {
String nsVarName = autoImport.getKey();
if ((tAutoImports == null || !tAutoImports.containsKey(nsVarName))
&& (envAutoImports == null || !envAutoImports.containsKey(nsVarName))) {
@@ -2536,11 +2591,11 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
// We can't store autoIncludes in LinkedHashSet-s because setAutoIncludes(List) allows duplicates,
// unfortunately. Yet we have to prevent duplicates among Configuration levels, with the lowest levels having
// priority. So we build some Set-s to do that, but we avoid the most common cases where they aren't needed.
+
+ List<String> tAutoIncludes = t.isAutoIncludesSet() ? t.getAutoIncludes() : null;
+ List<String> envAutoIncludes = env.isAutoIncludesSet() ? env.getAutoIncludes() : null;
- List<String> tAutoIncludes = t.getAutoIncludesWithoutFallback();
- List<String> envAutoIncludes = env.getAutoIncludesWithoutFallback();
-
- for (String templateName : getAutoIncludesWithoutFallback()) {
+ for (String templateName : getAutoIncludes()) {
if ((tAutoIncludes == null || !tAutoIncludes.contains(templateName))
&& (envAutoIncludes == null || !envAutoIncludes.contains(templateName))) {
env.include(getTemplate(templateName, env.getLocale()));
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/CustomAttribute.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/CustomAttribute.java b/src/main/java/org/apache/freemarker/core/CustomAttribute.java
deleted file mode 100644
index 37d7db9..0000000
--- a/src/main/java/org/apache/freemarker/core/CustomAttribute.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * 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.BugException;
-
-/**
- * A class that allows one to associate custom data with a {@link Configuration}, a {@link Template}, or
- * {@link Environment}.
- *
- * <p>This API has similar approach to that of {@link ThreadLocal} (which allows one to associate
- * custom data with a thread). With an example:</p>
- *
- * <pre>
- * // The object identity itself will serve as the attribute identifier; there's no attribute name String:
- * public static final CustomAttribute MY_ATTR = new CustomAttribute(CustomAttribute.SCOPE_CONFIGURATION);
- * ...
- * // Set the attribute in this particular Configuration object:
- * MY_ATTR.set(myAttrValue, cfg);
- * ...
- * // Read the attribute from this particular Configuration object:
- * myAttrValue = MY_ATTR.get(cfg);
- * </pre>
- */
-// [2.4] Use generics; type parameter used for the type of the stored value
-public class CustomAttribute {
-
- /**
- * Constant used in the constructor specifying that this attribute is {@link Environment}-scoped.
- */
- public static final int SCOPE_ENVIRONMENT = 0;
-
- /**
- * Constant used in the constructor specifying that this attribute is {@link Template}-scoped.
- */
- public static final int SCOPE_TEMPLATE = 1;
-
- /**
- * Constant used in the constructor specifying that this attribute is {@link Configuration}-scoped.
- */
- public static final int SCOPE_CONFIGURATION = 2;
-
- // We use an internal key instead of 'this' so that malicious subclasses
- // overriding equals() and hashCode() can't gain access to other attribute
- // values. That's also the reason why get() and set() are marked final.
- private final Object key = new Object();
- private final int scope;
-
- /**
- * Creates a new custom attribute with the specified scope
- * @param scope one of <tt>SCOPE_</tt> constants.
- */
- public CustomAttribute(int scope) {
- if (scope != SCOPE_ENVIRONMENT &&
- scope != SCOPE_TEMPLATE &&
- scope != SCOPE_CONFIGURATION) {
- throw new IllegalArgumentException();
- }
- this.scope = scope;
- }
-
- /**
- * This method is invoked when {@link #get()} is invoked without
- * {@link #set(Object)} being invoked before it to define the value in the
- * current scope. Override it to invoke the attribute value on-demand.
- * @return the initial value for the custom attribute. By default returns null.
- */
- protected Object create() {
- return null;
- }
-
- /**
- * Gets the attribute from the appropriate scope that's accessible through the specified {@link Environment}. If
- * the attribute has {@link #SCOPE_ENVIRONMENT} scope, it will be get from the given {@link Environment} directly.
- * If the attribute has {@link #SCOPE_TEMPLATE} scope, it will be get from the parent of the given
- * {@link Environment} (that is, in {@link Environment#getParent()}) directly). If the attribute has
- * {@link #SCOPE_CONFIGURATION} scope, it will be get from {@link Environment#getConfiguration()}.
- *
- * @throws NullPointerException
- * If {@code env} is null
- *
- * @return The new value of the attribute (possibly {@code null}), or {@code null} if the attribute doesn't exist.
- *
- * @since 2.3.22
- */
- public final Object get(Environment env) {
- return getScopeConfigurable(env).getCustomAttribute(key, this);
- }
-
- /**
- * Same as {@link #get(Environment)}, but uses {@link Environment#getCurrentEnvironment()} to fill the 2nd argument.
- *
- * @throws IllegalStateException
- * If there is no current {@link Environment}, which is usually the case when the current thread isn't
- * processing a template.
- */
- public final Object get() {
- return getScopeConfigurable(getRequiredCurrentEnvironment()).getCustomAttribute(key, this);
- }
-
- /**
- * Gets the value of a {@link Template}-scope attribute from the given {@link Template}.
- *
- * @throws UnsupportedOperationException
- * If this custom attribute has different scope than {@link #SCOPE_TEMPLATE}.
- * @throws NullPointerException
- * If {@code template} is null
- */
- public final Object get(Template template) {
- if (scope != SCOPE_TEMPLATE) {
- throw new UnsupportedOperationException("This is not a template-scope attribute");
- }
- return template.getCustomAttribute(key, this);
- }
-
- /**
- * Same as {@link #get(Template)}, but applies to a {@link TemplateConfiguration}.
- *
- * @since 2.3.24
- */
- public Object get(TemplateConfiguration templateConfiguration) {
- if (scope != SCOPE_TEMPLATE) {
- throw new UnsupportedOperationException("This is not a template-scope attribute");
- }
- return templateConfiguration.getCustomAttribute(key, this);
- }
-
- /**
- * Gets the value of a {@link Configuration}-scope attribute from the given {@link Configuration}.
- *
- * @throws UnsupportedOperationException
- * If this custom attribute has different scope than {@link #SCOPE_CONFIGURATION}.
- * @throws NullPointerException
- * If {@code cfg} is null
- *
- * @since 2.3.22
- */
- public final Object get(Configuration cfg) {
- if (scope != SCOPE_CONFIGURATION) {
- throw new UnsupportedOperationException("This is not a template-scope attribute");
- }
- return cfg.getCustomAttribute(key, this);
- }
-
- /**
- * Sets the attribute inside the appropriate scope that's accessible through the specified {@link Environment}. If
- * the attribute has {@link #SCOPE_ENVIRONMENT} scope, it will be set in the given {@link Environment} directly. If
- * the attribute has {@link #SCOPE_TEMPLATE} scope, it will be set in the parent of the given {@link Environment}
- * (that is, in {@link Environment#getParent()}) directly). If the attribute has {@link #SCOPE_CONFIGURATION} scope,
- * it will be set in {@link Environment#getConfiguration()}.
- *
- * @param value
- * The new value of the attribute. Can be {@code null}.
- *
- * @throws NullPointerException
- * If {@code env} is null
- *
- * @since 2.3.22
- */
- public final void set(Object value, Environment env) {
- getScopeConfigurable(env).setCustomAttribute(key, value);
- }
-
- /**
- * Same as {@link #set(Object, Environment)}, but uses {@link Environment#getCurrentEnvironment()} to fill the 2nd
- * argument.
- *
- * @throws IllegalStateException
- * If there is no current {@link Environment}, which is usually the case when the current thread isn't
- * processing a template.
- */
- public final void set(Object value) {
- getScopeConfigurable(getRequiredCurrentEnvironment()).setCustomAttribute(key, value);
- }
-
- /**
- * Sets the value of a {@link Template}-scope attribute in the given {@link Template}.
- *
- * @param value
- * The new value of the attribute. Can be {@code null}.
- *
- * @throws UnsupportedOperationException
- * If this custom attribute has different scope than {@link #SCOPE_TEMPLATE}.
- * @throws NullPointerException
- * If {@code template} is null
- */
- public final void set(Object value, Template template) {
- if (scope != SCOPE_TEMPLATE) {
- throw new UnsupportedOperationException("This is not a template-scope attribute");
- }
- template.setCustomAttribute(key, value);
- }
-
- /**
- * Same as {@link #set(Object, Template)}, but applicable to a {@link TemplateConfiguration}.
- *
- * @since 2.3.24
- */
- public final void set(Object value, TemplateConfiguration templateConfiguration) {
- if (scope != SCOPE_TEMPLATE) {
- throw new UnsupportedOperationException("This is not a template-scope attribute");
- }
- templateConfiguration.setCustomAttribute(key, value);
- }
-
- /**
- * Sets the value of a {@link Configuration}-scope attribute in the given {@link Configuration}.
- *
- * @param value
- * The new value of the attribute. Can be {@code null}.
- *
- * @throws UnsupportedOperationException
- * If this custom attribute has different scope than {@link #SCOPE_CONFIGURATION}.
- * @throws NullPointerException
- * If {@code cfg} is null
- *
- * @since 2.3.22
- */
- public final void set(Object value, Configuration cfg) {
- if (scope != SCOPE_CONFIGURATION) {
- throw new UnsupportedOperationException("This is not a configuration-scope attribute");
- }
- cfg.setCustomAttribute(key, value);
- }
-
- private Environment getRequiredCurrentEnvironment() {
- Environment c = Environment.getCurrentEnvironment();
- if (c == null) {
- throw new IllegalStateException("No current environment");
- }
- return c;
- }
-
- private Configurable getScopeConfigurable(Environment env) throws Error {
- switch (scope) {
- case SCOPE_ENVIRONMENT:
- return env;
- case SCOPE_TEMPLATE:
- return env.getParent();
- case SCOPE_CONFIGURATION:
- return env.getParent().getParent();
- default:
- throw new BugException();
- }
- }
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/CustomStateKey.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/CustomStateKey.java b/src/main/java/org/apache/freemarker/core/CustomStateKey.java
new file mode 100644
index 0000000..fd6f4d5
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/CustomStateKey.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+/**
+ * Used with {@link CustomStateScope}-s; each subclass must have exactly one instance, which should be stored in
+ * a static final field. So the usual usage is like this:
+ *
+ * <pre>
+ * static final CustomStateKey MY_STATE = new CustomStateKey() {
+ * @Override
+ * protected Object create() {
+ * return new ...;
+ * }
+ * };
+ * </pre>
+ */
+public abstract class CustomStateKey<T> {
+
+ /**
+ * This will be invoked when the state for this {@link CustomStateKey} is get via {@link
+ * CustomStateScope#getCustomState(CustomStateKey)}, but it doesn't yet exists in the given scope. Then the created
+ * object will be stored in the scope and then it's returned. Must not return {@code null}.
+ */
+ protected abstract T create();
+
+ /**
+ * Does identity comparison (like operator {@code ==}).
+ */
+ @Override
+ final public boolean equals(Object o) {
+ return o == this;
+ }
+
+ /**
+ * Returns {@link Object#hashCode()}.
+ */
+ @Override
+ final public int hashCode() {
+ return super.hashCode();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/CustomStateScope.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/CustomStateScope.java b/src/main/java/org/apache/freemarker/core/CustomStateScope.java
new file mode 100644
index 0000000..4067823
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/CustomStateScope.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * An object that's a scope that can store custom state objects.
+ */
+public interface CustomStateScope {
+
+ /**
+ * Gets the custom state belonging to the key, automatically creating it if it doesn't yet exists in the scope.
+ * If the scope is {@link Configuration} or {@link Template}, then this method is thread safe. If the scope is
+ * {@link Environment}, then this method is not thread safe ({@link Environment} is not thread safe either).
+ */
+ <T> T getCustomState(CustomStateKey<T> customStateKey);
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Environment.java b/src/main/java/org/apache/freemarker/core/Environment.java
index 7eac791..ce95b15 100644
--- a/src/main/java/org/apache/freemarker/core/Environment.java
+++ b/src/main/java/org/apache/freemarker/core/Environment.java
@@ -99,9 +99,9 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
* If you need to modify or read this object before or after the <tt>process</tt> call, use
* {@link Template#createProcessingEnvironment(Object rootMap, Writer out, ObjectWrapper wrapper)}
*/
-public final class Environment extends Configurable {
+public final class Environment extends MutableProcessingConfiguration<Environment> implements CustomStateScope {
- private static final ThreadLocal threadEnv = new ThreadLocal();
+ private static final ThreadLocal<Environment> TLS_ENVIRONMENT = new ThreadLocal();
private static final Logger LOG = _CoreLogs.RUNTIME;
private static final Logger LOG_ATTEMPT = _CoreLogs.ATTEMPT;
@@ -125,6 +125,7 @@ public final class Environment extends Configurable {
private TemplateNumberFormat cachedTemplateNumberFormat;
private Map<String, TemplateNumberFormat> cachedTemplateNumberFormats;
+ private Map<CustomStateKey, Object> customStateMap;
/**
* Stores the date/time/date-time formatters that are used when no format is explicitly given at the place of
@@ -196,11 +197,19 @@ public final class Environment extends Configurable {
* current thread.
*/
public static Environment getCurrentEnvironment() {
- return (Environment) threadEnv.get();
+ return TLS_ENVIRONMENT.get();
+ }
+
+ public static Environment getCurrentEnvironmentNotNull() {
+ Environment currentEnvironment = getCurrentEnvironment();
+ if (currentEnvironment == null) {
+ throw new IllegalStateException("There's no FreeMarker Environemnt in this this thread.");
+ }
+ return currentEnvironment;
}
static void setCurrentEnvironment(Environment env) {
- threadEnv.set(env);
+ TLS_ENVIRONMENT.set(env);
}
public Environment(Template template, final TemplateHashModel rootDataModel, Writer out) {
@@ -242,6 +251,14 @@ public final class Environment extends Configurable {
return ln == 0 ? getMainTemplate() : instructionStack[ln - 1].getTemplate();
}
+ public Template getCurrentTemplateNotNull() {
+ Template currentTemplate = getCurrentTemplate();
+ if (currentTemplate == null) {
+ throw new IllegalStateException("There's no current template at the moment.");
+ }
+ return currentTemplate;
+ }
+
/**
* Gets the currently executing <em>custom</em> directive's call place information, or {@code null} if there's no
* executing custom directive. This currently only works for calls made from templates with the {@code <@...>}
@@ -281,8 +298,8 @@ public final class Environment extends Configurable {
* Processes the template to which this environment belongs to.
*/
public void process() throws TemplateException, IOException {
- Object savedEnv = threadEnv.get();
- threadEnv.set(this);
+ Environment savedEnv = TLS_ENVIRONMENT.get();
+ TLS_ENVIRONMENT.set(this);
try {
// Cached values from a previous execution are possibly outdated.
clearCachedValues();
@@ -298,7 +315,7 @@ public final class Environment extends Configurable {
clearCachedValues();
}
} finally {
- threadEnv.set(savedEnv);
+ TLS_ENVIRONMENT.set(savedEnv);
}
}
@@ -1534,15 +1551,15 @@ public final class Environment extends Configurable {
String settingValue;
switch (dateType) {
case TemplateDateModel.TIME:
- settingName = Configurable.TIME_FORMAT_KEY;
+ settingName = MutableProcessingConfiguration.TIME_FORMAT_KEY;
settingValue = getTimeFormat();
break;
case TemplateDateModel.DATE:
- settingName = Configurable.DATE_FORMAT_KEY;
+ settingName = MutableProcessingConfiguration.DATE_FORMAT_KEY;
settingValue = getDateFormat();
break;
case TemplateDateModel.DATETIME:
- settingName = Configurable.DATETIME_FORMAT_KEY;
+ settingName = MutableProcessingConfiguration.DATETIME_FORMAT_KEY;
settingValue = getDateTimeFormat();
break;
default:
@@ -2647,44 +2664,22 @@ public final class Environment extends Configurable {
return currentNamespace.getTemplate().getDefaultNS();
}
- private IdentityHashMap<Object, Object> customStateVariables;
-
- /**
- * Returns the value of a custom state variable, or {@code null} if it's missing; see
- * {@link #setCustomState(Object, Object)} for more.
- *
- * @since 2.3.24
- */
- public Object getCustomState(Object identityKey) {
- if (customStateVariables == null) {
- return null;
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T getCustomState(CustomStateKey<T> customStateKey) {
+ if (customStateMap == null) {
+ customStateMap = new IdentityHashMap<>();
}
- return customStateVariables.get(identityKey);
- }
-
- /**
- * Sets the value of a custom state variable. Custom state variables meant to be used by
- * {@link TemplateNumberFormatFactory}-es, {@link TemplateDateFormatFactory}-es, and similar user-implementable,
- * pluggable objects, which want to maintain an {@link Environment}-scoped state (such as a cache).
- *
- * @param identityKey
- * The key that identifies the variable, by its object identity (not by {@link Object#equals(Object)}).
- * This should be something like a {@code private static final Object CUSTOM_STATE_KEY = new Object();}
- * in the class that needs this state variable.
- * @param value
- * The value of the variable. Can be anything, even {@code null}.
- *
- * @return The previous value of the variable, or {@code null} if the variable didn't exist.
- *
- * @since 2.3.24
- */
- public Object setCustomState(Object identityKey, Object value) {
- IdentityHashMap<Object, Object> customStateVariables = this.customStateVariables;
- if (customStateVariables == null) {
- customStateVariables = new IdentityHashMap<>();
- this.customStateVariables = customStateVariables;
+ T customState = (T) customStateMap.get(customStateKey);
+ if (customState == null) {
+ customState = customStateKey.create();
+ if (customState == null) {
+ throw new IllegalStateException("CustomStateKey.create() must not return null (for key: "
+ + customStateKey + ")");
+ }
+ customStateMap.put(customStateKey, customState);
}
- return customStateVariables.put(identityKey, value);
+ return customState;
}
final class NestedElementTemplateDirectiveBody implements TemplateDirectiveBody {
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java b/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java
new file mode 100644
index 0000000..2324dce
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java
@@ -0,0 +1,282 @@
+/*
+ * 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.InputStream;
+
+import org.apache.freemarker.core.outputformat.OutputFormat;
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.util._NullArgumentException;
+
+// TODO This will be the superclass of TemplateConfiguration.Builder and Configuration.Builder
+public abstract class MutableProcessingAndParseConfiguration<
+ SelfT extends MutableProcessingAndParseConfiguration<SelfT>>
+ extends MutableProcessingConfiguration<SelfT>
+ implements ParserConfiguration {
+
+ private TemplateLanguage templateLanguage;
+ private Integer tagSyntax;
+ private Integer namingConvention;
+ private Boolean whitespaceStripping;
+ private Integer autoEscapingPolicy;
+ private Boolean recognizeStandardFileExtensions;
+ private OutputFormat outputFormat;
+ private String encoding;
+ private Integer tabSize;
+
+ protected MutableProcessingAndParseConfiguration(Version incompatibleImprovements) {
+ super(incompatibleImprovements);
+ }
+
+ protected MutableProcessingAndParseConfiguration(MutableProcessingConfiguration parent) {
+ super(parent);
+ }
+
+ /**
+ * See {@link Configuration#setTagSyntax(int)}.
+ */
+ public void setTagSyntax(int tagSyntax) {
+ Configuration.valideTagSyntaxValue(tagSyntax);
+ this.tagSyntax = tagSyntax;
+ }
+
+ /**
+ * The getter pair of {@link #setTagSyntax(int)}.
+ */
+ @Override
+ public int getTagSyntax() {
+ return tagSyntax != null ? tagSyntax : getDefaultTagSyntax();
+ }
+
+ protected abstract int getDefaultTagSyntax();
+
+ @Override
+ public boolean isTagSyntaxSet() {
+ return tagSyntax != null;
+ }
+
+ /**
+ * See {@link Configuration#getTemplateLanguage()}
+ */
+ @Override
+ public TemplateLanguage getTemplateLanguage() {
+ return templateLanguage != null ? templateLanguage : getDefaultTemplateLanguage();
+ }
+
+ protected abstract TemplateLanguage getDefaultTemplateLanguage();
+
+ /**
+ * See {@link Configuration#setTemplateLanguage(TemplateLanguage)}
+ */
+ public void setTemplateLanguage(TemplateLanguage templateLanguage) {
+ _NullArgumentException.check("templateLanguage", templateLanguage);
+ this.templateLanguage = templateLanguage;
+ }
+
+ public boolean isTemplateLanguageSet() {
+ return templateLanguage != null;
+ }
+
+ /**
+ * See {@link Configuration#setNamingConvention(int)}.
+ */
+ public void setNamingConvention(int namingConvention) {
+ Configuration.validateNamingConventionValue(namingConvention);
+ this.namingConvention = namingConvention;
+ }
+
+ /**
+ * The getter pair of {@link #setNamingConvention(int)}.
+ */
+ @Override
+ public int getNamingConvention() {
+ return namingConvention != null ? namingConvention
+ : getDefaultNamingConvention();
+ }
+
+ protected abstract int getDefaultNamingConvention();
+
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ */
+ @Override
+ public boolean isNamingConventionSet() {
+ return namingConvention != null;
+ }
+
+ /**
+ * See {@link Configuration#setWhitespaceStripping(boolean)}.
+ */
+ public void setWhitespaceStripping(boolean whitespaceStripping) {
+ this.whitespaceStripping = Boolean.valueOf(whitespaceStripping);
+ }
+
+ /**
+ * The getter pair of {@link #getWhitespaceStripping()}.
+ */
+ @Override
+ public boolean getWhitespaceStripping() {
+ return whitespaceStripping != null ? whitespaceStripping.booleanValue()
+ : getDefaultWhitespaceStripping();
+ }
+
+ protected abstract boolean getDefaultWhitespaceStripping();
+
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ */
+ @Override
+ public boolean isWhitespaceStrippingSet() {
+ return whitespaceStripping != null;
+ }
+
+ /**
+ * Sets the output format of the template; see {@link Configuration#setAutoEscapingPolicy(int)} for more.
+ */
+ public void setAutoEscapingPolicy(int autoEscapingPolicy) {
+ Configuration.validateAutoEscapingPolicyValue(autoEscapingPolicy);
+
+ this.autoEscapingPolicy = Integer.valueOf(autoEscapingPolicy);
+ }
+
+ /**
+ * The getter pair of {@link #setAutoEscapingPolicy(int)}.
+ */
+ @Override
+ public int getAutoEscapingPolicy() {
+ return autoEscapingPolicy != null ? autoEscapingPolicy.intValue()
+ : getDefaultAutoEscapingPolicy();
+ }
+
+ protected abstract int getDefaultAutoEscapingPolicy();
+
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ */
+ @Override
+ public boolean isAutoEscapingPolicySet() {
+ return autoEscapingPolicy != null;
+ }
+
+ /**
+ * Sets the output format of the template; see {@link Configuration#setOutputFormat(OutputFormat)} for more.
+ */
+ public void setOutputFormat(OutputFormat outputFormat) {
+ _NullArgumentException.check("outputFormat", outputFormat);
+ this.outputFormat = outputFormat;
+ }
+
+ /**
+ * The getter pair of {@link #setOutputFormat(OutputFormat)}.
+ */
+ @Override
+ public OutputFormat getOutputFormat() {
+ return outputFormat != null ? outputFormat : getDefaultOutputFormat();
+ }
+
+ protected abstract OutputFormat getDefaultOutputFormat();
+
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ */
+ @Override
+ public boolean isOutputFormatSet() {
+ return outputFormat != null;
+ }
+
+ /**
+ * See {@link Configuration#setRecognizeStandardFileExtensions(boolean)}.
+ */
+ public void setRecognizeStandardFileExtensions(boolean recognizeStandardFileExtensions) {
+ this.recognizeStandardFileExtensions = Boolean.valueOf(recognizeStandardFileExtensions);
+ }
+
+ /**
+ * Getter pair of {@link #setRecognizeStandardFileExtensions(boolean)}.
+ */
+ @Override
+ public boolean getRecognizeStandardFileExtensions() {
+ return recognizeStandardFileExtensions != null ? recognizeStandardFileExtensions.booleanValue()
+ : getDefaultRecognizeStandardFileExtensions();
+ }
+
+ protected abstract boolean getDefaultRecognizeStandardFileExtensions();
+
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ */
+ @Override
+ public boolean isRecognizeStandardFileExtensionsSet() {
+ return recognizeStandardFileExtensions != null;
+ }
+
+ @Override
+ public String getEncoding() {
+ return encoding != null ? encoding : getDefaultEncoding();
+ }
+
+ protected abstract String getDefaultEncoding();
+
+ /**
+ * The charset to be used when reading the template "file" that the {@link TemplateLoader} returns as binary
+ * ({@link InputStream}). If the {@code #ftl} header sepcifies an encoding, that will override this.
+ */
+ public void setEncoding(String encoding) {
+ _NullArgumentException.check("encoding", encoding);
+ this.encoding = encoding;
+ }
+
+ public boolean isEncodingSet() {
+ return encoding != null;
+ }
+
+ /**
+ * See {@link Configuration#setTabSize(int)}.
+ *
+ * @since 2.3.25
+ */
+ public void setTabSize(int tabSize) {
+ this.tabSize = Integer.valueOf(tabSize);
+ }
+
+ /**
+ * Getter pair of {@link #setTabSize(int)}.
+ *
+ * @since 2.3.25
+ */
+ @Override
+ public int getTabSize() {
+ return tabSize != null ? tabSize.intValue()
+ : getDefailtTabSize();
+ }
+
+ protected abstract int getDefailtTabSize();
+
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ *
+ * @since 2.3.25
+ */
+ @Override
+ public boolean isTabSizeSet() {
+ return tabSize != null;
+ }
+
+}