Repository: incubator-freemarker Updated Branches: refs/heads/3 c42014cc0 -> db64ebc97
Tested/fixed the setters of configuration settings that has type Map and List. Other minor adjustments. Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/db64ebc9 Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/db64ebc9 Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/db64ebc9 Branch: refs/heads/3 Commit: db64ebc976a82ec1b6594fa3045e902084a229cb Parents: c42014c Author: ddekany <[email protected]> Authored: Thu Jun 8 23:32:01 2017 +0200 Committer: ddekany <[email protected]> Committed: Thu Jun 8 23:32:51 2017 +0200 ---------------------------------------------------------------------- .../freemarker/core/ConfigurableTest.java | 176 -------------- .../freemarker/core/ConfigurationTest.java | 36 ++- .../MutableProcessingConfigurationTest.java | 242 +++++++++++++++++++ .../core/RestrictedObjectWrapperTest.java | 72 ------ .../core/RestrictedObjetWrapperTest.java | 112 --------- .../core/TemplateConfigurationTest.java | 12 + .../model/impl/DefaultObjectWrapperTest.java | 12 + .../model/impl/RestrictedObjectWrapperTest.java | 155 ++++++++++++ .../apache/freemarker/core/Configuration.java | 34 +-- .../core/MutableProcessingConfiguration.java | 18 +- .../freemarker/core/TemplateConfiguration.java | 22 +- .../core/util/ProductWrappingBuilder.java | 43 ---- .../freemarker/core/util/_CollectionUtil.java | 12 + 13 files changed, 501 insertions(+), 445 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/db64ebc9/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurableTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurableTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurableTest.java deleted file mode 100644 index 5301f14..0000000 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurableTest.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core; - -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import java.io.IOException; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.apache.freemarker.core.util._StringUtil; -import org.junit.Test; - -public class ConfigurableTest { - - @Test - public void testGetSettingNamesAreSorted() throws Exception { - MutableProcessingConfiguration cfgable = createConfigurable(); - for (boolean camelCase : new boolean[] { false, true }) { - Collection<String> names = cfgable.getSettingNames(camelCase); - String prevName = null; - for (String name : names) { - if (prevName != null) { - assertThat(name, greaterThan(prevName)); - } - prevName = name; - } - } - } - - @Test - public void testStaticFieldKeysCoverAllGetSettingNames() throws Exception { - MutableProcessingConfiguration cfgable = createConfigurable(); - Collection<String> names = cfgable.getSettingNames(false); - for (String name : names) { - assertTrue("No field was found for " + name, keyFieldExists(name)); - } - } - - @Test - public void testGetSettingNamesCoversAllStaticKeyFields() throws Exception { - MutableProcessingConfiguration cfgable = createConfigurable(); - Collection<String> names = cfgable.getSettingNames(false); - - for (Field f : MutableProcessingConfiguration.class.getFields()) { - if (f.getName().endsWith("_KEY")) { - final Object name = f.get(null); - assertTrue("Missing setting name: " + name, names.contains(name)); - } - } - } - - @Test - public void testKeyStaticFieldsHasAllVariationsAndCorrectFormat() throws IllegalArgumentException, IllegalAccessException { - ConfigurableTest.testKeyStaticFieldsHasAllVariationsAndCorrectFormat(MutableProcessingConfiguration.class); - } - - @Test - public void testGetSettingNamesNameConventionsContainTheSame() throws Exception { - MutableProcessingConfiguration cfgable = createConfigurable(); - ConfigurableTest.testGetSettingNamesNameConventionsContainTheSame( - new ArrayList<>(cfgable.getSettingNames(false)), - new ArrayList<>(cfgable.getSettingNames(true))); - } - - public static void testKeyStaticFieldsHasAllVariationsAndCorrectFormat( - Class<? extends MutableProcessingConfiguration> confClass) throws IllegalArgumentException, IllegalAccessException { - // For all _KEY fields there must be a _KEY_CAMEL_CASE and a _KEY_SNAKE_CASE field. - // Their content must not contradict the expected naming convention. - // They _KEY filed value must be deducable from the field name - // The _KEY value must be the same as _KEY_SNAKE_CASE field. - // The _KEY_CAMEL_CASE converted to snake case must give the value of the _KEY_SNAKE_CASE. - for (Field field : confClass.getFields()) { - String fieldName = field.getName(); - if (fieldName.endsWith("_KEY")) { - String keyFieldValue = (String) field.get(null); - assertNotEquals(NamingConvention.CAMEL_CASE, - _StringUtil.getIdentifierNamingConvention(keyFieldValue)); - assertEquals(fieldName.substring(0, fieldName.length() - 4).toLowerCase(), keyFieldValue); - - try { - String keySCFieldValue = (String) confClass.getField(fieldName + "_SNAKE_CASE").get(null); - assertEquals(keyFieldValue, keySCFieldValue); - } catch (NoSuchFieldException e) { - fail("Missing ..._SNAKE_CASE field for " + fieldName); - } - - try { - String keyCCFieldValue = (String) confClass.getField(fieldName + "_CAMEL_CASE").get(null); - assertNotEquals(NamingConvention.LEGACY, - _StringUtil.getIdentifierNamingConvention(keyCCFieldValue)); - assertEquals(keyFieldValue, _StringUtil.camelCaseToUnderscored(keyCCFieldValue)); - } catch (NoSuchFieldException e) { - fail("Missing ..._CAMEL_CASE field for " + fieldName); - } - } - } - - // For each _KEY_SNAKE_CASE field there must be a _KEY field. - for (Field field : confClass.getFields()) { - String fieldName = field.getName(); - if (fieldName.endsWith("_KEY_SNAKE_CASE")) { - try { - confClass.getField(fieldName.substring(0, fieldName.length() - 11)).get(null); - } catch (NoSuchFieldException e) { - fail("Missing ..._KEY field for " + fieldName); - } - } - } - - // For each _KEY_CAMEL_CASE field there must be a _KEY field. - for (Field field : confClass.getFields()) { - String fieldName = field.getName(); - if (fieldName.endsWith("_KEY_CAMEL_CASE")) { - try { - confClass.getField(fieldName.substring(0, fieldName.length() - 11)).get(null); - } catch (NoSuchFieldException e) { - fail("Missing ..._KEY field for " + fieldName); - } - } - } - } - - public static void testGetSettingNamesNameConventionsContainTheSame(List<String> namesSCList, List<String> namesCCList) { - Set<String> namesSC = new HashSet<>(namesSCList); - assertEquals(namesSCList.size(), namesSC.size()); - - Set<String> namesCC = new HashSet<>(namesCCList); - assertEquals(namesCCList.size(), namesCC.size()); - - assertEquals(namesSC.size(), namesCC.size()); - - for (String nameCC : namesCC) { - final String nameSC = _StringUtil.camelCaseToUnderscored(nameCC); - if (!namesSC.contains(nameSC)) { - fail("\"" + nameCC + "\" misses corresponding snake case name, \"" + nameSC + "\"."); - } - } - } - - private MutableProcessingConfiguration createConfigurable() throws IOException { - return new TemplateConfiguration.Builder(); - } - - private boolean keyFieldExists(String name) throws Exception { - try { - MutableProcessingConfiguration.class.getField(name.toUpperCase() + "_KEY"); - } catch (NoSuchFieldException e) { - return false; - } - return true; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/db64ebc9/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 064e34a..fa1c4d2 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 @@ -35,6 +35,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.TimeZone; @@ -73,6 +74,7 @@ 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.util._CollectionUtil; import org.apache.freemarker.core.util._DateUtil; import org.apache.freemarker.core.util._NullArgumentException; import org.apache.freemarker.core.util._NullWriter; @@ -1279,7 +1281,7 @@ public class ConfigurationTest extends TestCase { @SuppressFBWarnings("DLS_DEAD_LOCAL_STORE") public void testGetSettingNamesNameConventionsContainTheSame() throws Exception { Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); - ConfigurableTest.testGetSettingNamesNameConventionsContainTheSame( + MutableProcessingConfigurationTest.testGetSettingNamesNameConventionsContainTheSame( new ArrayList<>(cfgB.getSettingNames(false)), new ArrayList<>(cfgB.getSettingNames(true))); } @@ -1314,7 +1316,7 @@ public class ConfigurationTest extends TestCase { @Test public void testKeyStaticFieldsHasAllVariationsAndCorrectFormat() throws IllegalArgumentException, IllegalAccessException { - ConfigurableTest.testKeyStaticFieldsHasAllVariationsAndCorrectFormat(Configuration.ExtendableBuilder.class); + MutableProcessingConfigurationTest.testKeyStaticFieldsHasAllVariationsAndCorrectFormat(Configuration.ExtendableBuilder.class); } @Test @@ -1423,7 +1425,35 @@ public class ConfigurationTest extends TestCase { assertThat(e.getMessage(), allOf(containsString("removed"), containsString("3.0.0"))); } } - + + @Test + public void testCanBeBuiltOnlyOnce() { + Configuration.Builder builder = new Configuration.Builder(Configuration.VERSION_3_0_0); + builder.build(); + try { + builder.build(); + fail(); + } catch (IllegalStateException e) { + // Expected + } + } + + @Test + public void testCollectionSettingMutability() throws IOException { + Configuration.Builder cb = new Configuration.Builder(Configuration.VERSION_3_0_0); + + assertTrue(_CollectionUtil.isMapKnownToBeUnmodifiable(cb.getSharedVariables())); + Map<String, Object> mutableValue = new HashMap<>(); + mutableValue.put("x", "v1"); + cb.setSharedVariables(mutableValue); + Map<String, Object> immutableValue = cb.getSharedVariables(); + assertNotSame(mutableValue, immutableValue); // Must be a copy + assertTrue(_CollectionUtil.isMapKnownToBeUnmodifiable(immutableValue)); + assertEquals(mutableValue, immutableValue); + mutableValue.put("y", "v2"); + assertNotEquals(mutableValue, immutableValue); // No aliasing + } + @SuppressWarnings("boxing") private void assertStartsWith(List<String> list, List<String> headList) { int index = 0; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/db64ebc9/freemarker-core-test/src/test/java/org/apache/freemarker/core/MutableProcessingConfigurationTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/MutableProcessingConfigurationTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/MutableProcessingConfigurationTest.java new file mode 100644 index 0000000..f780e9d --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/MutableProcessingConfigurationTest.java @@ -0,0 +1,242 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.freemarker.core.userpkg.BaseNTemplateNumberFormatFactory; +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.util._CollectionUtil; +import org.apache.freemarker.core.util._StringUtil; +import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory; +import org.junit.Test; + +public class MutableProcessingConfigurationTest { + + @Test + public void testGetSettingNamesAreSorted() throws Exception { + MutableProcessingConfiguration mpc = createMutableProcessingConfiguration(); + for (boolean camelCase : new boolean[] { false, true }) { + Collection<String> names = mpc.getSettingNames(camelCase); + String prevName = null; + for (String name : names) { + if (prevName != null) { + assertThat(name, greaterThan(prevName)); + } + prevName = name; + } + } + } + + @Test + public void testStaticFieldKeysCoverAllGetSettingNames() throws Exception { + MutableProcessingConfiguration mpc = createMutableProcessingConfiguration(); + Collection<String> names = mpc.getSettingNames(false); + for (String name : names) { + assertTrue("No field was found for " + name, keyFieldExists(name)); + } + } + + @Test + public void testGetSettingNamesCoversAllStaticKeyFields() throws Exception { + MutableProcessingConfiguration mpc = createMutableProcessingConfiguration(); + Collection<String> names = mpc.getSettingNames(false); + + for (Field f : MutableProcessingConfiguration.class.getFields()) { + if (f.getName().endsWith("_KEY")) { + final Object name = f.get(null); + assertTrue("Missing setting name: " + name, names.contains(name)); + } + } + } + + @Test + public void testKeyStaticFieldsHasAllVariationsAndCorrectFormat() throws IllegalArgumentException, IllegalAccessException { + MutableProcessingConfigurationTest.testKeyStaticFieldsHasAllVariationsAndCorrectFormat(MutableProcessingConfiguration.class); + } + + @Test + public void testGetSettingNamesNameConventionsContainTheSame() throws Exception { + MutableProcessingConfiguration mpc = createMutableProcessingConfiguration(); + MutableProcessingConfigurationTest.testGetSettingNamesNameConventionsContainTheSame( + new ArrayList<>(mpc.getSettingNames(false)), + new ArrayList<>(mpc.getSettingNames(true))); + } + + public static void testKeyStaticFieldsHasAllVariationsAndCorrectFormat( + Class<? extends MutableProcessingConfiguration> confClass) throws IllegalArgumentException, IllegalAccessException { + // For all _KEY fields there must be a _KEY_CAMEL_CASE and a _KEY_SNAKE_CASE field. + // Their content must not contradict the expected naming convention. + // They _KEY filed value must be deducable from the field name + // The _KEY value must be the same as _KEY_SNAKE_CASE field. + // The _KEY_CAMEL_CASE converted to snake case must give the value of the _KEY_SNAKE_CASE. + for (Field field : confClass.getFields()) { + String fieldName = field.getName(); + if (fieldName.endsWith("_KEY")) { + String keyFieldValue = (String) field.get(null); + assertNotEquals(NamingConvention.CAMEL_CASE, + _StringUtil.getIdentifierNamingConvention(keyFieldValue)); + assertEquals(fieldName.substring(0, fieldName.length() - 4).toLowerCase(), keyFieldValue); + + try { + String keySCFieldValue = (String) confClass.getField(fieldName + "_SNAKE_CASE").get(null); + assertEquals(keyFieldValue, keySCFieldValue); + } catch (NoSuchFieldException e) { + fail("Missing ..._SNAKE_CASE field for " + fieldName); + } + + try { + String keyCCFieldValue = (String) confClass.getField(fieldName + "_CAMEL_CASE").get(null); + assertNotEquals(NamingConvention.LEGACY, + _StringUtil.getIdentifierNamingConvention(keyCCFieldValue)); + assertEquals(keyFieldValue, _StringUtil.camelCaseToUnderscored(keyCCFieldValue)); + } catch (NoSuchFieldException e) { + fail("Missing ..._CAMEL_CASE field for " + fieldName); + } + } + } + + // For each _KEY_SNAKE_CASE field there must be a _KEY field. + for (Field field : confClass.getFields()) { + String fieldName = field.getName(); + if (fieldName.endsWith("_KEY_SNAKE_CASE")) { + try { + confClass.getField(fieldName.substring(0, fieldName.length() - 11)).get(null); + } catch (NoSuchFieldException e) { + fail("Missing ..._KEY field for " + fieldName); + } + } + } + + // For each _KEY_CAMEL_CASE field there must be a _KEY field. + for (Field field : confClass.getFields()) { + String fieldName = field.getName(); + if (fieldName.endsWith("_KEY_CAMEL_CASE")) { + try { + confClass.getField(fieldName.substring(0, fieldName.length() - 11)).get(null); + } catch (NoSuchFieldException e) { + fail("Missing ..._KEY field for " + fieldName); + } + } + } + } + + public static void testGetSettingNamesNameConventionsContainTheSame(List<String> namesSCList, List<String> namesCCList) { + Set<String> namesSC = new HashSet<>(namesSCList); + assertEquals(namesSCList.size(), namesSC.size()); + + Set<String> namesCC = new HashSet<>(namesCCList); + assertEquals(namesCCList.size(), namesCC.size()); + + assertEquals(namesSC.size(), namesCC.size()); + + for (String nameCC : namesCC) { + final String nameSC = _StringUtil.camelCaseToUnderscored(nameCC); + if (!namesSC.contains(nameSC)) { + fail("\"" + nameCC + "\" misses corresponding snake case name, \"" + nameSC + "\"."); + } + } + } + + private MutableProcessingConfiguration createMutableProcessingConfiguration() throws IOException { + return new TemplateConfiguration.Builder(); + } + + private boolean keyFieldExists(String name) throws Exception { + try { + MutableProcessingConfiguration.class.getField(name.toUpperCase() + "_KEY"); + } catch (NoSuchFieldException e) { + return false; + } + return true; + } + + @Test + public void testCollectionSettingMutability() throws IOException { + MutableProcessingConfiguration<?> mpc = new Configuration.Builder(Configuration.VERSION_3_0_0); + + { + assertTrue(_CollectionUtil.isListKnownToBeUnmodifiable(mpc.getAutoIncludes())); + List<String> mutableValue = new ArrayList<>(); + mutableValue.add("x"); + mpc.setAutoIncludes(mutableValue); + List<String> immutableValue = mpc.getAutoIncludes(); + assertNotSame(mutableValue, immutableValue); // Must be a copy + assertTrue(_CollectionUtil.isListKnownToBeUnmodifiable(immutableValue)); + assertEquals(mutableValue, immutableValue); + mutableValue.add("y"); + assertNotEquals(mutableValue, immutableValue); // No aliasing + } + + { + assertTrue(_CollectionUtil.isMapKnownToBeUnmodifiable(mpc.getAutoImports())); + Map<String, String> mutableValue = new HashMap<>(); + mutableValue.put("x", "x.ftl"); + mpc.setAutoImports(mutableValue); + Map<String, String> immutableValue = mpc.getAutoImports(); + assertNotSame(mutableValue, immutableValue); // Must be a copy + assertTrue(_CollectionUtil.isMapKnownToBeUnmodifiable(immutableValue)); + assertEquals(mutableValue, immutableValue); + mutableValue.put("y", "y.ftl"); + assertNotEquals(mutableValue, immutableValue); // No aliasing + } + + { + assertTrue(_CollectionUtil.isMapKnownToBeUnmodifiable(mpc.getCustomDateFormats())); + Map<String, TemplateDateFormatFactory> mutableValue = new HashMap<>(); + mutableValue.put("x", EpochMillisTemplateDateFormatFactory.INSTANCE); + mpc.setCustomDateFormats(mutableValue); + Map<String, TemplateDateFormatFactory> immutableValue = mpc.getCustomDateFormats(); + assertNotSame(mutableValue, immutableValue); // Must be a copy + assertTrue(_CollectionUtil.isMapKnownToBeUnmodifiable(immutableValue)); + assertEquals(mutableValue, immutableValue); + mutableValue.put("y", EpochMillisDivTemplateDateFormatFactory.INSTANCE); + assertNotEquals(mutableValue, immutableValue); // No aliasing + } + + { + assertTrue(_CollectionUtil.isMapKnownToBeUnmodifiable(mpc.getCustomNumberFormats())); + Map<String, TemplateNumberFormatFactory> mutableValue = new HashMap<>(); + mutableValue.put("x", BaseNTemplateNumberFormatFactory.INSTANCE); + mpc.setCustomNumberFormats(mutableValue); + Map<String, TemplateNumberFormatFactory> immutableValue = mpc.getCustomNumberFormats(); + assertNotSame(mutableValue, immutableValue); // Must be a copy + assertTrue(_CollectionUtil.isMapKnownToBeUnmodifiable(immutableValue)); + assertEquals(mutableValue, immutableValue); + mutableValue.put("y", HexTemplateNumberFormatFactory.INSTANCE); + assertNotEquals(mutableValue, immutableValue); // No aliasing + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/db64ebc9/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java deleted file mode 100644 index 702a254..0000000 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core; - -import static org.apache.freemarker.test.hamcerst.Matchers.*; -import static org.junit.Assert.*; - -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; - -import javax.annotation.PostConstruct; - -import org.apache.freemarker.core.model.TemplateBooleanModel; -import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.model.impl.DefaultArrayAdapter; -import org.apache.freemarker.core.model.impl.DefaultListAdapter; -import org.apache.freemarker.core.model.impl.DefaultMapAdapter; -import org.apache.freemarker.core.model.impl.DefaultNonListCollectionAdapter; -import org.apache.freemarker.core.model.impl.DefaultObjectWrapperTest.TestBean; -import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper; -import org.apache.freemarker.core.model.impl.SimpleDate; -import org.apache.freemarker.core.model.impl.SimpleNumber; -import org.apache.freemarker.core.model.impl.SimpleScalar; -import org.junit.Test; - -public class RestrictedObjectWrapperTest { - - @Test - public void testBasics() throws TemplateModelException { - PostConstruct.class.toString(); - RestrictedObjectWrapper ow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); - testCustomizationCommonPart(ow); - assertTrue(ow.wrap(Collections.emptyMap()) instanceof DefaultMapAdapter); - assertTrue(ow.wrap(Collections.emptyList()) instanceof DefaultListAdapter); - assertTrue(ow.wrap(new boolean[] { }) instanceof DefaultArrayAdapter); - assertTrue(ow.wrap(new HashSet()) instanceof DefaultNonListCollectionAdapter); - } - - @SuppressWarnings("boxing") - private void testCustomizationCommonPart(RestrictedObjectWrapper ow) throws TemplateModelException { - assertTrue(ow.wrap("x") instanceof SimpleScalar); - assertTrue(ow.wrap(1.5) instanceof SimpleNumber); - assertTrue(ow.wrap(new Date()) instanceof SimpleDate); - assertEquals(TemplateBooleanModel.TRUE, ow.wrap(true)); - - try { - ow.wrap(new TestBean()); - fail(); - } catch (TemplateModelException e) { - assertThat(e.getMessage(), containsStringIgnoringCase("type")); - } - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/db64ebc9/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java deleted file mode 100644 index 43ff3bf..0000000 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core; - -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import java.io.File; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.apache.freemarker.core.model.TemplateBooleanModel; -import org.apache.freemarker.core.model.TemplateCollectionModel; -import org.apache.freemarker.core.model.TemplateCollectionModelEx; -import org.apache.freemarker.core.model.TemplateDateModel; -import org.apache.freemarker.core.model.TemplateHashModelEx2; -import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.model.TemplateModelWithAPISupport; -import org.apache.freemarker.core.model.TemplateNumberModel; -import org.apache.freemarker.core.model.TemplateScalarModel; -import org.apache.freemarker.core.model.TemplateSequenceModel; -import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper; -import org.junit.Test; -import org.w3c.dom.Document; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -public class RestrictedObjetWrapperTest { - - @Test - public void testDoesNotAllowAPIBuiltin() throws TemplateModelException { - RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); - - TemplateModelWithAPISupport map = (TemplateModelWithAPISupport) sow.wrap(new HashMap()); - try { - map.getAPI(); - fail(); - } catch (TemplateException e) { - assertThat(e.getMessage(), containsString("?api")); - } - } - - @SuppressWarnings("boxing") - @Test - public void testCanWrapBasicTypes() throws TemplateModelException { - RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); - assertTrue(sow.wrap("s") instanceof TemplateScalarModel); - assertTrue(sow.wrap(1) instanceof TemplateNumberModel); - assertTrue(sow.wrap(true) instanceof TemplateBooleanModel); - assertTrue(sow.wrap(new Date()) instanceof TemplateDateModel); - assertTrue(sow.wrap(new ArrayList()) instanceof TemplateSequenceModel); - assertTrue(sow.wrap(new String[0]) instanceof TemplateSequenceModel); - assertTrue(sow.wrap(new ArrayList().iterator()) instanceof TemplateCollectionModel); - assertTrue(sow.wrap(new HashSet()) instanceof TemplateCollectionModelEx); - assertTrue(sow.wrap(new HashMap()) instanceof TemplateHashModelEx2); - assertNull(sow.wrap(null)); - } - - @Test - public void testWontWrapDOM() throws SAXException, IOException, ParserConfigurationException, - TemplateModelException { - DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - InputSource is = new InputSource(); - is.setCharacterStream(new StringReader("<doc><sub a='1' /></doc>")); - Document doc = db.parse(is); - - RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); - try { - sow.wrap(doc); - fail(); - } catch (TemplateModelException e) { - assertThat(e.getMessage(), containsString("won't wrap")); - } - } - - @Test - public void testWontWrapGenericObjects() { - RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); - try { - sow.wrap(new File("/x")); - fail(); - } catch (TemplateModelException e) { - assertThat(e.getMessage(), containsString("won't wrap")); - } - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/db64ebc9/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java index a3fe6dc..5cf5811 100644 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java @@ -898,5 +898,17 @@ public class TemplateConfigurationTest { } } } + + @Test + public void testCanBeBuiltOnlyOnce() { + TemplateConfiguration.Builder builder = new TemplateConfiguration.Builder(); + builder.build(); + try { + builder.build(); + fail(); + } catch (IllegalStateException e) { + // Expected + } + } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/db64ebc9/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java index 6581b10..0a1e16c 100644 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java @@ -707,6 +707,18 @@ public class DefaultObjectWrapperTest { } } + @Test + public void testCanBeBuiltOnlyOnce() { + DefaultObjectWrapper.Builder tcb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0); + tcb.build(); + try { + tcb.build(); + fail(); + } catch (IllegalStateException e) { + // Expected + } + } + private TemplateHashModel wrapWithExposureLevel(Object bean, int exposureLevel) throws TemplateModelException { return (TemplateHashModel) new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0) .exposureLevel(exposureLevel).build() http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/db64ebc9/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapperTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapperTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapperTest.java new file mode 100644 index 0000000..1033340 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapperTest.java @@ -0,0 +1,155 @@ +/* + * 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.model.impl; + +import static org.apache.freemarker.test.hamcerst.Matchers.*; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; + +import javax.annotation.PostConstruct; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateBooleanModel; +import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateCollectionModelEx; +import org.apache.freemarker.core.model.TemplateDateModel; +import org.apache.freemarker.core.model.TemplateHashModelEx2; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelWithAPISupport; +import org.apache.freemarker.core.model.TemplateNumberModel; +import org.apache.freemarker.core.model.TemplateScalarModel; +import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.model.impl.DefaultObjectWrapperTest.TestBean; +import org.junit.Test; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +public class RestrictedObjectWrapperTest { + + @Test + public void testBasics() throws TemplateModelException { + PostConstruct.class.toString(); + RestrictedObjectWrapper ow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); + testCustomizationCommonPart(ow); + assertTrue(ow.wrap(Collections.emptyMap()) instanceof DefaultMapAdapter); + assertTrue(ow.wrap(Collections.emptyList()) instanceof DefaultListAdapter); + assertTrue(ow.wrap(new boolean[] { }) instanceof DefaultArrayAdapter); + assertTrue(ow.wrap(new HashSet()) instanceof DefaultNonListCollectionAdapter); + } + + @SuppressWarnings("boxing") + private void testCustomizationCommonPart(RestrictedObjectWrapper ow) throws TemplateModelException { + assertTrue(ow.wrap("x") instanceof SimpleScalar); + assertTrue(ow.wrap(1.5) instanceof SimpleNumber); + assertTrue(ow.wrap(new Date()) instanceof SimpleDate); + assertEquals(TemplateBooleanModel.TRUE, ow.wrap(true)); + + try { + ow.wrap(new TestBean()); + fail(); + } catch (TemplateModelException e) { + assertThat(e.getMessage(), containsStringIgnoringCase("type")); + } + } + + @Test + public void testDoesNotAllowAPIBuiltin() throws TemplateModelException { + RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); + + TemplateModelWithAPISupport map = (TemplateModelWithAPISupport) sow.wrap(new HashMap()); + try { + map.getAPI(); + fail(); + } catch (TemplateException e) { + assertThat(e.getMessage(), containsString("?api")); + } + } + + @SuppressWarnings("boxing") + @Test + public void testCanWrapBasicTypes() throws TemplateModelException { + RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); + assertTrue(sow.wrap("s") instanceof TemplateScalarModel); + assertTrue(sow.wrap(1) instanceof TemplateNumberModel); + assertTrue(sow.wrap(true) instanceof TemplateBooleanModel); + assertTrue(sow.wrap(new Date()) instanceof TemplateDateModel); + assertTrue(sow.wrap(new ArrayList()) instanceof TemplateSequenceModel); + assertTrue(sow.wrap(new String[0]) instanceof TemplateSequenceModel); + assertTrue(sow.wrap(new ArrayList().iterator()) instanceof TemplateCollectionModel); + assertTrue(sow.wrap(new HashSet()) instanceof TemplateCollectionModelEx); + assertTrue(sow.wrap(new HashMap()) instanceof TemplateHashModelEx2); + assertNull(sow.wrap(null)); + } + + @Test + public void testWontWrapDOM() throws SAXException, IOException, ParserConfigurationException, + TemplateModelException { + DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader("<doc><sub a='1' /></doc>")); + Document doc = db.parse(is); + + RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); + try { + sow.wrap(doc); + fail(); + } catch (TemplateModelException e) { + assertThat(e.getMessage(), containsString("won't wrap")); + } + } + + @Test + public void testWontWrapGenericObjects() { + RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); + try { + sow.wrap(new File("/x")); + fail(); + } catch (TemplateModelException e) { + assertThat(e.getMessage(), containsString("won't wrap")); + } + } + + @Test + public void testCanBeBuiltOnlyOnce() { + RestrictedObjectWrapper.Builder builder = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0); + builder.build(); + try { + builder.build(); + fail(); + } catch (IllegalStateException e) { + // Expected + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/db64ebc9/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 98ae7a9..e7e5947 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 @@ -1550,30 +1550,9 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc } /** - * Returns the FreeMarker version information, most importantly the major.minor.micro version numbers. - * - * On FreeMarker version numbering rules: - * <ul> - * <li>For final/stable releases the version number is like major.minor.micro, like 2.3.19. (Historically, - * when micro was 0 the version strings was like major.minor instead of the proper major.minor.0, but that's - * not like that anymore.) - * <li>When only the micro version is increased, compatibility with previous versions with the same - * major.minor is kept. Thus <tt>freemarker.jar</tt> can be replaced in an existing application without - * breaking it.</li> - * <li>For non-final/unstable versions (that almost nobody uses), the format is: - * <ul> - * <li>Starting from 2.3.20: major.minor.micro-extraInfo, like - * 2.3.20-nightly_20130506T123456Z, 2.4.0-RC01. The major.minor.micro - * always indicates the target we move towards, so 2.3.20-nightly or 2.3.20-M01 is - * after 2.3.19 and will eventually become to 2.3.20. "PRE", "M" and "RC" (uppercase!) means - * "preview", "milestone" and "release candidate" respectively, and is always followed by a 2 digit - * 0-padded counter, like M03 is the 3rd milestone release of a given major.minor.micro.</li> - * <li>Before 2.3.20: The extraInfo wasn't preceded by a "-". - * Instead of "nightly" there was "mod", where the major.minor.micro part has indicated where - * are we coming from, so 2.3.19mod (read as: 2.3.19 modified) was after 2.3.19 but before 2.3.20. - * Also, "pre" and "rc" was lowercase, and was followd by a number without 0-padding.</li> - * </ul> - * </ul> + * Returns the FreeMarker version information, most importantly the major.minor.micro version numbers; do not use + * this for {@link #getIncompatibleImprovements() #incompatibleImprovements} value, use constants like + * {@link Configuration#VERSION_3_0_0} for that. */ public static Version getVersion() { return VERSION; @@ -2551,7 +2530,7 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc _NullArgumentException.check("sharedVariables", sharedVariables); _CollectionUtil.safeCastMap( "sharedVariables", sharedVariables, String.class, false, Object.class,true); - this.sharedVariables = new HashMap<>(sharedVariables); + this.sharedVariables = Collections.unmodifiableMap(new HashMap<>(sharedVariables)); } /** @@ -2834,8 +2813,9 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc /** * @param incompatibleImprovements - * Specifies the value of the {@linkplain Configuration#getIncompatibleImprovements()} incompatible - * improvements setting}. This setting can't be changed later. + * Specifies the value of the {@link Configuration#getIncompatibleImprovements()} + * incompatibleImprovements} setting, such as {@link Configuration#VERSION_3_0_0}. This setting can't be + * changed later. */ public Builder(Version incompatibleImprovements) { super(incompatibleImprovements); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/db64ebc9/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java index eb28dac..75eac65 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java @@ -525,7 +525,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces * Not {@code null}; will be copied (to prevent aliasing effect); keys must conform to format name * syntactical restrictions (see in {@link #getCustomNumberFormats()}) */ - public void setCustomNumberFormats(Map<String, TemplateNumberFormatFactory> customNumberFormats) { + public void setCustomNumberFormats(Map<String, ? extends TemplateNumberFormatFactory> customNumberFormats) { setCustomNumberFormats(customNumberFormats, false); } @@ -534,7 +534,8 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces * {@code true} if we know that the 1st argument is already validated, immutable, and unchanging (means, * won't change later because of aliasing). */ - void setCustomNumberFormats(Map<String, TemplateNumberFormatFactory> customNumberFormats, + @SuppressWarnings({ "unchecked", "rawtypes" }) + void setCustomNumberFormats(Map<String, ? extends TemplateNumberFormatFactory> customNumberFormats, boolean validatedImmutableUnchanging) { _NullArgumentException.check("customNumberFormats", customNumberFormats); if (!validatedImmutableUnchanging) { @@ -547,7 +548,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces validateFormatNames(customNumberFormats.keySet()); this.customNumberFormats = Collections.unmodifiableMap(new HashMap<>(customNumberFormats)); } else { - this.customNumberFormats = customNumberFormats; + this.customNumberFormats = (Map) customNumberFormats; } } @@ -794,7 +795,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces * Not {@code null}; will be copied (to prevent aliasing effect); keys must conform to format name * syntactical restrictions (see in {@link #getCustomDateFormats()}) */ - public void setCustomDateFormats(Map<String, TemplateDateFormatFactory> customDateFormats) { + public void setCustomDateFormats(Map<String, ? extends TemplateDateFormatFactory> customDateFormats) { setCustomDateFormats(customDateFormats, false); } @@ -803,8 +804,9 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces * {@code true} if we know that the 1st argument is already validated, immutable, and unchanging (means, * won't change later because of aliasing). */ + @SuppressWarnings({ "unchecked", "rawtypes" }) void setCustomDateFormats( - Map<String, TemplateDateFormatFactory> customDateFormats, + Map<String, ? extends TemplateDateFormatFactory> customDateFormats, boolean validatedImmutableUnchanging) { _NullArgumentException.check("customDateFormats", customDateFormats); if (!validatedImmutableUnchanging) { @@ -815,9 +817,9 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces String.class, false, TemplateDateFormatFactory.class, false); validateFormatNames(customDateFormats.keySet()); - this.customDateFormats = Collections.unmodifiableMap(new HashMap(customDateFormats)); + this.customDateFormats = Collections.unmodifiableMap(new HashMap<>(customDateFormats)); } else { - this.customDateFormats = customDateFormats; + this.customDateFormats = (Map) customDateFormats; } } @@ -1363,7 +1365,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces return; } _CollectionUtil.safeCastMap("autoImports", autoImports, String.class, false, String.class, false); - this.autoImports = new LinkedHashMap<>(autoImports); + this.autoImports = Collections.unmodifiableMap(new LinkedHashMap<>(autoImports)); } else { this.autoImports = autoImports; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/db64ebc9/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java index 40542a6..4aaa7c2 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java @@ -23,10 +23,12 @@ import java.io.Serializable; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.TimeZone; import org.apache.freemarker.core.arithmetic.ArithmeticEngine; @@ -162,14 +164,23 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur * Adds two {@link List}-s; assuming the inputs are already unmodifiable and unchanging, it returns an * unmodifiable and unchanging {@link List} itself. */ - private static List<String> mergeLists(List<String> list1, List<String> list2) { + private static List<String> mergeLists(List<String> list1, List<String> list2, boolean skipDuplicatesInList1) { if (list1 == null) return list2; if (list2 == null) return list1; if (list1.isEmpty()) return list2; if (list2.isEmpty()) return list1; ArrayList<String> mergedList = new ArrayList<>(list1.size() + list2.size()); - mergedList.addAll(list1); + if (skipDuplicatesInList1) { + Set<String> list2Set = new HashSet<>(list2); + for (String it : list1) { + if (!list2Set.contains(it)) { + mergedList.add(it); + } + } + } else { + mergedList.addAll(list1); + } mergedList.addAll(list2); return Collections.unmodifiableList(mergedList); } @@ -933,12 +944,15 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur setAutoImports(mergeMaps( isAutoImportsSet() ? getAutoImports() : null, tc.isAutoImportsSet() ? tc.getAutoImports() : null, - true)); + true), + true); } if (tc.isAutoIncludesSet()) { setAutoIncludes(mergeLists( isAutoIncludesSet() ? getAutoIncludes() : null, - tc.isAutoIncludesSet() ? tc.getAutoIncludes() : null)); + tc.isAutoIncludesSet() ? tc.getAutoIncludes() : null, + true), + true); } setCustomSettingsMap(mergeMaps( http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/db64ebc9/freemarker-core/src/main/java/org/apache/freemarker/core/util/ProductWrappingBuilder.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/ProductWrappingBuilder.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/ProductWrappingBuilder.java deleted file mode 100644 index 86d0b3e..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/ProductWrappingBuilder.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.util; - -/** - * A builder that encloses an already built product. {@link #build()} will always return the same product object. - */ -public class ProductWrappingBuilder<ProductT> implements CommonBuilder<ProductT> { - - private final ProductT product; - private boolean alreadyBuilt; - - public ProductWrappingBuilder(ProductT product) { - _NullArgumentException.check("product", product); - this.product = product; - } - - @Override - public ProductT build() { - if (alreadyBuilt) { - throw new IllegalStateException("build() can only be executed once."); - } - alreadyBuilt = true; - return product; - } -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/db64ebc9/freemarker-core/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java index 275f64c..1f91821 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java @@ -19,6 +19,7 @@ package org.apache.freemarker.core.util; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -114,6 +115,9 @@ public class _CollectionUtil { private static final Class<?> UNMODIFIABLE_MAP_CLASS_1 = Collections.emptyMap().getClass(); private static final Class<?> UNMODIFIABLE_MAP_CLASS_2 = Collections.unmodifiableMap( new HashMap<Object, Object> (1)).getClass(); + private static final Class<?> UNMODIFIABLE_LIST_CLASS_1 = Collections.emptyList().getClass(); + private static final Class<?> UNMODIFIABLE_LIST_CLASS_2 = Collections.unmodifiableList( + new ArrayList<Object>(1)).getClass(); public static boolean isMapKnownToBeUnmodifiable(Map<?, ?> map) { if (map == null) { @@ -123,6 +127,14 @@ public class _CollectionUtil { return mapClass == UNMODIFIABLE_MAP_CLASS_1 || mapClass == UNMODIFIABLE_MAP_CLASS_2; } + public static boolean isListKnownToBeUnmodifiable(List<?> list) { + if (list == null) { + return true; + } + Class<? extends List> listClass = list.getClass(); + return listClass == UNMODIFIABLE_LIST_CLASS_1 || listClass == UNMODIFIABLE_LIST_CLASS_2; + } + /** * Optimized version of {@link Collections#unmodifiableMap(Map)} (avoids needless wrapping). *
