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; + } + +}
