Repository: incubator-freemarker Updated Branches: refs/heads/3 164f292ef -> bd1c4c40d
Added protected getImpliedRegisteredCustomOutputFormats() as well, and some cleanup around the customOutputFormats setting. Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/bd1c4c40 Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/bd1c4c40 Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/bd1c4c40 Branch: refs/heads/3 Commit: bd1c4c40d828a87cd23733e948ec2edcb96b0444 Parents: 164f292 Author: ddekany <[email protected]> Authored: Sat Jun 10 19:58:49 2017 +0200 Committer: ddekany <[email protected]> Committed: Sat Jun 10 19:58:49 2017 +0200 ---------------------------------------------------------------------- .../freemarker/core/ConfigurationTest.java | 33 ++++++- .../userpkg/NameClashingDummyOutputFormat.java | 65 +++++++++++++ .../apache/freemarker/core/Configuration.java | 97 +++++++++++++------- .../freemarker/core/TopLevelConfiguration.java | 33 +++++++ 4 files changed, 191 insertions(+), 37 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/bd1c4c40/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java index 7cf205f..ae9bd74 100644 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java @@ -76,6 +76,8 @@ import org.apache.freemarker.core.userpkg.DummyOutputFormat; import org.apache.freemarker.core.userpkg.EpochMillisDivTemplateDateFormatFactory; import org.apache.freemarker.core.userpkg.EpochMillisTemplateDateFormatFactory; import org.apache.freemarker.core.userpkg.HexTemplateNumberFormatFactory; +import org.apache.freemarker.core.userpkg.NameClashingDummyOutputFormat; +import org.apache.freemarker.core.userpkg.SeldomEscapedOutputFormat; import org.apache.freemarker.core.util._CollectionUtil; import org.apache.freemarker.core.util._DateUtil; import org.apache.freemarker.core.util._NullArgumentException; @@ -1457,7 +1459,8 @@ public class ConfigurationTest extends TestCase { } @Test - public void testImpliedSettingValues() throws IOException, TemplateConfigurationFactoryException { + public void testImpliedSettingValues() + throws IOException, TemplateConfigurationFactoryException, UnregisteredOutputFormatException { Configuration cfg = new ImpliedSettingValuesTestBuilder().build(); assertEquals("Y,N", cfg.getTemplateConfigurations().get("t.yn", null).getBooleanFormat()); @@ -1466,10 +1469,16 @@ public class ConfigurationTest extends TestCase { assertEquals(ImmutableMap.of("lib", "lib.ftl"), cfg.getAutoImports()); assertEquals(ImmutableList.of("inc.ftl"), cfg.getAutoIncludes()); assertEquals(ImmutableMap.of("v", 1), cfg.getSharedVariables()); + assertEquals(ImmutableList.of(CustomHTMLOutputFormat.INSTANCE, DummyOutputFormat.INSTANCE), + cfg.getRegisteredCustomOutputFormats()); + assertSame(CustomHTMLOutputFormat.INSTANCE, cfg.getOutputFormat("HTML")); + assertSame(DummyOutputFormat.INSTANCE, cfg.getOutputFormat("dummy")); + assertSame(XMLOutputFormat.INSTANCE, cfg.getOutputFormat("XML")); } @Test - public void testImpliedSettingValues2() throws IOException, TemplateConfigurationFactoryException { + public void testImpliedSettingValues2() + throws IOException, TemplateConfigurationFactoryException, UnregisteredOutputFormatException { Configuration cfg = new ImpliedSettingValuesTestBuilder() .templateConfigurations( new ConditionalTemplateConfigurationFactory( @@ -1482,6 +1491,8 @@ public class ConfigurationTest extends TestCase { .autoImports(ImmutableMap.of("lib2", "lib2.ftl")) .autoIncludes(ImmutableList.of("inc2.ftl")) .sharedVariables(ImmutableMap.of("v2", 2)) + .registeredCustomOutputFormats( + SeldomEscapedOutputFormat.INSTANCE, NameClashingDummyOutputFormat.INSTANCE) .build(); TemplateConfigurationFactory tcf = cfg.getTemplateConfigurations(); @@ -1499,6 +1510,17 @@ public class ConfigurationTest extends TestCase { assertEquals(ImmutableList.of("inc.ftl", "inc2.ftl"), cfg.getAutoIncludes()); assertEquals(ImmutableMap.of("v", 1, "v2", 2), cfg.getSharedVariables()); + + assertEquals( + ImmutableList.of( + CustomHTMLOutputFormat.INSTANCE, + SeldomEscapedOutputFormat.INSTANCE, + NameClashingDummyOutputFormat.INSTANCE), + cfg.getRegisteredCustomOutputFormats()); + assertSame(CustomHTMLOutputFormat.INSTANCE, cfg.getOutputFormat("HTML")); + assertSame(NameClashingDummyOutputFormat.INSTANCE, cfg.getOutputFormat("dummy")); + assertSame(SeldomEscapedOutputFormat.INSTANCE, cfg.getOutputFormat("seldomEscaped")); + assertSame(XMLOutputFormat.INSTANCE, cfg.getOutputFormat("XML")); } @SuppressWarnings("boxing") @@ -1568,9 +1590,14 @@ public class ConfigurationTest extends TestCase { } @Override - protected Map<String, Object> getImplicitSharedVariables() { + protected Map<String, Object> getImpliedSharedVariables() { return ImmutableMap.<String, Object>of("v", 1); } + + @Override + protected Collection<OutputFormat> getImpliedRegisteredCustomOutputFormats() { + return ImmutableList.<OutputFormat>of(CustomHTMLOutputFormat.INSTANCE, DummyOutputFormat.INSTANCE); + } } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/bd1c4c40/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NameClashingDummyOutputFormat.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NameClashingDummyOutputFormat.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NameClashingDummyOutputFormat.java new file mode 100644 index 0000000..dd26156 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NameClashingDummyOutputFormat.java @@ -0,0 +1,65 @@ +/* + * 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.userpkg; + +import java.io.IOException; +import java.io.Writer; + +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.outputformat.CommonMarkupOutputFormat; + +public class NameClashingDummyOutputFormat extends CommonMarkupOutputFormat<TemplateDummyOutputModel> { + + public static final NameClashingDummyOutputFormat INSTANCE = new NameClashingDummyOutputFormat(); + + private NameClashingDummyOutputFormat() { + // hide + } + + @Override + public String getName() { + return "dummy"; + } + + @Override + public String getMimeType() { + return "text/dummy"; + } + + @Override + public void output(String textToEsc, Writer out) throws IOException, TemplateModelException { + out.write(escapePlainText(textToEsc)); + } + + @Override + public String escapePlainText(String plainTextContent) { + return plainTextContent.replaceAll("(\\.|\\\\)", "\\\\$1"); + } + + @Override + public boolean isLegacyBuiltInBypassed(String builtInName) { + return false; + } + + @Override + protected TemplateDummyOutputModel newTemplateMarkupOutputModel(String plainTextContent, String markupContent) { + return new TemplateDummyOutputModel(plainTextContent, markupContent); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/bd1c4c40/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java index 93bd1ff..3b96857 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java @@ -26,9 +26,11 @@ import java.io.InputStream; import java.io.Serializable; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -302,12 +304,36 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc incompatibleImprovements = builder.getIncompatibleImprovements(); { - Collection<OutputFormat> registeredCustomOutputFormats = builder.getRegisteredCustomOutputFormats(); + final Collection<OutputFormat> regCustOutputFormats; + { + Collection<OutputFormat> directRegCustOutputFormats = builder.getRegisteredCustomOutputFormats(); + Collection<OutputFormat> impliedRegCustOutputFormats = + builder.getImpliedRegisteredCustomOutputFormats(); + if (impliedRegCustOutputFormats.isEmpty()) { + regCustOutputFormats = directRegCustOutputFormats; + } else if (directRegCustOutputFormats.isEmpty()) { + regCustOutputFormats = impliedRegCustOutputFormats; + } else { + List<OutputFormat> mergedOutputFormats = new ArrayList<>( + impliedRegCustOutputFormats.size() + directRegCustOutputFormats.size()); + HashSet<String> directNames = new HashSet<>(directRegCustOutputFormats.size() * 4 / 3 + 1, .75f); + for (OutputFormat directRegCustOutputFormat : directRegCustOutputFormats) { + directNames.add(directRegCustOutputFormat.getName()); + } + for (OutputFormat impliedRegCustOutputFormat : impliedRegCustOutputFormats) { + if (!directNames.contains(impliedRegCustOutputFormat.getName())) { + mergedOutputFormats.add(impliedRegCustOutputFormat); + } + } + mergedOutputFormats.addAll(directRegCustOutputFormats); + regCustOutputFormats = Collections.unmodifiableList(mergedOutputFormats); + } + } - _NullArgumentException.check(registeredCustomOutputFormats); + _NullArgumentException.check(regCustOutputFormats); Map<String, OutputFormat> registeredCustomOutputFormatsByName = new LinkedHashMap<>( - registeredCustomOutputFormats.size() * 4 / 3, 1f); - for (OutputFormat outputFormat : registeredCustomOutputFormats) { + regCustOutputFormats.size() * 4 / 3, 1f); + for (OutputFormat outputFormat : regCustOutputFormats) { String name = outputFormat.getName(); if (name.equals(UndefinedOutputFormat.INSTANCE.getName())) { throw new InvalidSettingValueException( @@ -367,14 +393,14 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc this.registeredCustomOutputFormatsByName = registeredCustomOutputFormatsByName; this.registeredCustomOutputFormats = Collections.unmodifiableList( - new ArrayList<> (registeredCustomOutputFormats)); + new ArrayList<> (regCustOutputFormats)); } ObjectWrapper objectWrapper = builder.getObjectWrapper(); { Map<String, Object> sharedVariables = _CollectionUtil.mergeImmutableMaps(builder - .getImplicitSharedVariables(), builder.getSharedVariables(), false); + .getImpliedSharedVariables(), builder.getSharedVariables(), false); HashMap<String, TemplateModel> wrappedSharedVariables = new HashMap<>( sharedVariables.size() * 4 / 3 + 1, 0.75f); @@ -803,32 +829,19 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc return (MarkupOutputFormat) of; } - /** - * The custom output formats that can be referred by their unique name ({@link OutputFormat#getName()}) from - * templates. Names are also used to look up the {@link OutputFormat} for standard file extensions; see them at - * {@link #getRecognizeStandardFileExtensions()}. Each must be different and has a unique name - * ({@link OutputFormat#getName()}) within this collection. - * - * <p> - * When there's a clash between a custom output format name and a standard output format name, the custom format - * will win, thus you can override the meaning of standard output format names. Except, it's not allowed to override - * {@link UndefinedOutputFormat} and {@link PlainTextOutputFormat}. - * - * <p> - * The default value is an empty collection. - * - * @throws IllegalArgumentException - * When multiple different {@link OutputFormat}-s have the same name in the parameter collection. When - * the same {@link OutputFormat} object occurs for multiple times in the collection. If an - * {@link OutputFormat} name is 0 long. If an {@link OutputFormat} name doesn't start with letter or - * digit. If an {@link OutputFormat} name contains {@code '+'} or <code>'{'</code> or <code>'}'</code>. - * If an {@link OutputFormat} name equals to {@link UndefinedOutputFormat#getName()} or - * {@link PlainTextOutputFormat#getName()}. - */ public Collection<OutputFormat> getRegisteredCustomOutputFormats() { return registeredCustomOutputFormats; } + /** + * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default + * value in the {@link Configuration}. + */ + @Override + public boolean isRegisteredCustomOutputFormatsSet() { + return true; + } + @Override public boolean getRecognizeStandardFileExtensions() { return recognizeStandardFileExtensions == null @@ -2477,9 +2490,6 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc : getDefaultRegisteredCustomOutputFormats(); } - /** - * Tells if this setting was explicitly set (if not, the default value of the setting will be used). - */ public boolean isRegisteredCustomOutputFormatsSet() { return registeredCustomOutputFormats != null; } @@ -2489,11 +2499,23 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc } /** + * The imports that will be added to the built {@link Configuration} before the ones coming from + * {@link #getRegisteredCustomOutputFormats()}. When overriding this method, always consider adding to the + * return value of the super method, rather than replacing it. + * + * @return Immutable {@link Collection}; not {@code null} + */ + protected Collection<OutputFormat> getImpliedRegisteredCustomOutputFormats() { + return Collections.emptyList(); + } + + /** * Setter pair of {@link Configuration#getRegisteredCustomOutputFormats()}. */ public void setRegisteredCustomOutputFormats(Collection<OutputFormat> registeredCustomOutputFormats) { _NullArgumentException.check("registeredCustomOutputFormats", registeredCustomOutputFormats); - this.registeredCustomOutputFormats = registeredCustomOutputFormats; + this.registeredCustomOutputFormats = Collections.unmodifiableCollection( + new ArrayList<>(registeredCustomOutputFormats)); } /** @@ -2505,6 +2527,13 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc } /** + * Varargs overload if {@link #registeredCustomOutputFormats(Collection)}. + */ + public SelfT registeredCustomOutputFormats(OutputFormat... registeredCustomOutputFormats) { + return registeredCustomOutputFormats(Arrays.asList(registeredCustomOutputFormats)); + } + + /** * Resets this setting to its initial state, as if it was never set. */ public void unsetRegisteredCustomOutputFormats() { @@ -2534,7 +2563,7 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc /** * The {@link Map} to use as shared variables if {@link #isSharedVariablesSet()} is {@code false}. * - * @see #getImplicitSharedVariables() + * @see #getImpliedSharedVariables() */ protected Map<String, Object> getDefaultSharedVariables() { return Collections.emptyMap(); @@ -2559,7 +2588,7 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc * * @return Immutable {@link Map}; not {@code null} */ - protected Map<String, Object> getImplicitSharedVariables() { + protected Map<String, Object> getImpliedSharedVariables() { return DEFAULT_SHARED_VARIABLES; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/bd1c4c40/freemarker-core/src/main/java/org/apache/freemarker/core/TopLevelConfiguration.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TopLevelConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TopLevelConfiguration.java index fed778e..66d3495 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/TopLevelConfiguration.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TopLevelConfiguration.java @@ -19,9 +19,13 @@ package org.apache.freemarker.core; +import java.util.Collection; import java.util.Map; import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.outputformat.OutputFormat; +import org.apache.freemarker.core.outputformat.impl.PlainTextOutputFormat; +import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat; import org.apache.freemarker.core.templateresolver.CacheStorage; import org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory; import org.apache.freemarker.core.templateresolver.TemplateLoader; @@ -237,4 +241,33 @@ public interface TopLevelConfiguration extends ParsingAndProcessingConfiguration */ boolean isSharedVariablesSet(); + /** + * The custom output formats that can be referred by their unique name ({@link OutputFormat#getName()}) from + * templates. Names are also used to look up the {@link OutputFormat} for standard file extensions; see them at + * {@link #getRecognizeStandardFileExtensions()}. Each must be different and has a unique name + * ({@link OutputFormat#getName()}) within this collection. + * + * <p> + * When there's a clash between a custom output format name and a standard output format name, the custom format + * will win, thus you can override the meaning of standard output format names. Except, it's not allowed to override + * {@link UndefinedOutputFormat} and {@link PlainTextOutputFormat}. + * + * <p> + * The default value is an empty collection. + * + * @throws IllegalArgumentException + * When multiple different {@link OutputFormat}-s have the same name in the parameter collection. When + * the same {@link OutputFormat} object occurs for multiple times in the collection. If an + * {@link OutputFormat} name is 0 long. If an {@link OutputFormat} name doesn't start with letter or + * digit. If an {@link OutputFormat} name contains {@code '+'} or <code>'{'</code> or <code>'}'</code>. + * If an {@link OutputFormat} name equals to {@link UndefinedOutputFormat#getName()} or + * {@link PlainTextOutputFormat#getName()}. + */ + Collection<OutputFormat> getRegisteredCustomOutputFormats(); + + /** + * Tells if this setting was explicitly set (if not, the default value of the setting will be used). + */ + boolean isRegisteredCustomOutputFormatsSet(); + }
