blob: f94f2d962521c9a25177600d3a90d845f20fa50c [file] [log] [blame]
/*
* 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 freemarker.template;
import static freemarker.test.hamcerst.Matchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
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 java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.junit.Test;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.HashAdapter;
import freemarker.ext.util.WrapperTemplateModel;
public class DefaultObjectWrapperTest {
private final static DefaultObjectWrapper OW0 = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_0)
.build();
private final static DefaultObjectWrapper OW22 = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_22)
.build();
private final static DefaultObjectWrapper OW22NM = new DefaultObjectWrapper(Configuration.VERSION_2_3_22);
static {
OW22NM.setNullModel(NullModel.INSTANCE);
}
private final static DefaultObjectWrapper OW22_FUTURE = new DefaultObjectWrapper(Configuration.VERSION_2_3_22);
static {
OW22_FUTURE.setForceLegacyNonListCollections(false);
}
@Test
public void testIncompatibleImprovementsVersionBreakPoints() throws Exception {
List<Version> expected = new ArrayList<Version>();
for (int u = 0; u < 21; u++) {
expected.add(Configuration.VERSION_2_3_0);
}
expected.add(Configuration.VERSION_2_3_21);
expected.add(Configuration.VERSION_2_3_22);
expected.add(Configuration.VERSION_2_3_22); // no non-BC change in 2.3.23
expected.add(Configuration.VERSION_2_3_24);
List<Version> actual = new ArrayList<Version>();
for (int i = _TemplateAPI.VERSION_INT_2_3_0; i <= Configuration.getVersion().intValue(); i++) {
int major = i / 1000000;
int minor = i % 1000000 / 1000;
int micro = i % 1000;
final Version version = new Version(major, minor, micro);
final Version normalizedVersion = DefaultObjectWrapper.normalizeIncompatibleImprovementsVersion(version);
actual.add(normalizedVersion);
final DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(version);
assertEquals(normalizedVersion, builder.getIncompatibleImprovements());
assertEquals(normalizedVersion, builder.build().getIncompatibleImprovements());
}
assertEquals(expected, actual);
}
@Test
public void testIncompatibleImprovementsVersionOutOfBounds() throws Exception {
try {
DefaultObjectWrapper.normalizeIncompatibleImprovementsVersion(new Version(2, 2, 0));
fail();
} catch (IllegalArgumentException e) {
// expected
}
Version curVersion = Configuration.getVersion();
final Version futureVersion = new Version(curVersion.getMajor(), curVersion.getMicro(),
curVersion.getMicro() + 1);
try {
DefaultObjectWrapper.normalizeIncompatibleImprovementsVersion(futureVersion);
fail();
} catch (IllegalArgumentException e) {
// expected
}
try {
new DefaultObjectWrapperBuilder(futureVersion);
fail();
} catch (IllegalArgumentException e) {
// expected
}
}
@SuppressWarnings("boxing")
@Test
public void testBuilder() throws Exception {
{
DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_19);
DefaultObjectWrapper bw = builder.build();
assertSame(bw, builder.build());
assertSame(bw.getClass(), DefaultObjectWrapper.class);
assertEquals(Configuration.VERSION_2_3_0, bw.getIncompatibleImprovements());
assertTrue(bw.isWriteProtected());
assertTrue(bw.isClassIntrospectionCacheRestricted());
assertFalse(bw.getUseAdaptersForContainers());
assertTrue(bw.getForceLegacyNonListCollections());
assertTrue(bw.wrap(new HashMap()) instanceof SimpleHash);
assertTrue(bw.wrap(new ArrayList()) instanceof SimpleSequence);
assertTrue(bw.wrap(new String[] {}) instanceof SimpleSequence);
assertTrue(bw.wrap(new HashSet()) instanceof SimpleSequence);
}
for (boolean simpleMapWrapper : new boolean[] { true, false }) {
{
DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_21);
builder.setSimpleMapWrapper(simpleMapWrapper); // Shouldn't mater
DefaultObjectWrapper bw = builder.build();
assertSame(bw, builder.build());
assertSame(bw.getClass(), DefaultObjectWrapper.class);
assertEquals(Configuration.VERSION_2_3_21, bw.getIncompatibleImprovements());
assertTrue(bw.isWriteProtected());
assertEquals(simpleMapWrapper, bw.isSimpleMapWrapper());
assertFalse(bw.getUseAdaptersForContainers());
assertTrue(bw.getForceLegacyNonListCollections());
assertTrue(bw.wrap(new HashMap()) instanceof SimpleHash);
assertTrue(bw.wrap(new ArrayList()) instanceof SimpleSequence);
assertTrue(bw.wrap(new String[] {}) instanceof SimpleSequence);
assertTrue(bw.wrap(new HashSet()) instanceof SimpleSequence);
assertTrue(bw.wrap('c') instanceof TemplateScalarModel); // StringModel now, but should change later
}
{
DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_22);
builder.setSimpleMapWrapper(simpleMapWrapper); // Shouldn't mater
DefaultObjectWrapper bw = builder.build();
assertSame(bw, builder.build());
assertSame(bw.getClass(), DefaultObjectWrapper.class);
assertEquals(Configuration.VERSION_2_3_22, bw.getIncompatibleImprovements());
assertTrue(bw.isWriteProtected());
assertEquals(simpleMapWrapper, bw.isSimpleMapWrapper());
assertTrue(bw.getUseAdaptersForContainers());
assertTrue(bw.getForceLegacyNonListCollections());
assertTrue(bw.wrap(new HashMap()) instanceof DefaultMapAdapter);
assertTrue(bw.wrap(new ArrayList()) instanceof DefaultListAdapter);
assertTrue(bw.wrap(new String[] {}) instanceof DefaultArrayAdapter);
assertTrue(bw.wrap(new HashSet()) instanceof SimpleSequence);
}
}
{
DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.getVersion());
builder.setSimpleMapWrapper(true);
BeansWrapper bw = builder.build();
assertSame(bw, builder.build());
assertSame(bw.getClass(), DefaultObjectWrapper.class);
assertEquals(
DefaultObjectWrapper.normalizeIncompatibleImprovementsVersion(Configuration.getVersion()),
bw.getIncompatibleImprovements());
assertTrue(bw.isWriteProtected());
assertTrue(bw.isSimpleMapWrapper());
assertTrue(bw.wrap(new HashMap()) instanceof DefaultMapAdapter);
}
{
DefaultObjectWrapper bw = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_19).build();
assertSame(bw.getClass(), DefaultObjectWrapper.class);
assertEquals(Configuration.VERSION_2_3_0, bw.getIncompatibleImprovements());
assertTrue(bw.isWriteProtected());
assertFalse(bw.isSimpleMapWrapper());
assertTrue(bw.wrap(new HashMap()) instanceof SimpleHash);
assertSame(bw, new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_20).build());
assertSame(bw, new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_0).build());
assertSame(bw, new DefaultObjectWrapperBuilder(new Version(2, 3, 5)).build());
}
{
DefaultObjectWrapper bw = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_21).build();
assertSame(bw.getClass(), DefaultObjectWrapper.class);
assertEquals(Configuration.VERSION_2_3_21, bw.getIncompatibleImprovements());
assertTrue(bw.isWriteProtected());
assertFalse(bw.isSimpleMapWrapper());
assertTrue(bw.wrap(new HashMap()) instanceof SimpleHash);
assertTrue(bw.isClassIntrospectionCacheRestricted());
assertSame(bw, new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_21).build());
}
{
DefaultObjectWrapper bw = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_19).build();
assertEquals(Configuration.VERSION_2_3_0, bw.getIncompatibleImprovements());
}
{
DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_19);
builder.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);
DefaultObjectWrapper bw = builder.build();
DefaultObjectWrapper bw2 = builder.build();
assertSame(bw, bw2); // not cached
assertSame(bw.getClass(), DefaultObjectWrapper.class);
assertEquals(Configuration.VERSION_2_3_0, bw.getIncompatibleImprovements());
assertTrue(bw.isWriteProtected());
assertFalse(bw.isSimpleMapWrapper());
assertTrue(bw.wrap(new HashMap()) instanceof SimpleHash);
assertEquals(BeansWrapper.EXPOSE_PROPERTIES_ONLY, bw.getExposureLevel());
}
{
DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_19);
builder.setExposeFields(true);
BeansWrapper bw = builder.build();
BeansWrapper bw2 = builder.build();
assertSame(bw, bw2); // not cached
assertSame(bw.getClass(), DefaultObjectWrapper.class);
assertEquals(Configuration.VERSION_2_3_0, bw.getIncompatibleImprovements());
assertTrue(bw.isWriteProtected());
assertFalse(bw.isSimpleMapWrapper());
assertTrue(bw.wrap(new HashMap()) instanceof SimpleHash);
assertEquals(true, bw.isExposeFields());
}
{
DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_21);
builder.setForceLegacyNonListCollections(false);
DefaultObjectWrapper bw = builder.build();
assertSame(bw, builder.build());
assertFalse(bw.getForceLegacyNonListCollections());
assertTrue(bw.wrap(new HashMap()) instanceof SimpleHash);
assertTrue(bw.wrap(new ArrayList()) instanceof SimpleSequence);
assertTrue(bw.wrap(new String[] {}) instanceof SimpleSequence);
assertTrue(bw.wrap(new HashSet()) instanceof SimpleSequence);
}
{
DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_22);
builder.setForceLegacyNonListCollections(false);
DefaultObjectWrapper bw = builder.build();
assertSame(bw, builder.build());
assertFalse(bw.getForceLegacyNonListCollections());
assertTrue(bw.wrap(new HashMap()) instanceof DefaultMapAdapter);
assertTrue(bw.wrap(new ArrayList()) instanceof DefaultListAdapter);
assertTrue(bw.wrap(new String[] {}) instanceof DefaultArrayAdapter);
assertTrue(bw.wrap(new HashSet()) instanceof DefaultNonListCollectionAdapter);
}
}
@Test
public void testConstructors() throws Exception {
{
DefaultObjectWrapper ow = new DefaultObjectWrapper();
assertEquals(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS, ow.getIncompatibleImprovements());
assertFalse(ow.isWriteProtected());
assertFalse(ow.getUseAdaptersForContainers());
assertTrue(ow.getForceLegacyNonListCollections());
}
{
DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_2_3_20);
assertEquals(Configuration.VERSION_2_3_0, ow.getIncompatibleImprovements());
assertFalse(ow.isWriteProtected());
assertFalse(ow.getUseAdaptersForContainers());
assertTrue(ow.getForceLegacyNonListCollections());
}
{
DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_2_3_21);
assertEquals(Configuration.VERSION_2_3_21, ow.getIncompatibleImprovements());
assertFalse(ow.isWriteProtected());
assertFalse(ow.getUseAdaptersForContainers());
assertTrue(ow.getForceLegacyNonListCollections());
}
{
DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_2_3_22);
assertEquals(Configuration.VERSION_2_3_22, ow.getIncompatibleImprovements());
assertFalse(ow.isWriteProtected());
assertTrue(ow.getUseAdaptersForContainers());
assertTrue(ow.getForceLegacyNonListCollections());
}
try {
new DefaultObjectWrapper(new Version(99, 9, 9));
fail();
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("version"));
}
}
@Test
public void testCustomization() throws TemplateModelException {
{
CustomizedDefaultObjectWrapper ow = new CustomizedDefaultObjectWrapper(Configuration.VERSION_2_3_22);
assertEquals(Configuration.VERSION_2_3_22, ow.getIncompatibleImprovements());
assertTrue(ow.getUseAdaptersForContainers());
testCustomizationCommonPart(ow,
DefaultMapAdapter.class, DefaultListAdapter.class, DefaultArrayAdapter.class);
}
{
CustomizedDefaultObjectWrapper ow = new CustomizedDefaultObjectWrapper(Configuration.VERSION_2_3_21);
assertFalse(ow.getUseAdaptersForContainers());
assertEquals(Configuration.VERSION_2_3_21, ow.getIncompatibleImprovements());
testCustomizationCommonPart(ow,
SimpleHash.class, SimpleSequence.class, SimpleSequence.class);
}
{
CustomizedDefaultObjectWrapper ow = new CustomizedDefaultObjectWrapper(Configuration.VERSION_2_3_20);
assertFalse(ow.getUseAdaptersForContainers());
assertEquals(Configuration.VERSION_2_3_0, ow.getIncompatibleImprovements());
testCustomizationCommonPart(ow,
SimpleHash.class, SimpleSequence.class, SimpleSequence.class);
}
}
@SuppressWarnings("boxing")
private void testCustomizationCommonPart(CustomizedDefaultObjectWrapper ow,
Class<? extends TemplateHashModel> mapTMClass,
Class<? extends TemplateSequenceModel> listTMClass,
Class<? extends TemplateSequenceModel> arrayTMClass)
throws TemplateModelException {
assertFalse(ow.isWriteProtected());
TemplateSequenceModel seq = (TemplateSequenceModel) ow.wrap(new Tupple(11, 22));
assertEquals(2, seq.size());
assertEquals(11, ow.unwrap(seq.get(0)));
assertEquals(22, ow.unwrap(seq.get(1)));
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));
assertTrue(mapTMClass.isInstance(ow.wrap(Collections.emptyMap())));
assertTrue(listTMClass.isInstance(ow.wrap(Collections.emptyList())));
assertTrue(arrayTMClass.isInstance(ow.wrap(new boolean[] { })));
assertTrue(ow.wrap(new HashSet()) instanceof SimpleSequence); // at least until IcI 2.4
assertTrue(ow.wrap('c') instanceof TemplateScalarModel); // StringModel right now, but should change later
TemplateHashModel bean = (TemplateHashModel) ow.wrap(new TestBean());
assertEquals(1, ow.unwrap(bean.get("x")));
{
// Check method calls, and also if the return value is wrapped with the overidden "wrap".
final TemplateModel mr = (TemplateModel) ((TemplateMethodModelEx) bean.get("m")).exec(Collections.emptyList());
assertEquals(
Collections.singletonList(1),
ow.unwrap(mr));
assertTrue(listTMClass.isInstance(mr));
}
{
// Check custom TM usage and round trip:
final TemplateModel mr = (TemplateModel) ((TemplateMethodModelEx) bean.get("incTupple"))
.exec(Collections.singletonList(ow.wrap(new Tupple<Integer, Integer>(1, 2))));
assertEquals(new Tupple<Integer, Integer>(2, 3), ow.unwrap(mr));
assertTrue(TuppleAdapter.class.isInstance(mr));
}
}
@SuppressWarnings("boxing")
@Test
public void testRoundtripping() throws TemplateModelException, ClassNotFoundException {
DefaultObjectWrapper dow21 = new DefaultObjectWrapper(Configuration.VERSION_2_3_21);
DefaultObjectWrapper dow22 = new DefaultObjectWrapper(Configuration.VERSION_2_3_22);
final Map hashMap = new HashMap();
inintTestMap(hashMap);
final Map treeMap = new TreeMap();
inintTestMap(treeMap);
final Map linkedHashMap = new LinkedHashMap();
inintTestMap(linkedHashMap);
final Map gMap = ImmutableMap.<String, Object> of("a", 1, "b", 2, "c", 3);
final LinkedList linkedList = new LinkedList();
linkedList.add("a");
linkedList.add("b");
linkedList.add("c");
final int[] intArray = new int[] { 1, 2, 3 };
final String[] stringArray = new String[] { "a", "b", "c" };
assertRoundtrip(dow21, linkedHashMap, SimpleHash.class, HashAdapter.class, linkedHashMap.toString());
assertRoundtrip(dow21, treeMap, SimpleHash.class, HashAdapter.class, treeMap.toString());
assertRoundtrip(dow21, gMap, SimpleHash.class, HashAdapter.class, hashMap.toString());
assertRoundtrip(dow21, linkedList, SimpleSequence.class, Class.forName("freemarker.ext.beans.SequenceAdapter"),
linkedList.toString());
assertRoundtrip(dow21, intArray, SimpleSequence.class, Class.forName("freemarker.ext.beans.SequenceAdapter"),
"[1, 2, 3]");
assertRoundtrip(dow21, stringArray, SimpleSequence.class,
Class.forName("freemarker.ext.beans.SequenceAdapter"),
"[a, b, c]");
assertRoundtrip(dow22, linkedHashMap, DefaultMapAdapter.class, LinkedHashMap.class, linkedHashMap.toString());
assertRoundtrip(dow22, treeMap, DefaultMapAdapter.class, TreeMap.class, treeMap.toString());
assertRoundtrip(dow22, gMap, DefaultMapAdapter.class, ImmutableMap.class, gMap.toString());
assertRoundtrip(dow22, linkedList, DefaultListAdapter.class, LinkedList.class, linkedList.toString());
assertRoundtrip(dow22, intArray, DefaultArrayAdapter.class, int[].class, null);
assertRoundtrip(dow22, stringArray, DefaultArrayAdapter.class, String[].class, null);
}
@SuppressWarnings("boxing")
private void inintTestMap(Map map) {
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
}
@SuppressWarnings("boxing")
@Test
public void testMapAdapter() throws TemplateModelException {
HashMap<String, Object> testMap = new LinkedHashMap<String, Object>();
testMap.put("a", 1);
testMap.put("b", null);
testMap.put("c", "C");
testMap.put("d", Collections.singletonList("x"));
{
TemplateHashModelEx hash = (TemplateHashModelEx) OW22.wrap(testMap);
assertEquals(4, hash.size());
assertFalse(hash.isEmpty());
assertNull(hash.get("e"));
assertEquals(1, ((TemplateNumberModel) hash.get("a")).getAsNumber());
assertNull(hash.get("b"));
assertEquals("C", ((TemplateScalarModel) hash.get("c")).getAsString());
assertTrue(hash.get("d") instanceof DefaultListAdapter);
assertCollectionTMEquals(hash.keys(), "a", "b", "c", "d");
assertCollectionTMEquals(hash.values(), 1, null, "C", Collections.singletonList("x"));
assertSizeThroughAPIModel(4, hash);
}
{
assertTrue(((TemplateHashModel) OW22.wrap(Collections.emptyMap())).isEmpty());
}
{
final TemplateHashModelEx hash = (TemplateHashModelEx) OW22NM.wrap(testMap);
assertSame(NullModel.INSTANCE, hash.get("b"));
assertNull(hash.get("e"));
assertCollectionTMEquals(hash.keys(), "a", "b", "c", "d");
assertCollectionTMEquals(hash.values(), 1, null, "C", Collections.singletonList("x"));
}
}
private void assertCollectionTMEquals(TemplateCollectionModel coll, Object... expectedItems)
throws TemplateModelException {
for (int i = 0; i < 2; i++) { // Run twice to check if we always get a new iterator
int idx = 0;
TemplateModelIterator it2 = null;
for (TemplateModelIterator it = coll.iterator(); it.hasNext(); ) {
TemplateModel actualItem = it.next();
if (idx >= expectedItems.length) {
fail("Number of items is more than the expected " + expectedItems.length);
}
assertEquals(expectedItems[idx], OW22.unwrap(actualItem));
if (i == 1) {
// In the 2nd round we also test with two iterators in parallel.
// This 2nd iterator is also special in that its hasNext() is never called.
if (it2 == null) {
it2 = coll.iterator();
}
assertEquals(expectedItems[idx], OW22.unwrap(it2.next()));
}
idx++;
}
if (expectedItems.length != idx) {
fail("Number of items is " + idx + ", which is less than the expected " + expectedItems.length);
}
}
}
@SuppressWarnings("boxing")
@Test
public void testListAdapter() throws TemplateModelException {
{
List testList = new ArrayList<Object>();
testList.add(1);
testList.add(null);
testList.add("c");
testList.add(new String[] { "x" });
TemplateSequenceModel seq = (TemplateSequenceModel) OW22.wrap(testList);
assertTrue(seq instanceof DefaultListAdapter);
assertFalse(seq instanceof TemplateCollectionModel); // Maybe changes at 2.4.0
assertEquals(4, seq.size());
assertNull(seq.get(-1));
assertEquals(1, ((TemplateNumberModel) seq.get(0)).getAsNumber());
assertNull(seq.get(1));
assertEquals("c", ((TemplateScalarModel) seq.get(2)).getAsString());
assertTrue(seq.get(3) instanceof DefaultArrayAdapter);
assertNull(seq.get(4));
assertSizeThroughAPIModel(4, seq);
}
{
List testList = new LinkedList<Object>();
testList.add(1);
testList.add(null);
testList.add("c");
TemplateSequenceModel seq = (TemplateSequenceModel) OW22.wrap(testList);
assertTrue(seq instanceof DefaultListAdapter);
assertTrue(seq instanceof TemplateCollectionModel); // Maybe changes at 2.4.0
assertEquals(3, seq.size());
assertNull(seq.get(-1));
assertEquals(1, ((TemplateNumberModel) seq.get(0)).getAsNumber());
assertNull(seq.get(1));
assertEquals("c", ((TemplateScalarModel) seq.get(2)).getAsString());
assertNull(seq.get(3));
assertCollectionTMEquals((TemplateCollectionModel) seq, 1, null, "c");
TemplateModelIterator it = ((TemplateCollectionModel) seq).iterator();
it.next();
it.next();
it.next();
try {
it.next();
fail();
} catch (TemplateModelException e) {
assertThat(e.getMessage(), containsString("no more"));
}
}
{
List testList = new ArrayList<Object>();
testList.add(null);
final TemplateSequenceModel seq = (TemplateSequenceModel) OW22NM.wrap(testList);
assertSame(NullModel.INSTANCE, seq.get(0));
assertNull(seq.get(1));
}
}
@Test
public void testArrayAdapterTypes() throws TemplateModelException {
assertArrayAdapterClass("Object", OW22.wrap(new Object[] {}));
assertArrayAdapterClass("Object", OW22.wrap(new String[] {}));
assertArrayAdapterClass("byte", OW22.wrap(new byte[] {}));
assertArrayAdapterClass("short", OW22.wrap(new short[] {}));
assertArrayAdapterClass("int", OW22.wrap(new int[] {}));
assertArrayAdapterClass("long", OW22.wrap(new long[] {}));
assertArrayAdapterClass("float", OW22.wrap(new float[] {}));
assertArrayAdapterClass("double", OW22.wrap(new double[] {}));
assertArrayAdapterClass("boolean", OW22.wrap(new boolean[] {}));
assertArrayAdapterClass("char", OW22.wrap(new char[] {}));
}
private void assertArrayAdapterClass(String adapterCompType, TemplateModel adaptedArray) {
assertTrue(adaptedArray instanceof DefaultArrayAdapter);
assertThat(adaptedArray.getClass().getName(),
containsString("$" + adapterCompType.substring(0, 1).toUpperCase() + adapterCompType.substring(1)));
}
@SuppressWarnings("boxing")
@Test
public void testArrayAdapters() throws TemplateModelException {
{
final String[] testArray = new String[] { "a", null, "c" };
{
TemplateSequenceModel seq = (TemplateSequenceModel) OW22.wrap(testArray);
assertEquals(3, seq.size());
assertNull(seq.get(-1));
assertEquals("a", ((TemplateScalarModel) seq.get(0)).getAsString());
assertNull(seq.get(1));
assertEquals("c", ((TemplateScalarModel) seq.get(2)).getAsString());
assertNull(seq.get(3));
}
{
TemplateSequenceModel seq = (TemplateSequenceModel) OW22NM.wrap(testArray);
assertNull(seq.get(-1));
assertEquals("a", ((TemplateScalarModel) seq.get(0)).getAsString());
assertSame(NullModel.INSTANCE, seq.get(1));
assertEquals("c", ((TemplateScalarModel) seq.get(2)).getAsString());
assertNull(seq.get(3));
}
}
{
final int[] testArray = new int[] { 11, 22 };
TemplateSequenceModel seq = (TemplateSequenceModel) OW22.wrap(testArray);
assertEquals(2, seq.size());
assertNull(seq.get(-1));
assertEqualsAndSameClass(Integer.valueOf(11), ((TemplateNumberModel) seq.get(0)).getAsNumber());
assertEqualsAndSameClass(Integer.valueOf(22), ((TemplateNumberModel) seq.get(1)).getAsNumber());
assertNull(seq.get(2));
}
{
final double[] testArray = new double[] { 11, 22 };
TemplateSequenceModel seq = (TemplateSequenceModel) OW22.wrap(testArray);
assertEquals(2, seq.size());
assertNull(seq.get(-1));
assertEqualsAndSameClass(Double.valueOf(11), ((TemplateNumberModel) seq.get(0)).getAsNumber());
assertEqualsAndSameClass(Double.valueOf(22), ((TemplateNumberModel) seq.get(1)).getAsNumber());
assertNull(seq.get(2));
}
{
final boolean[] testArray = new boolean[] { true, false };
TemplateSequenceModel seq = (TemplateSequenceModel) OW22.wrap(testArray);
assertEquals(2, seq.size());
assertNull(seq.get(-1));
assertEqualsAndSameClass(Boolean.valueOf(true), ((TemplateBooleanModel) seq.get(0)).getAsBoolean());
assertEqualsAndSameClass(Boolean.valueOf(false), ((TemplateBooleanModel) seq.get(1)).getAsBoolean());
assertNull(seq.get(2));
}
{
final char[] testArray = new char[] { 'a', 'b' };
TemplateSequenceModel seq = (TemplateSequenceModel) OW22.wrap(testArray);
assertEquals(2, seq.size());
assertNull(seq.get(-1));
assertEquals("a", ((TemplateScalarModel) seq.get(0)).getAsString());
assertEquals("b", ((TemplateScalarModel) seq.get(1)).getAsString());
assertNull(seq.get(2));
}
}
private void assertEqualsAndSameClass(Object expected, Object actual) {
assertEquals(expected, actual);
if (expected != null) {
assertEquals(expected.getClass(), actual.getClass());
}
}
private void assertRoundtrip(DefaultObjectWrapper dow, Object obj, Class expectedTMClass,
Class expectedPojoClass,
String expectedPojoToString)
throws TemplateModelException {
final TemplateModel objTM = dow.wrap(obj);
assertTrue(expectedTMClass.isAssignableFrom(objTM.getClass()));
final TemplateHashModel testBeanTM = (TemplateHashModel) dow.wrap(new RoundtripTesterBean());
{
TemplateMethodModelEx getClassM = (TemplateMethodModelEx) testBeanTM.get("getClass");
Object r = getClassM.exec(Collections.singletonList(objTM));
final Class rClass = (Class) ((WrapperTemplateModel) r).getWrappedObject();
assertTrue(rClass + " instanceof " + expectedPojoClass, expectedPojoClass.isAssignableFrom(rClass));
}
if (expectedPojoToString != null) {
TemplateMethodModelEx getToStringM = (TemplateMethodModelEx) testBeanTM.get("toString");
Object r = getToStringM.exec(Collections.singletonList(objTM));
assertEquals(expectedPojoToString, ((TemplateScalarModel) r).getAsString());
}
}
@SuppressWarnings("boxing")
@Test
public void testCollectionAdapterBasics() throws TemplateModelException {
{
Set set = new TreeSet();
set.add("a");
set.add("b");
set.add("c");
TemplateCollectionModelEx coll = (TemplateCollectionModelEx) OW22_FUTURE.wrap(set);
assertTrue(coll instanceof DefaultNonListCollectionAdapter);
assertEquals(3, coll.size());
assertFalse(coll.isEmpty());
assertCollectionTMEquals(coll, "a", "b", "c");
assertTrue(coll.contains(OW22_FUTURE.wrap("a")));
assertTrue(coll.contains(OW22_FUTURE.wrap("b")));
assertTrue(coll.contains(OW22_FUTURE.wrap("c")));
assertTrue(coll.contains(OW22_FUTURE.wrap("c")));
assertFalse(coll.contains(OW22_FUTURE.wrap("d")));
try {
assertFalse(coll.contains(OW22_FUTURE.wrap(1)));
fail();
} catch (TemplateModelException e) {
assertThat(e.getMessage(), containsString("Integer"));
}
assertRoundtrip(OW22_FUTURE, set, DefaultNonListCollectionAdapter.class, TreeSet.class, "[a, b, c]");
assertSizeThroughAPIModel(3, coll);
}
{
Set set = new HashSet();
final List<String> list = Collections.singletonList("b");
set.add(list);
set.add(null);
TemplateCollectionModelEx coll = (TemplateCollectionModelEx) OW22_FUTURE.wrap(set);
TemplateModelIterator it = coll.iterator();
final TemplateModel tm1 = it.next();
Object obj1 = OW22_FUTURE.unwrap(tm1);
final TemplateModel tm2 = it.next();
Object obj2 = OW22_FUTURE.unwrap(tm2);
assertTrue(obj1 == null || obj2 == null);
assertTrue(obj1 != null && obj1.equals(list) || obj2 != null && obj2.equals(list));
assertTrue(tm1 instanceof DefaultListAdapter || tm2 instanceof DefaultListAdapter);
List similarList = new ArrayList();
similarList.add("b");
assertTrue(coll.contains(OW22_FUTURE.wrap(similarList)));
assertTrue(coll.contains(OW22_FUTURE.wrap(null)));
assertFalse(coll.contains(OW22_FUTURE.wrap("a")));
assertFalse(coll.contains(OW22_FUTURE.wrap(1)));
assertRoundtrip(OW22_FUTURE, set, DefaultNonListCollectionAdapter.class, HashSet.class, "[" + obj1 + ", "
+ obj2 + "]");
}
}
@SuppressWarnings("boxing")
@Test
public void testCollectionAdapterOutOfBounds() throws TemplateModelException {
Set set = Collections.singleton(123);
TemplateCollectionModelEx coll = (TemplateCollectionModelEx) OW22_FUTURE.wrap(set);
TemplateModelIterator it = coll.iterator();
for (int i = 0; i < 3; i++) {
assertTrue(it.hasNext());
}
assertEquals(123, OW22_FUTURE.unwrap(it.next()));
for (int i = 0; i < 3; i++) {
assertFalse(it.hasNext());
try {
it.next();
fail();
} catch (TemplateModelException e) {
assertThat(e.getMessage(), containsStringIgnoringCase("no more"));
}
}
}
@Test
public void testCollectionAdapterAndNulls() throws TemplateModelException {
Set set = new HashSet();
set.add(null);
{
DefaultObjectWrapper dow = new DefaultObjectWrapper(Configuration.VERSION_2_3_22);
dow.setForceLegacyNonListCollections(false);
TemplateCollectionModelEx coll = (TemplateCollectionModelEx) dow.wrap(set);
assertEquals(1, coll.size());
assertFalse(coll.isEmpty());
assertNull(coll.iterator().next());
}
{
DefaultObjectWrapper dow = new DefaultObjectWrapper(Configuration.VERSION_2_3_22);
dow.setForceLegacyNonListCollections(false);
dow.setNullModel(NullModel.INSTANCE);
TemplateCollectionModelEx coll = (TemplateCollectionModelEx) dow.wrap(set);
assertEquals(1, coll.size());
assertFalse(coll.isEmpty());
assertEquals(NullModel.INSTANCE, coll.iterator().next());
}
}
@Test
public void testLegacyNonListCollectionWrapping() throws TemplateModelException {
Set set = new TreeSet();
set.add("a");
set.add("b");
set.add("c");
TemplateSequenceModel seq = (TemplateSequenceModel) OW22.wrap(set);
assertTrue(seq instanceof SimpleSequence);
assertEquals(3, seq.size());
assertEquals("a", OW22.unwrap(seq.get(0)));
assertEquals("b", OW22.unwrap(seq.get(1)));
assertEquals("c", OW22.unwrap(seq.get(2)));
}
@Test
public void testIteratorWrapping() throws TemplateModelException, ClassNotFoundException {
testIteratorWrapping(OW0, SimpleCollection.class, Class.forName("freemarker.ext.beans.SetAdapter"));
testIteratorWrapping(OW22, DefaultIteratorAdapter.class, Iterator.class);
}
private void testIteratorWrapping(DefaultObjectWrapper ow, Class<?> expectedTMClass, Class<?> expectedPOJOClass)
throws TemplateModelException {
final List<String> list = ImmutableList.<String> of("a", "b", "c");
Iterator<String> it = list.iterator();
TemplateCollectionModel coll = (TemplateCollectionModel) ow.wrap(it);
assertRoundtrip(ow, coll, expectedTMClass, expectedPOJOClass, null);
TemplateModelIterator itIt = coll.iterator();
TemplateModelIterator itIt2 = coll.iterator(); // used later
assertTrue(itIt.hasNext());
assertEquals("a", ow.unwrap(itIt.next()));
assertTrue(itIt.hasNext());
assertEquals("b", ow.unwrap(itIt.next()));
assertTrue(itIt.hasNext());
assertEquals("c", ow.unwrap(itIt.next()));
assertFalse(itIt.hasNext());
try {
itIt.next();
fail();
} catch (TemplateModelException e) {
assertThat(e.getMessage(), containsStringIgnoringCase("no more"));
}
try {
itIt2.hasNext();
fail();
} catch (TemplateModelException e) {
assertThat(e.getMessage(), containsString("can be listed only once"));
}
TemplateModelIterator itIt3 = coll.iterator();
try {
itIt3.hasNext();
fail();
} catch (TemplateModelException e) {
assertThat(e.getMessage(), containsString("can be listed only once"));
}
}
@SuppressWarnings("boxing")
@Test
public void testCharKeyFallback() throws TemplateModelException {
Map hashMapS = new HashMap<String, Integer>();
hashMapS.put("a", 1);
Map sortedMapS = new TreeMap<String, Integer>();
sortedMapS.put("a", 1);
Map hashMapC = new HashMap<Character, Integer>();
hashMapC.put('a', 1);
Map sortedMapC = new TreeMap<Character, Integer>();
sortedMapC.put('a', 1);
for (DefaultObjectWrapper ow : new DefaultObjectWrapper[] { OW0, OW22 } ) {
assertEquals(1, ow.unwrap(((TemplateHashModel) ow.wrap(hashMapS)).get("a")));
assertEquals(1, ow.unwrap(((TemplateHashModel) ow.wrap(hashMapC)).get("a")));
assertEquals(1, ow.unwrap(((TemplateHashModel) ow.wrap(sortedMapS)).get("a")));
try {
((TemplateHashModel) ow.wrap(sortedMapC)).get("a");
} catch (TemplateModelException e) {
assertThat(e.getMessage(), containsStringIgnoringCase("String key"));
}
assertNull(((TemplateHashModel) ow.wrap(hashMapS)).get("b"));
assertNull(((TemplateHashModel) ow.wrap(hashMapC)).get("b"));
assertNull(((TemplateHashModel) ow.wrap(sortedMapS)).get("b"));
try {
((TemplateHashModel) ow.wrap(sortedMapC)).get("b");
} catch (TemplateModelException e) {
assertThat(e.getMessage(), containsStringIgnoringCase("String key"));
}
}
}
@Test
public void assertCanWrapDOM() 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);
assertTrue(OW22.wrap(doc) instanceof TemplateNodeModel);
}
private void assertSizeThroughAPIModel(int expectedSize, TemplateModel normalModel) throws TemplateModelException {
if (!(normalModel instanceof TemplateModelWithAPISupport)) {
fail();
}
TemplateHashModel apiModel = (TemplateHashModel) ((TemplateModelWithAPISupport) normalModel).getAPI();
TemplateMethodModelEx sizeMethod = (TemplateMethodModelEx) apiModel.get("size");
TemplateNumberModel r = (TemplateNumberModel) sizeMethod.exec(Collections.emptyList());
assertEquals(expectedSize, r.getAsNumber().intValue());
}
public static class RoundtripTesterBean {
public Class getClass(Object o) {
return o.getClass();
}
public String toString(Object o) {
return o.toString();
}
}
private static final class NullModel implements TemplateModel, AdapterTemplateModel {
final static NullModel INSTANCE = new NullModel();
public Object getAdaptedObject(Class hint) {
return null;
}
}
private static class Tupple<E1, E2> {
private final E1 e1;
private final E2 e2;
public Tupple(E1 e1, E2 e2) {
if (e1 == null || e2 == null) throw new NullPointerException();
this.e1 = e1;
this.e2 = e2;
}
public E1 getE1() {
return e1;
}
public E2 getE2() {
return e2;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((e1 == null) ? 0 : e1.hashCode());
result = prime * result + ((e2 == null) ? 0 : e2.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Tupple other = (Tupple) obj;
if (e1 == null) {
if (other.e1 != null) return false;
} else if (!e1.equals(other.e1)) return false;
if (e2 == null) {
if (other.e2 != null) return false;
} else if (!e2.equals(other.e2)) return false;
return true;
}
}
@SuppressWarnings("boxing")
public static class TestBean {
public int getX() {
return 1;
}
public List<Integer> m() {
return Collections.singletonList(1);
}
public Tupple incTupple(Tupple<Integer, Integer> tupple) {
return new Tupple(tupple.e1 + 1, tupple.e2 + 1);
}
}
private static class CustomizedDefaultObjectWrapper extends DefaultObjectWrapper {
private CustomizedDefaultObjectWrapper(Version incompatibleImprovements) {
super(incompatibleImprovements);
}
@Override
protected TemplateModel handleUnknownType(final Object obj) throws TemplateModelException {
if (obj instanceof Tupple) {
return new TuppleAdapter((Tupple<?, ?>) obj, this);
}
return super.handleUnknownType(obj);
}
}
private static class TuppleAdapter extends WrappingTemplateModel implements TemplateSequenceModel,
AdapterTemplateModel {
private final Tupple<?, ?> tupple;
public TuppleAdapter(Tupple<?, ?> tupple, ObjectWrapper ow) {
super(ow);
this.tupple = tupple;
}
public int size() throws TemplateModelException {
return 2;
}
public TemplateModel get(int index) throws TemplateModelException {
switch (index) {
case 0: return wrap(tupple.getE1());
case 1: return wrap(tupple.getE2());
default: return null;
}
}
public Object getAdaptedObject(Class hint) {
return tupple;
}
};
}