Made Template immutable (via public API-s). Template-specific settings now can only come from the TemplateConfiguration associated to the template, or from the #ftl header for some settings (most notably for custom attributes). Many smaller cleanups in the still evolving new configuration setting API-s. An important change is that TemplateConfiguration doesn't store the Configuration anymore, thus we don't have a loop in the bean dependency graph anymore. Also, getParent() is not part of the these API-s anymore, as for many configuration API implementations it doesn't make sense anymore.
Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/0356b30b Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/0356b30b Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/0356b30b Branch: refs/heads/3 Commit: 0356b30b05ddb2699b92c7ead768541b7563c408 Parents: 413c0e1 Author: ddekany <[email protected]> Authored: Tue Apr 11 22:04:51 2017 +0200 Committer: ddekany <[email protected]> Committed: Tue Apr 11 22:04:51 2017 +0200 ---------------------------------------------------------------------- .../Editor-Inspections-FreeMarker.xml | 18 + .../Java-code-style-FreeMarker.xml | 18 + .../freemarker/core/ASTExpStringLiteral.java | 8 +- .../freemarker/core/BuiltInsForStringsMisc.java | 23 +- .../apache/freemarker/core/Configuration.java | 3 - .../org/apache/freemarker/core/Environment.java | 74 +-- .../MutableProcessingAndParseConfiguration.java | 88 +++- .../core/MutableProcessingConfiguration.java | 114 ++-- .../freemarker/core/ParserConfiguration.java | 3 +- .../core/SettingValueNotSetException.java | 4 +- .../org/apache/freemarker/core/Template.java | 524 ++++++++++++++----- .../freemarker/core/TemplateConfiguration.java | 420 ++++++--------- .../freemarker/core/TemplateLanguage.java | 19 +- ...TemplateParserConfigurationWithFallback.java | 146 ++++++ ..._ParserConfigurationWithInheritedFormat.java | 147 ------ ...ConditionalTemplateConfigurationFactory.java | 11 - .../FirstMatchTemplateConfigurationFactory.java | 8 - .../MergingTemplateConfigurationFactory.java | 23 +- .../TemplateConfigurationFactory.java | 36 -- .../templateresolver/TemplateLoadingResult.java | 5 +- .../impl/DefaultTemplateResolver.java | 19 +- src/main/javacc/FTL.jj | 48 +- src/manual/en_US/FM3-CHANGE-LOG.txt | 4 + .../freemarker/core/ConfigurableTest.java | 2 +- .../freemarker/core/ConfigurationTest.java | 182 ++++--- .../freemarker/core/CustomAttributeTest.java | 161 +++--- .../IncludeAndImportConfigurableLayersTest.java | 19 +- .../freemarker/core/OutputFormatTest.java | 8 +- .../core/TemplateConfigurationTest.java | 164 +----- .../freemarker/core/TemplateLevelSettings.java | 103 ---- .../TemplateConfigurationFactoryTest.java | 37 +- 31 files changed, 1200 insertions(+), 1239 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml ---------------------------------------------------------------------- diff --git a/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml b/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml index c2e6c17..4b40e57 100644 --- a/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml +++ b/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml @@ -1,3 +1,21 @@ +<!-- + 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. +--> <component name="InspectionProjectProfileManager"> <profile version="1.0"> <option name="myName" value="FreeMarker" /> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml ---------------------------------------------------------------------- diff --git a/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml b/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml index d77ca2d..983f742 100644 --- a/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml +++ b/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml @@ -1,3 +1,21 @@ +<!-- + 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. +--> <code_scheme name="FreeMarker"> <option name="LINE_SEPARATOR" value="
" /> <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" /> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java b/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java index 6af5d27..972752a 100644 --- a/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java +++ b/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java @@ -54,19 +54,21 @@ final class ASTExpStringLiteral extends ASTExpression implements TemplateScalarM if (value.length() > 3 && (value.indexOf("${") >= 0 || value.indexOf("#{") >= 0)) { Template parentTemplate = getTemplate(); - ParserConfiguration pcfg = parentTemplate.getParserConfiguration(); + ParserConfiguration pCfg = parentTemplate.getParserConfiguration(); try { SimpleCharStream simpleCharacterStream = new SimpleCharStream( new StringReader(value), beginLine, beginColumn + 1, value.length()); - simpleCharacterStream.setTabSize(pcfg.getTabSize()); + simpleCharacterStream.setTabSize(pCfg.getTabSize()); FMParserTokenManager tkMan = new FMParserTokenManager( simpleCharacterStream); - FMParser parser = new FMParser(parentTemplate, false, tkMan, pcfg, null); + FMParser parser = new FMParser(parentTemplate, false, + tkMan, pCfg, null, null, + null); // We continue from the parent parser's current state: parser.setupStringLiteralMode(parentTkMan, outputFormat); try { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java index f83cbbb..ae06392 100644 --- a/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java +++ b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java @@ -86,14 +86,11 @@ class BuiltInsForStringsMisc { simpleCharStream); tkMan.SwitchTo(FMParserConstants.FM_EXPRESSION); - // pCfg.outputFormat is exceptional: it's inherited from the lexical context - if (pCfg.getOutputFormat() != outputFormat) { - pCfg = new _ParserConfigurationWithInheritedFormat( - pCfg, outputFormat, Integer.valueOf(autoEscapingPolicy)); - } - + // pCfg.outputFormat+autoEscapingPolicy is exceptional: it's inherited from the lexical context FMParser parser = new FMParser( - parentTemplate, false, tkMan, pCfg, null); + parentTemplate, false, tkMan, + pCfg, outputFormat, autoEscapingPolicy, + null); exp = parser.ASTExpression(); } catch (TokenMgrError e) { @@ -172,17 +169,14 @@ class BuiltInsForStringsMisc { final Template interpretedTemplate; try { ParserConfiguration pCfg = parentTemplate.getParserConfiguration(); - // pCfg.outputFormat is exceptional: it's inherited from the lexical context - if (pCfg.getOutputFormat() != outputFormat) { - pCfg = new _ParserConfigurationWithInheritedFormat( - pCfg, outputFormat, Integer.valueOf(autoEscapingPolicy)); - } + // pCfg.outputFormat+autoEscapingPolicy is exceptional: it's inherited from the lexical context interpretedTemplate = new Template( (parentTemplate.getName() != null ? parentTemplate.getName() : "nameless_template") + "->" + id, null, new StringReader(templateSource), - parentTemplate.getConfiguration(), pCfg, - null); + parentTemplate.getConfiguration(), parentTemplate.getTemplateConfiguration(), + outputFormat, autoEscapingPolicy, + null, null); } catch (IOException e) { throw new _MiscTemplateException(this, e, env, "Template parsing with \"?", key, "\" has failed with this error:\n\n", @@ -192,7 +186,6 @@ class BuiltInsForStringsMisc { "\n\nThe failed expression:"); } - interpretedTemplate.setLocale(env.getLocale()); return new TemplateProcessorModel(interpretedTemplate); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/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 c8e700a..5d2cd34 100644 --- a/src/main/java/org/apache/freemarker/core/Configuration.java +++ b/src/main/java/org/apache/freemarker/core/Configuration.java @@ -793,9 +793,6 @@ public final class Configuration extends MutableProcessingConfiguration<Configur */ public void setTemplateConfigurations(TemplateConfigurationFactory templateConfigurations) { if (templateResolver.getTemplateConfigurations() != templateConfigurations) { - if (templateConfigurations != null) { - templateConfigurations.setConfiguration(this); - } recreateTemplateResolverWith(templateResolver.getTemplateLoader(), templateResolver.getCacheStorage(), templateResolver.getTemplateLookupStrategy(), templateResolver.getTemplateNameFormat(), templateConfigurations); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/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 5382404..1f75e2f 100644 --- a/src/main/java/org/apache/freemarker/core/Environment.java +++ b/src/main/java/org/apache/freemarker/core/Environment.java @@ -173,6 +173,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen private Writer out; private ASTDirMacro.Context currentMacroContext; private LocalContextStack localContextStack; + private final Template mainTemplate; private final Namespace mainNamespace; private Namespace currentNamespace, globalNamespace; private HashMap<String, Namespace> loadedLibs; @@ -217,10 +218,10 @@ public final class Environment extends MutableProcessingConfiguration<Environmen } public Environment(Template template, final TemplateHashModel rootDataModel, Writer out) { - super(template); + mainTemplate = template; configuration = template.getConfiguration(); globalNamespace = new Namespace(null); - currentNamespace = mainNamespace = new Namespace(template); + currentNamespace = mainNamespace = new Namespace(mainTemplate); this.out = out; this.rootDataModel = rootDataModel; importMacros(template); @@ -235,7 +236,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen * @since 2.3.22 */ public Template getMainTemplate() { - return mainNamespace.getTemplate(); + return mainTemplate; } /** @@ -922,17 +923,17 @@ public final class Environment extends MutableProcessingConfiguration<Environmen @Override protected TemplateExceptionHandler getInheritedTemplateExceptionHandler() { - return getParent().getTemplateExceptionHandler(); + return getMainTemplate().getTemplateExceptionHandler(); } @Override protected ArithmeticEngine getInheritedArithmeticEngine() { - return getParent().getArithmeticEngine(); + return getMainTemplate().getArithmeticEngine(); } @Override protected ObjectWrapper getInheritedObjectWrapper() { - return getParent().getObjectWrapper(); + return getMainTemplate().getObjectWrapper(); } @Override @@ -962,7 +963,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen @Override protected Locale getInheritedLocale() { - return getParent().getLocale(); + return getMainTemplate().getLocale(); } @Override @@ -991,7 +992,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen @Override protected TimeZone getInheritedTimeZone() { - return getParent().getTimeZone(); + return getMainTemplate().getTimeZone(); } @Override @@ -1020,7 +1021,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen @Override protected TimeZone getInheritedSQLDateAndTimeTimeZone() { - return getParent().getSQLDateAndTimeTimeZone(); + return getMainTemplate().getSQLDateAndTimeTimeZone(); } // Replace with Objects.equals in Java 7 @@ -1051,57 +1052,57 @@ public final class Environment extends MutableProcessingConfiguration<Environmen @Override protected Charset getInheritedURLEscapingCharset() { - return getParent().getURLEscapingCharset(); + return getMainTemplate().getURLEscapingCharset(); } @Override protected TemplateClassResolver getInheritedNewBuiltinClassResolver() { - return getParent().getNewBuiltinClassResolver(); + return getMainTemplate().getNewBuiltinClassResolver(); } @Override protected boolean getInheritedAutoFlush() { - return getParent().getAutoFlush(); + return getMainTemplate().getAutoFlush(); } @Override protected boolean getInheritedShowErrorTips() { - return getParent().getShowErrorTips(); + return getMainTemplate().getShowErrorTips(); } @Override protected boolean getInheritedAPIBuiltinEnabled() { - return getParent().getAPIBuiltinEnabled(); + return getMainTemplate().getAPIBuiltinEnabled(); } @Override protected boolean getInheritedLogTemplateExceptions() { - return getParent().getLogTemplateExceptions(); + return getMainTemplate().getLogTemplateExceptions(); } @Override protected boolean getInheritedLazyImports() { - return getParent().getLazyImports(); + return getMainTemplate().getLazyImports(); } @Override protected Boolean getInheritedLazyAutoImports() { - return getParent().getLazyAutoImports(); + return getMainTemplate().getLazyAutoImports(); } @Override protected Map<String, String> getInheritedAutoImports() { - return getParent().getAutoImports(); + return getMainTemplate().getAutoImports(); } @Override protected List<String> getInheritedAutoIncludes() { - return getParent().getAutoIncludes(); + return getMainTemplate().getAutoIncludes(); } @Override protected Object getInheritedCustomAttribute(Object name) { - return getParent().getCustomAttribute(name); + return getMainTemplate().getCustomAttribute(name); } /* @@ -1117,7 +1118,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen @Override protected Charset getInheritedOutputEncoding() { - return getParent().getOutputEncoding(); + return getMainTemplate().getOutputEncoding(); } /** @@ -1220,27 +1221,27 @@ public final class Environment extends MutableProcessingConfiguration<Environmen @Override protected String getInheritedNumberFormat() { - return getParent().getNumberFormat(); + return getMainTemplate().getNumberFormat(); } @Override protected Map<String, TemplateNumberFormatFactory> getInheritedCustomNumberFormats() { - return getParent().getCustomNumberFormats(); + return getMainTemplate().getCustomNumberFormats(); } @Override protected TemplateNumberFormatFactory getInheritedCustomNumberFormat(String name) { - return getParent().getCustomNumberFormat(name); + return getMainTemplate().getCustomNumberFormat(name); } @Override protected boolean getInheritedHasCustomFormats() { - return getParent().hasCustomFormats(); + return getMainTemplate().hasCustomFormats(); } @Override protected String getInheritedBooleanFormat() { - return getParent().getBooleanFormat(); + return getMainTemplate().getBooleanFormat(); } String formatBoolean(boolean value, boolean fallbackToTrueFalse) throws TemplateException { @@ -1535,7 +1536,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen @Override protected String getInheritedTimeFormat() { - return getParent().getTimeFormat(); + return getMainTemplate().getTimeFormat(); } @Override @@ -1553,7 +1554,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen @Override protected String getInheritedDateFormat() { - return getParent().getDateFormat(); + return getMainTemplate().getDateFormat(); } @Override @@ -1571,17 +1572,17 @@ public final class Environment extends MutableProcessingConfiguration<Environmen @Override protected String getInheritedDateTimeFormat() { - return getParent().getDateTimeFormat(); + return getMainTemplate().getDateTimeFormat(); } @Override protected Map<String, TemplateDateFormatFactory> getInheritedCustomDateFormats() { - return getParent().getCustomDateFormats(); + return getMainTemplate().getCustomDateFormats(); } @Override protected TemplateDateFormatFactory getInheritedCustomDateFormat(String name) { - return getParent().getCustomDateFormat(name); + return getMainTemplate().getCustomDateFormat(name); } public Configuration getConfiguration() { @@ -2986,6 +2987,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen private Template template; + // TODO [FM3] #macro etc. uses this, so the NS is associated to the main temp., even if #macro is elsewhere. Namespace() { this(Environment.this.getMainTemplate()); } @@ -3001,9 +3003,15 @@ public final class Environment extends MutableProcessingConfiguration<Environmen public Template getTemplate() { return template == null ? Environment.this.getMainTemplate() : template; } - + + /** + * Used when initializing a lazily initialized namespace. + */ void setTemplate(Template template) { - this.template = template; + if (this.template != null) { + throw new IllegalStateException("Can't change the template of a namespace once it was established."); + } + this.template = template; } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/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 index aaac98d..6b47360 100644 --- a/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java +++ b/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java @@ -46,8 +46,8 @@ public abstract class MutableProcessingAndParseConfiguration< super(incompatibleImprovements); } - protected MutableProcessingAndParseConfiguration(MutableProcessingConfiguration parent) { - super(parent); + protected MutableProcessingAndParseConfiguration() { + super(); } /** @@ -59,6 +59,14 @@ public abstract class MutableProcessingAndParseConfiguration< } /** + * Fluent API equivalent of {@link #tagSyntax(int)} + */ + public SelfT tagSyntax(int tagSyntax) { + setTagSyntax(tagSyntax); + return self(); + } + + /** * The getter pair of {@link #setTagSyntax(int)}. */ @Override @@ -91,6 +99,14 @@ public abstract class MutableProcessingAndParseConfiguration< this.templateLanguage = templateLanguage; } + /** + * Fluent API equivalent of {@link #setTemplateLanguage(TemplateLanguage)} + */ + public SelfT templateLanguage(TemplateLanguage templateLanguage) { + setTemplateLanguage(templateLanguage); + return self(); + } + public boolean isTemplateLanguageSet() { return templateLanguage != null; } @@ -104,6 +120,14 @@ public abstract class MutableProcessingAndParseConfiguration< } /** + * Fluent API equivalent of {@link #setNamingConvention(int)} + */ + public SelfT namingConvention(int namingConvention) { + setNamingConvention(namingConvention); + return self(); + } + + /** * The getter pair of {@link #setNamingConvention(int)}. */ @Override @@ -115,7 +139,7 @@ public abstract class MutableProcessingAndParseConfiguration< protected abstract int getInheritedNamingConvention(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration.. */ @Override public boolean isNamingConventionSet() { @@ -130,6 +154,14 @@ public abstract class MutableProcessingAndParseConfiguration< } /** + * Fluent API equivalent of {@link #setWhitespaceStripping(boolean)} + */ + public SelfT whitespaceStripping(boolean whitespaceStripping) { + setWhitespaceStripping(whitespaceStripping); + return self(); + } + + /** * The getter pair of {@link #getWhitespaceStripping()}. */ @Override @@ -141,7 +173,7 @@ public abstract class MutableProcessingAndParseConfiguration< protected abstract boolean getInheritedWhitespaceStripping(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration.. */ @Override public boolean isWhitespaceStrippingSet() { @@ -158,6 +190,14 @@ public abstract class MutableProcessingAndParseConfiguration< } /** + * Fluent API equivalent of {@link #setAutoEscapingPolicy(int)} + */ + public SelfT autoEscapingPolicy(int autoEscapingPolicy) { + setAutoEscapingPolicy(autoEscapingPolicy); + return self(); + } + + /** * The getter pair of {@link #setAutoEscapingPolicy(int)}. */ @Override @@ -169,7 +209,7 @@ public abstract class MutableProcessingAndParseConfiguration< protected abstract int getInheritedAutoEscapingPolicy(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration.. */ @Override public boolean isAutoEscapingPolicySet() { @@ -185,6 +225,14 @@ public abstract class MutableProcessingAndParseConfiguration< } /** + * Fluent API equivalent of {@link #setOutputFormat(OutputFormat)} + */ + public SelfT outputFormat(OutputFormat outputFormat) { + setOutputFormat(outputFormat); + return self(); + } + + /** * The getter pair of {@link #setOutputFormat(OutputFormat)}. */ @Override @@ -195,7 +243,7 @@ public abstract class MutableProcessingAndParseConfiguration< protected abstract OutputFormat getInheritedOutputFormat(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration.. */ @Override public boolean isOutputFormatSet() { @@ -210,6 +258,14 @@ public abstract class MutableProcessingAndParseConfiguration< } /** + * Fluent API equivalent of {@link #setRecognizeStandardFileExtensions(boolean)} + */ + public SelfT recognizeStandardFileExtensions(boolean recognizeStandardFileExtensions) { + setRecognizeStandardFileExtensions(recognizeStandardFileExtensions); + return self(); + } + + /** * Getter pair of {@link #setRecognizeStandardFileExtensions(boolean)}. */ @Override @@ -221,7 +277,7 @@ public abstract class MutableProcessingAndParseConfiguration< protected abstract boolean getInheritedRecognizeStandardFileExtensions(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration.. */ @Override public boolean isRecognizeStandardFileExtensionsSet() { @@ -244,6 +300,14 @@ public abstract class MutableProcessingAndParseConfiguration< this.sourceEncoding = sourceEncoding; } + /** + * Fluent API equivalent of {@link #setSourceEncoding(Charset)} + */ + public SelfT sourceEncoding(Charset sourceEncoding) { + setSourceEncoding(sourceEncoding); + return self(); + } + public boolean isSourceEncodingSet() { return sourceEncoding != null; } @@ -258,6 +322,14 @@ public abstract class MutableProcessingAndParseConfiguration< } /** + * Fluent API equivalent of {@link #setTabSize(int)} + */ + public SelfT tabSize(int tabSize) { + setTabSize(tabSize); + return self(); + } + + /** * Getter pair of {@link #setTabSize(int)}. * * @since 2.3.25 @@ -270,7 +342,7 @@ public abstract class MutableProcessingAndParseConfiguration< protected abstract int getInheritedTabSize(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration.. * * @since 2.3.25 */ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java b/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java index 305194d..76204df 100644 --- a/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java +++ b/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java @@ -317,8 +317,6 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces URL_ESCAPING_CHARSET_KEY_CAMEL_CASE }; - private ProcessingConfiguration parent; - private Locale locale; private String numberFormat; private String timeFormat; @@ -354,9 +352,8 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces * default without marking them as set. */ // TODO Move to Configuration(Builder) constructor - MutableProcessingConfiguration(Version incompatibleImprovements) { - _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements); - parent = null; + MutableProcessingConfiguration(Version iciForDefaults) { + _CoreAPI.checkVersionNotNullAndSupported(iciForDefaults); locale = Configuration.getDefaultLocale(); timeZone = Configuration.getDefaultTimeZone(); sqlDateAndTimeTimeZone = null; @@ -367,7 +364,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces dateTimeFormat = ""; templateExceptionHandler = Configuration.getDefaultTemplateExceptionHandler(); arithmeticEngine = BigDecimalArithmeticEngine.INSTANCE; - objectWrapper = Configuration.getDefaultObjectWrapper(incompatibleImprovements); + objectWrapper = Configuration.getDefaultObjectWrapper(iciForDefaults); autoFlush = Boolean.TRUE; newBuiltinClassResolver = TemplateClassResolver.UNRESTRICTED_RESOLVER; showErrorTips = Boolean.TRUE; @@ -391,44 +388,10 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces * Creates a new instance. Normally you do not need to use this constructor, * as you don't use <code>MutableProcessingConfiguration</code> directly, but its subclasses. */ - protected MutableProcessingConfiguration(MutableProcessingConfiguration parent) { - this.parent = parent; - locale = null; - numberFormat = null; - templateExceptionHandler = null; - } - - /** - * Returns the parent {@link MutableProcessingConfiguration} object of this object. The parent stores the default setting values for - * this {@link MutableProcessingConfiguration}. For example, the parent of a {@link org.apache.freemarker.core.Template} object is a - * {@link Configuration} object, so values not specified on {@link Template}-level are get from the - * {@link Configuration} object. - * - * <p> - * Note on the parent of {@link Environment}: If you set {@link Configuration#setIncompatibleImprovements(Version) - * incompatible_improvements} to at least 2.3.22, it will be always the "main" {@link Template}, that is, the - * template for whose processing the {@link Environment} was created. With lower {@code incompatible_improvements}, - * the current parent can temporary change <em>during template execution</em>, for example when your are inside an - * {@code #include}-d template (among others). Thus, don't build on which {@link Template} the parent of - * {@link Environment} is during template execution, unless you set {@code incompatible_improvements} to 2.3.22 or - * higher. - * - * @return The parent {@link MutableProcessingConfiguration} object, or {@code null} if this is the root {@link MutableProcessingConfiguration} object - * (i.e, if it's the {@link Configuration} object). - */ - public final ProcessingConfiguration getParent() { - return parent; + protected MutableProcessingConfiguration() { + // Empty } - - /** - * Reparenting support. This is used by Environment when it includes a - * template - the included template becomes the parent configurable during - * its evaluation. - */ - void setParent(ProcessingConfiguration parent) { - this.parent = parent; - } - + /** * Sets the default locale used for number and date formatting (among others), also the locale used for searching * localized template variations when no locale was explicitly requested. @@ -456,7 +419,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract Locale getInheritedLocale(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -500,7 +463,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract TimeZone getInheritedTimeZone(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -603,7 +566,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract TimeZone getInheritedSQLDateAndTimeTimeZone(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -661,7 +624,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract String getInheritedNumberFormat(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -744,7 +707,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces } /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -829,7 +792,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract String getInheritedBooleanFormat(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -870,7 +833,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract String getInheritedTimeFormat(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -911,7 +874,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract String getInheritedDateFormat(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -1030,7 +993,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract String getInheritedDateTimeFormat(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -1091,7 +1054,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces } /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -1164,7 +1127,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract TemplateExceptionHandler getInheritedTemplateExceptionHandler(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -1202,7 +1165,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract ArithmeticEngine getInheritedArithmeticEngine(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -1240,7 +1203,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract ObjectWrapper getInheritedObjectWrapper(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -1280,7 +1243,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract Charset getInheritedOutputEncoding(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -1318,7 +1281,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract Charset getInheritedURLEscapingCharset(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -1369,7 +1332,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract TemplateClassResolver getInheritedNewBuiltinClassResolver(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -1419,7 +1382,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract boolean getInheritedAutoFlush(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -1459,7 +1422,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract boolean getInheritedShowErrorTips(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -1499,7 +1462,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract boolean getInheritedAPIBuiltinEnabled(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -1536,7 +1499,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract boolean getInheritedLogTemplateExceptions(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.24 */ @@ -1584,7 +1547,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces } /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.25 */ @@ -1619,7 +1582,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces } /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.25 */ @@ -1745,7 +1708,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract Map<String,String> getInheritedAutoImports(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.25 */ @@ -1841,7 +1804,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces protected abstract List<String> getInheritedAutoIncludes(); /** - * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration. * * @since 2.3.25 */ @@ -2597,7 +2560,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces * attribute is not present in the configurable, and the configurable has * a parent, then the parent is looked up as well. * - * @param name the name of the custom attribute + * @param key the name of the custom attribute * * @return the value of the custom attribute. Note that if the custom attribute * was created with <tt><#ftl attributes={...}></tt>, then this value is already @@ -2605,20 +2568,17 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces * <code>Map</code>, ...etc., not a FreeMarker specific class). */ @Override - public Object getCustomAttribute(Object name) { - Object r; + public Object getCustomAttribute(Object key) { + Object value; if (customAttributes != null) { - r = customAttributes.get(name); - if (r == null && customAttributes.containsKey(name)) { + value = customAttributes.get(key); + if (value == null && customAttributes.containsKey(key)) { return null; } } else { - r = null; - } - if (r == null && parent != null) { - return getInheritedCustomAttribute(name); + value = null; } - return r; + return value != null ? value : getInheritedCustomAttribute(key); } protected abstract Object getInheritedCustomAttribute(Object name); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/ParserConfiguration.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ParserConfiguration.java b/src/main/java/org/apache/freemarker/core/ParserConfiguration.java index 599adbb..87bab74 100644 --- a/src/main/java/org/apache/freemarker/core/ParserConfiguration.java +++ b/src/main/java/org/apache/freemarker/core/ParserConfiguration.java @@ -123,7 +123,8 @@ public interface ParserConfiguration { boolean isRecognizeStandardFileExtensionsSet(); /** - * See {@link Configuration#getIncompatibleImprovements()}. + * See {@link Configuration#getIncompatibleImprovements()}; as this is normally directly delegates to + * {@link Configuration#getIncompatibleImprovements()}, it's always set. */ Version getIncompatibleImprovements(); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/SettingValueNotSetException.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/SettingValueNotSetException.java b/src/main/java/org/apache/freemarker/core/SettingValueNotSetException.java index 6ff7bab..1ce895d 100644 --- a/src/main/java/org/apache/freemarker/core/SettingValueNotSetException.java +++ b/src/main/java/org/apache/freemarker/core/SettingValueNotSetException.java @@ -26,8 +26,8 @@ public class SettingValueNotSetException extends IllegalStateException { private final String settingName; public SettingValueNotSetException(String settingName) { - super("Setting " + _StringUtil.jQuote(settingName) - + " is not set in this layer and has no default here either."); + super("The " + _StringUtil.jQuote(settingName) + + " setting is not set in this layer and has no default here either."); this.settingName = settingName; } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/Template.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/Template.java b/src/main/java/org/apache/freemarker/core/Template.java index 5e58508..8671dc1 100644 --- a/src/main/java/org/apache/freemarker/core/Template.java +++ b/src/main/java/org/apache/freemarker/core/Template.java @@ -35,6 +35,7 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -77,49 +78,51 @@ import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory; * shared {@link Configuration}, and you are using {@link Configuration#getTemplate(String)} (or its overloads), then * use {@link Configuration#setTemplateConfigurations(org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory)} to achieve that. */ -public class Template extends MutableProcessingConfiguration<Template> implements CustomStateScope { +// TODO [FM3] Try to make Template serializable for distributed caching. Transient fields will have to be restored. +public class Template implements ProcessingConfiguration, CustomStateScope { public static final String DEFAULT_NAMESPACE_PREFIX = "D"; public static final String NO_NS_PREFIX = "N"; private static final int READER_BUFFER_SIZE = 8192; - - private Map macros = new HashMap(); - private List imports = new ArrayList(); + private ASTElement rootElement; - private Charset actualSourceEncoding; - private String defaultNS; - private Serializable customLookupCondition; - private int actualTagSyntax; - private int actualNamingConvention; - private boolean autoEscaping; - private OutputFormat outputFormat; - private final String name; + private Map macros = new HashMap(); // TODO Don't create new object if it remains empty. + private List imports = new ArrayList(); // TODO Don't create new object if it remains empty. + + // Source (TemplateLoader) related information: private final String sourceName; private final ArrayList lines = new ArrayList(); - private final ParserConfiguration parserConfiguration; + + // TODO [FM3] We want to get rid of these, thenthe same Template object could be reused for different lookups. + // Template lookup parameters: + private final String name; + private Locale lookupLocale; + private Serializable customLookupCondition; + + // Inherited settings: + private final transient Configuration cfg; + private final transient TemplateConfiguration tCfg; + private final transient ParserConfiguration parserConfiguration; + + // Values from the template content (#ftl header parameters usually), as opposed to from the TemplateConfiguration: + private transient OutputFormat outputFormat; // TODO Deserialization: use the name of the output format + private String defaultNS; private Map prefixToNamespaceURILookup = new HashMap(); private Map namespaceURIToPrefixLookup = new HashMap(); + private Map<String, Serializable> customAttributes; + private transient Map<Object, Object> mergedCustomAttributes; + + private Integer autoEscapingPolicy; + // Values from template content that are detected automatically: + private Charset actualSourceEncoding; + private int actualTagSyntax; + private int actualNamingConvention; + // Custom state: private final Object lock = new Object(); private final ConcurrentHashMap<CustomStateKey, Object> customStateMap = new ConcurrentHashMap<>(0); /** - * A prime constructor to which all other constructors should - * delegate directly or indirectly. - */ - private Template(String name, String sourceName, Configuration cfg, ParserConfiguration customParserConfiguration) { - super(cfg); - _NullArgumentException.check("cfg", cfg); - this.name = name; - this.sourceName = sourceName; - if (customParserConfiguration instanceof TemplateConfiguration.Builder) { - throw new IllegalArgumentException("Using TemplateConfiguration.Builder as Template constructor " - + "argument is not allowed; the TemplateConfiguration that it has built is needed instead."); - } - parserConfiguration = customParserConfiguration != null ? customParserConfiguration : getConfiguration(); - } - - /** * Same as {@link #Template(String, String, Reader, Configuration)} with {@code null} {@code sourceName} parameter. */ public Template(String name, Reader reader, Configuration cfg) throws IOException { @@ -137,6 +140,16 @@ public class Template extends MutableProcessingConfiguration<Template> implement } /** + * Convenience constructor for {@link #Template(String, String, Reader, Configuration, TemplateConfiguration, + * Charset) Template(name, null, new StringReader(reader), cfg), tc, null}. + * + * @since 2.3.20 + */ + public Template(String name, String sourceCode, Configuration cfg, TemplateConfiguration tc) throws IOException { + this(name, null, new StringReader(sourceCode), cfg, tc, null); + } + + /** * Convenience constructor for {@link #Template(String, String, Reader, Configuration, Charset) Template(name, null, * reader, cfg, sourceEncoding)}. */ @@ -180,12 +193,12 @@ public class Template extends MutableProcessingConfiguration<Template> implement String name, String sourceName, Reader reader, Configuration cfg) throws IOException { this(name, sourceName, reader, cfg, null); } - + /** * Same as {@link #Template(String, String, Reader, Configuration)}, but also specifies the template's source * encoding. * - * @param sourceEncoding + * @param actualSourceEncoding * This is the charset that was used to read the template. This can be {@code null} if the template * was loaded from a source that returns it already as text. If this is not {@code null} and there's an * {@code #ftl} header with {@code encoding} parameter, they must match, or else a @@ -194,43 +207,37 @@ public class Template extends MutableProcessingConfiguration<Template> implement * @since 2.3.22 */ public Template( - String name, String sourceName, Reader reader, Configuration cfg, Charset sourceEncoding) throws + String name, String sourceName, Reader reader, Configuration cfg, Charset actualSourceEncoding) throws IOException { - this(name, sourceName, reader, cfg, null, sourceEncoding); + this(name, sourceName, reader, cfg, null, actualSourceEncoding); } - + /** * Same as {@link #Template(String, String, Reader, Configuration, Charset)}, but also specifies a * {@link TemplateConfiguration}. This is mostly meant to be used by FreeMarker internally, but advanced users might * still find this useful. * - * @param customParserConfiguration - * Overrides the parsing related configuration settings of the {@link Configuration} parameter; can be + * @param templateConfiguration + * Overrides the configuration settings of the {@link Configuration} parameter; can be * {@code null}. This is useful as the {@link Configuration} is normally a singleton shared by all - * templates, and so it's not good for specifying template-specific settings. (While {@link Template} - * itself has methods to specify settings just for that template, those don't influence the parsing, and - * you only have opportunity to call them after the parsing anyway.) This objects is often a - * {@link TemplateConfiguration} whose parent is the {@link Configuration} parameter, and then it - * practically just overrides some of the parser settings, as the others are inherited from the - * {@link Configuration}. Note that if this is a {@link TemplateConfiguration}, you will also want to - * call {@link TemplateConfiguration#apply(Template)} on the resulting {@link Template} so that - * {@link MutableProcessingConfiguration} settings will be set too, because this constructor only uses it as a - * {@link ParserConfiguration}. - * @param sourceEncoding + * templates, and so it's not good for specifying template-specific settings. Settings that influence + * parsing always have an effect, while settings that influence processing only have effect when the + * template is the main template of the {@link Environment}. + * @param actualSourceEncoding * Same as in {@link #Template(String, String, Reader, Configuration, Charset)}. * * @since 2.3.24 */ public Template( String name, String sourceName, Reader reader, - Configuration cfg, ParserConfiguration customParserConfiguration, - Charset sourceEncoding) throws IOException { - this(name, sourceName, reader, cfg, customParserConfiguration, sourceEncoding, null); + Configuration cfg, TemplateConfiguration templateConfiguration, + Charset actualSourceEncoding) throws IOException { + this(name, sourceName, reader, cfg, templateConfiguration, actualSourceEncoding, null); } /** - * Same as {@link #Template(String, String, Reader, Configuration, ParserConfiguration, Charset)}, but allows - * specifying the {@code streamToUnmarkWhenEncEstabd} {@link InputStream}. + * Same as {@link #Template(String, String, Reader, Configuration, TemplateConfiguration, Charset)}, but allows + * specifying the {@code streamToUnmarkWhenEncEstabd}. * * @param streamToUnmarkWhenEncEstabd * If not {@code null}, when during the parsing we reach a point where we know that no {@link @@ -240,29 +247,61 @@ public class Template extends MutableProcessingConfiguration<Template> implement * that you can retry if a {@link WrongTemplateCharsetException} is thrown without extra I/O. As keeping that * mark consumes some resources, so you may want to release it as soon as possible. */ - public Template( - String name, String sourceName, Reader reader, - Configuration cfg, ParserConfiguration customParserConfiguration, - Charset sourceEncoding, InputStream streamToUnmarkWhenEncEstabd) throws IOException, ParseException { - this(name, sourceName, cfg, customParserConfiguration); + public Template( + String name, String sourceName, Reader reader, + Configuration cfg, TemplateConfiguration templateConfiguration, + Charset actualSourceEncoding, InputStream streamToUnmarkWhenEncEstabd) throws IOException, ParseException { + this(name, sourceName, reader, + cfg, templateConfiguration, + null, null, + actualSourceEncoding, streamToUnmarkWhenEncEstabd); + } - setActualSourceEncoding(sourceEncoding); + /** + * Same as {@link #Template(String, String, Reader, Configuration, TemplateConfiguration, Charset, InputStream)}, + * but allows specifying the output format and the auto escaping policy, with similar effect as if they were + * specified in the template content (like in the #ftl header). + * <p> + * <p>This method is currently only used internally, as it's not generalized enough and so it carries too much + * backward compatibility risk. Also, the same functionality can be achieved by constructing an appropriate + * {@link TemplateConfiguration}, only that's somewhat slower. + * + * @param contextOutputFormat + * The output format of the enclosing lexical context, used when a template snippet is parsed on runtime. If + * not {@code null}, this will override the value coming from the {@link TemplateConfiguration} or the + * {@link Configuration}. + * @param contextAutoEscapingPolicy + * Similar to {@code contextOutputFormat}; usually this and the that is set together. + */ + Template( + String name, String sourceName, Reader reader, + Configuration configuration, TemplateConfiguration templateConfiguration, + OutputFormat contextOutputFormat, Integer contextAutoEscapingPolicy, + Charset actualSourceEncoding, InputStream streamToUnmarkWhenEncEstabd) throws IOException, ParseException { + _NullArgumentException.check("configuration", configuration); + this.cfg = configuration; + this.tCfg = templateConfiguration; + this.parserConfiguration = tCfg != null ? new TemplateParserConfigurationWithFallback(cfg, tCfg) : cfg; + this.name = name; + this.sourceName = sourceName; + + setActualSourceEncoding(actualSourceEncoding); LineTableBuilder ltbReader; try { - ParserConfiguration actualParserConfiguration = getParserConfiguration(); - // Ensure that the parameter Reader is only read in bigger chunks, as we don't know if the it's buffered. // In particular, inside the FreeMarker code, we assume that the stream stages need not be buffered. if (!(reader instanceof BufferedReader) && !(reader instanceof StringReader)) { reader = new BufferedReader(reader, READER_BUFFER_SIZE); } - ltbReader = new LineTableBuilder(reader, actualParserConfiguration); + ltbReader = new LineTableBuilder(reader, parserConfiguration); reader = ltbReader; try { FMParser parser = new FMParser( - this, reader, actualParserConfiguration, streamToUnmarkWhenEncEstabd); + this, reader, + parserConfiguration, contextOutputFormat, contextAutoEscapingPolicy, + streamToUnmarkWhenEncEstabd); try { rootElement = parser.Root(); } catch (IndexOutOfBoundsException exc) { @@ -550,20 +589,21 @@ public class Template extends MutableProcessingConfiguration<Template> implement * Returns the Configuration object associated with this template. */ public Configuration getConfiguration() { - return (Configuration) getParent(); + return cfg; } - + /** - * Returns the {@link ParserConfiguration} that was used for parsing this template. This is most often the same - * object as {@link #getConfiguration()}, but sometimes it's a {@link TemplateConfiguration}, or something else. - * It's never {@code null}. - * - * @since 2.3.24 + * The {@link TemplateConfiguration} associated to this template, or {@code null} if there was none. */ + public TemplateConfiguration getTemplateConfiguration() { + return tCfg; + } + public ParserConfiguration getParserConfiguration() { return parserConfiguration; } + /** * @param actualSourceEncoding * The sourceEncoding that was used to read this template, or {@code null} if the source of the template @@ -633,40 +673,39 @@ public class Template extends MutableProcessingConfiguration<Template> implement * Returns the output format (see {@link Configuration#setOutputFormat(OutputFormat)}) used for this template. * The output format of a template can come from various places, in order of increasing priority: * {@link Configuration#getOutputFormat()}, {@link ParserConfiguration#getOutputFormat()} (which is usually - * provided by {@link Configuration#getTemplateConfigurations()}) and the {@code #ftl} header's {@code output_format} - * option in the template. + * provided by {@link Configuration#getTemplateConfigurations()}) and the {@code #ftl} header's + * {@code output_format} option in the template. * * @since 2.3.24 */ public OutputFormat getOutputFormat() { return outputFormat; } - + /** - * Meant to be called by the parser only. + * Should be called by the parser, for example to apply the output format specified in the #ftl header. */ void setOutputFormat(OutputFormat outputFormat) { this.outputFormat = outputFormat; } /** - * Returns if the template actually uses auto-escaping (see {@link Configuration#setAutoEscapingPolicy(int)}). This value - * is decided by the parser based on the actual {@link OutputFormat}, and the auto-escaping enums, in order of - * increasing priority: {@link Configuration#getAutoEscapingPolicy()}, {@link ParserConfiguration#getAutoEscapingPolicy()} - * (which is usually provided by {@link Configuration#getTemplateConfigurations()}), and finally on the {@code #ftl} - * header's {@code auto_esc} option in the template. - * - * @since 2.3.24 + * Returns if the auto-escaping policy (see {@link Configuration#setAutoEscapingPolicy(int)}) that this template + * uses. This is decided from these, in increasing priority: + * {@link Configuration#getAutoEscapingPolicy()}, {@link ParserConfiguration#getAutoEscapingPolicy()}, + * {@code #ftl} header's {@code auto_esc} option in the template. */ - public boolean getAutoEscaping() { - return autoEscaping; + public int getAutoEscapingPolicy() { + return autoEscapingPolicy != null ? autoEscapingPolicy + : tCfg != null && tCfg.isAutoEscapingPolicySet() ? tCfg.getAutoEscapingPolicy() + : cfg.getAutoEscapingPolicy(); } /** - * Meant to be called by the parser only. + * Should be called by the parser, for example to apply the auto escaping policy specified in the #ftl header. */ - void setAutoEscaping(boolean autoEscaping) { - this.autoEscaping = autoEscaping; + void setAutoEscapingPolicy(int autoEscapingPolicy) { + this.autoEscapingPolicy = autoEscapingPolicy; } /** @@ -680,7 +719,7 @@ public class Template extends MutableProcessingConfiguration<Template> implement * Dump the raw template in canonical form. */ public void dump(Writer out) throws IOException { - out.write(rootElement.getCanonicalForm()); + out.write(rootElement != null ? rootElement.getCanonicalForm() : "Unfinished template"); } void addMacro(ASTDirMacro macro) { @@ -730,143 +769,346 @@ public class Template extends MutableProcessingConfiguration<Template> implement } @Override - protected Locale getInheritedLocale() { - return getParent().getLocale(); + public Locale getLocale() { + // TODO [FM3] Temporary hack; See comment above the locale field + if (lookupLocale != null) { + return lookupLocale; + } + + return tCfg != null && tCfg.isLocaleSet() ? tCfg.getLocale() : cfg.getLocale(); + } + + // TODO [FM3] Temporary hack; See comment above the locale field + public void setLookupLocale(Locale lookupLocale) { + this.lookupLocale = lookupLocale; + } + + @Override + public boolean isLocaleSet() { + return tCfg != null && tCfg.isLocaleSet(); + } + + @Override + public TimeZone getTimeZone() { + return tCfg != null && tCfg.isTimeZoneSet() ? tCfg.getTimeZone() : cfg.getTimeZone(); + } + + @Override + public boolean isTimeZoneSet() { + return tCfg != null && tCfg.isTimeZoneSet(); + } + + @Override + public TimeZone getSQLDateAndTimeTimeZone() { + return tCfg != null && tCfg.isSQLDateAndTimeTimeZoneSet() ? tCfg.getSQLDateAndTimeTimeZone() : cfg.getSQLDateAndTimeTimeZone(); + } + + @Override + public boolean isSQLDateAndTimeTimeZoneSet() { + return tCfg != null && tCfg.isSQLDateAndTimeTimeZoneSet(); + } + + @Override + public String getNumberFormat() { + return tCfg != null && tCfg.isNumberFormatSet() ? tCfg.getNumberFormat() : cfg.getNumberFormat(); } @Override - protected TimeZone getInheritedTimeZone() { - return getParent().getTimeZone(); + public boolean isNumberFormatSet() { + return tCfg != null && tCfg.isNumberFormatSet(); } @Override - protected TimeZone getInheritedSQLDateAndTimeTimeZone() { - return getParent().getSQLDateAndTimeTimeZone(); + public Map<String, TemplateNumberFormatFactory> getCustomNumberFormats() { + return tCfg != null && tCfg.isCustomNumberFormatsSet() ? tCfg.getCustomNumberFormats() + : cfg.getCustomNumberFormats(); } @Override - protected String getInheritedNumberFormat() { - return getParent().getNumberFormat(); + public TemplateNumberFormatFactory getCustomNumberFormat(String name) { + if (tCfg != null && tCfg.isCustomNumberFormatsSet()) { + TemplateNumberFormatFactory value = tCfg.getCustomNumberFormats().get(name); + if (value != null) { + return value; + } + } + return cfg.getCustomNumberFormat(name); + } + + @Override + public boolean isCustomNumberFormatsSet() { + return tCfg != null && tCfg.isCustomNumberFormatsSet(); + } + + @Override + public boolean hasCustomFormats() { + if (tCfg != null && tCfg.hasCustomFormats()) { + return true; + } + return cfg.hasCustomFormats(); + } + + @Override + public String getBooleanFormat() { + return tCfg != null && tCfg.isBooleanFormatSet() ? tCfg.getBooleanFormat() : cfg.getBooleanFormat(); + } + + @Override + public boolean isBooleanFormatSet() { + return tCfg != null && tCfg.isBooleanFormatSet(); + } + + @Override + public String getTimeFormat() { + return tCfg != null && tCfg.isTimeFormatSet() ? tCfg.getTimeFormat() : cfg.getTimeFormat(); + } + + @Override + public boolean isTimeFormatSet() { + return tCfg != null && tCfg.isTimeFormatSet(); + } + + @Override + public String getDateFormat() { + return tCfg != null && tCfg.isDateFormatSet() ? tCfg.getDateFormat() : cfg.getDateFormat(); + } + + @Override + public boolean isDateFormatSet() { + return tCfg != null && tCfg.isDateFormatSet(); + } + + @Override + public String getDateTimeFormat() { + return tCfg != null && tCfg.isDateTimeFormatSet() ? tCfg.getDateTimeFormat() : cfg.getDateTimeFormat(); + } + + @Override + public boolean isDateTimeFormatSet() { + return tCfg != null && tCfg.isDateTimeFormatSet(); + } + + @Override + public Map<String, TemplateDateFormatFactory> getCustomDateFormats() { + return tCfg != null && tCfg.isCustomDateFormatsSet() ? tCfg.getCustomDateFormats() : cfg.getCustomDateFormats(); + } + + @Override + public TemplateDateFormatFactory getCustomDateFormat(String name) { + if (tCfg != null && tCfg.isCustomDateFormatsSet()) { + TemplateDateFormatFactory value = tCfg.getCustomDateFormats().get(name); + if (value != null) { + return value; + } + } + return cfg.getCustomDateFormat(name); } @Override - protected Map<String, TemplateNumberFormatFactory> getInheritedCustomNumberFormats() { - return getParent().getCustomNumberFormats(); + public boolean isCustomDateFormatsSet() { + return tCfg != null && tCfg.isCustomDateFormatsSet(); } @Override - protected TemplateNumberFormatFactory getInheritedCustomNumberFormat(String name) { - return getParent().getCustomNumberFormat(name); + public TemplateExceptionHandler getTemplateExceptionHandler() { + return tCfg != null && tCfg.isTemplateExceptionHandlerSet() ? tCfg.getTemplateExceptionHandler() : cfg.getTemplateExceptionHandler(); } @Override - protected boolean getInheritedHasCustomFormats() { - return getParent().hasCustomFormats(); + public boolean isTemplateExceptionHandlerSet() { + return tCfg != null && tCfg.isTemplateExceptionHandlerSet(); } @Override - protected String getInheritedBooleanFormat() { - return getParent().getBooleanFormat(); + public ArithmeticEngine getArithmeticEngine() { + return tCfg != null && tCfg.isArithmeticEngineSet() ? tCfg.getArithmeticEngine() : cfg.getArithmeticEngine(); } @Override - protected String getInheritedTimeFormat() { - return getParent().getTimeFormat(); + public boolean isArithmeticEngineSet() { + return tCfg != null && tCfg.isArithmeticEngineSet(); } @Override - protected String getInheritedDateFormat() { - return getParent().getDateFormat(); + public ObjectWrapper getObjectWrapper() { + return tCfg != null && tCfg.isObjectWrapperSet() ? tCfg.getObjectWrapper() : cfg.getObjectWrapper(); } @Override - protected String getInheritedDateTimeFormat() { - return getParent().getDateTimeFormat(); + public boolean isObjectWrapperSet() { + return tCfg != null && tCfg.isObjectWrapperSet(); } @Override - protected Map<String, TemplateDateFormatFactory> getInheritedCustomDateFormats() { - return getParent().getCustomDateFormats(); + public Charset getOutputEncoding() { + return tCfg != null && tCfg.isOutputEncodingSet() ? tCfg.getOutputEncoding() : cfg.getOutputEncoding(); } @Override - protected TemplateDateFormatFactory getInheritedCustomDateFormat(String name) { - return getParent().getCustomDateFormat(name); + public boolean isOutputEncodingSet() { + return tCfg != null && tCfg.isOutputEncodingSet(); } @Override - protected TemplateExceptionHandler getInheritedTemplateExceptionHandler() { - return getParent().getTemplateExceptionHandler(); + public Charset getURLEscapingCharset() { + return tCfg != null && tCfg.isURLEscapingCharsetSet() ? tCfg.getURLEscapingCharset() : cfg.getURLEscapingCharset(); } @Override - protected ArithmeticEngine getInheritedArithmeticEngine() { - return getParent().getArithmeticEngine(); + public boolean isURLEscapingCharsetSet() { + return tCfg != null && tCfg.isURLEscapingCharsetSet(); } @Override - protected ObjectWrapper getInheritedObjectWrapper() { - return getParent().getObjectWrapper(); + public TemplateClassResolver getNewBuiltinClassResolver() { + return tCfg != null && tCfg.isNewBuiltinClassResolverSet() ? tCfg.getNewBuiltinClassResolver() : cfg.getNewBuiltinClassResolver(); } @Override - protected Charset getInheritedOutputEncoding() { - return getParent().getOutputEncoding(); + public boolean isNewBuiltinClassResolverSet() { + return tCfg != null && tCfg.isNewBuiltinClassResolverSet(); } @Override - protected Charset getInheritedURLEscapingCharset() { - return getParent().getURLEscapingCharset(); + public boolean getAPIBuiltinEnabled() { + return tCfg != null && tCfg.isAPIBuiltinEnabledSet() ? tCfg.getAPIBuiltinEnabled() : cfg.getAPIBuiltinEnabled(); } @Override - protected TemplateClassResolver getInheritedNewBuiltinClassResolver() { - return getParent().getNewBuiltinClassResolver(); + public boolean isAPIBuiltinEnabledSet() { + return tCfg != null && tCfg.isAPIBuiltinEnabledSet(); } @Override - protected boolean getInheritedAutoFlush() { - return getParent().getAutoFlush(); + public boolean getAutoFlush() { + return tCfg != null && tCfg.isAutoFlushSet() ? tCfg.getAutoFlush() : cfg.getAutoFlush(); } @Override - protected boolean getInheritedShowErrorTips() { - return getParent().getShowErrorTips(); + public boolean isAutoFlushSet() { + return tCfg != null && tCfg.isAutoFlushSet(); } @Override - protected boolean getInheritedAPIBuiltinEnabled() { - return getParent().getAPIBuiltinEnabled(); + public boolean getShowErrorTips() { + return tCfg != null && tCfg.isShowErrorTipsSet() ? tCfg.getShowErrorTips() : cfg.getShowErrorTips(); } @Override - protected boolean getInheritedLogTemplateExceptions() { - return getParent().getLogTemplateExceptions(); + public boolean isShowErrorTipsSet() { + return tCfg != null && tCfg.isShowErrorTipsSet(); } @Override - protected boolean getInheritedLazyImports() { - return getParent().getLazyImports(); + public boolean getLogTemplateExceptions() { + return tCfg != null && tCfg.isLogTemplateExceptionsSet() ? tCfg.getLogTemplateExceptions() : cfg.getLogTemplateExceptions(); } @Override - protected Boolean getInheritedLazyAutoImports() { - return getParent().getLazyAutoImports(); + public boolean isLogTemplateExceptionsSet() { + return tCfg != null && tCfg.isLogTemplateExceptionsSet(); } @Override - protected Map<String, String> getInheritedAutoImports() { - return getParent().getAutoImports(); + public boolean getLazyImports() { + return tCfg != null && tCfg.isLazyImportsSet() ? tCfg.getLazyImports() : cfg.getLazyImports(); } @Override - protected List<String> getInheritedAutoIncludes() { - return getParent().getAutoIncludes(); + public boolean isLazyImportsSet() { + return tCfg != null && tCfg.isLazyImportsSet(); } @Override - protected Object getInheritedCustomAttribute(Object name) { - return getParent().getCustomAttribute(name); + public Boolean getLazyAutoImports() { + return tCfg != null && tCfg.isLazyAutoImportsSet() ? tCfg.getLazyAutoImports() : cfg.getLazyAutoImports(); + } + + @Override + public boolean isLazyAutoImportsSet() { + return tCfg != null && tCfg.isLazyAutoImportsSet(); + } + + @Override + public Map<String, String> getAutoImports() { + return tCfg != null && tCfg.isAutoImportsSet() ? tCfg.getAutoImports() : cfg.getAutoImports(); + } + + @Override + public boolean isAutoImportsSet() { + return tCfg != null && tCfg.isAutoImportsSet(); + } + + @Override + public List<String> getAutoIncludes() { + return tCfg != null && tCfg.isAutoIncludesSet() ? tCfg.getAutoIncludes() : cfg.getAutoIncludes(); + } + + @Override + public boolean isAutoIncludesSet() { + return tCfg != null && tCfg.isAutoIncludesSet(); + } + + /** + * This exists to provide the functionality required by {@link ProcessingConfiguration}, but try not call it + * too frequently as it has some overhead compared to an usual getter. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Map<Object, Object> getCustomAttributes() { + if (mergedCustomAttributes != null) { + return Collections.unmodifiableMap(mergedCustomAttributes); + } else if (customAttributes != null) { + return (Map) Collections.unmodifiableMap(customAttributes); + } else if (tCfg != null && tCfg.isCustomAttributesSet()) { + return tCfg.getCustomAttributes(); + } else { + return cfg.getCustomAttributes(); + } + } + + @Override + public boolean isCustomAttributesSet() { + return customAttributes != null || tCfg != null && tCfg.isCustomAttributesSet(); + } + + @Override + public Object getCustomAttribute(Object name) { + // Extra step for custom attributes specified in the #ftl header: + if (mergedCustomAttributes != null) { + Object value = mergedCustomAttributes.get(name); + if (value != null || mergedCustomAttributes.containsKey(name)) { + return value; + } + } else if (customAttributes != null) { + Object value = customAttributes.get(name); + if (value != null || customAttributes.containsKey(name)) { + return value; + } + } else if (tCfg != null && tCfg.isCustomAttributesSet()) { + Object value = tCfg.getCustomAttributes().get(name); + if (value != null || tCfg.getCustomAttributes().containsKey(name)) { + return value; + } + } + return cfg.getCustomAttribute(name); + } + + /** + * Should be called by the parser, for example to add the attributes specified in the #ftl header. + */ + void setCustomAttribute(String attName, Serializable attValue) { + if (customAttributes == null) { + customAttributes = new LinkedHashMap<>(); + } + customAttributes.put(attName, attValue); + + if (tCfg != null && tCfg.isCustomAttributesSet()) { + if (mergedCustomAttributes == null) { + mergedCustomAttributes = new LinkedHashMap<>(tCfg.getCustomAttributes()); + } + mergedCustomAttributes.put(attName, attValue); + } } /**
