Backported more 2.3.20 compatibility tests
diff --git a/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java b/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
index b9c6bab..a041ac3 100644
--- a/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
+++ b/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
@@ -80,6 +80,7 @@
import freemarker.ext.beans.ResourceBundleModel;
import freemarker.ext.dom.NodeModel;
import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
import freemarker.template.SimpleCollection;
import freemarker.template.SimpleDate;
import freemarker.template.SimpleNumber;
@@ -367,6 +368,7 @@
else if (testName.startsWith("overloaded-methods-2-")) {
dataModel.put("obj", new OverloadedMethods2());
+ dataModel.put("dow", Boolean.valueOf(conf.getObjectWrapper() instanceof DefaultObjectWrapper));
}
else if (testName.startsWith("overloaded-methods-")) {
diff --git a/src/test/java/freemarker/test/templatesuite/models/AllTemplateModels.java b/src/test/java/freemarker/test/templatesuite/models/AllTemplateModels.java
new file mode 100644
index 0000000..7a7e650
--- /dev/null
+++ b/src/test/java/freemarker/test/templatesuite/models/AllTemplateModels.java
@@ -0,0 +1,94 @@
+package freemarker.test.templatesuite.models;
+
+import java.util.Date;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateBooleanModel;
+import freemarker.template.TemplateCollectionModel;
+import freemarker.template.TemplateDateModel;
+import freemarker.template.TemplateHashModelEx;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateModelIterator;
+import freemarker.template.TemplateNumberModel;
+import freemarker.template.TemplateScalarModel;
+import freemarker.template.TemplateSequenceModel;
+
+/**
+ * Implements all template models that are interesting when calling overloaded Java methods.
+ */
+public class AllTemplateModels implements
+ TemplateScalarModel, TemplateNumberModel, TemplateDateModel, TemplateBooleanModel,
+ TemplateHashModelEx, TemplateSequenceModel, TemplateCollectionModel {
+
+ public static final AllTemplateModels INSTANCE = new AllTemplateModels();
+
+ private final TemplateModelIterator EMPTY_ITERATOR = new TemplateModelIterator() {
+
+ public TemplateModel next() throws TemplateModelException {
+ return null;
+ }
+
+ public boolean hasNext() throws TemplateModelException {
+ return false;
+ }
+
+ };
+
+ private final TemplateCollectionModel EMPTY_COLLECTION = new TemplateCollectionModel() {
+
+ public TemplateModelIterator iterator() throws TemplateModelException {
+ return EMPTY_ITERATOR;
+ }
+ };
+
+ public TemplateModel get(String key) throws TemplateModelException {
+ return new SimpleScalar("value for key " + key);
+ }
+
+ public boolean isEmpty() throws TemplateModelException {
+ return true;
+ }
+
+ public TemplateModelIterator iterator() throws TemplateModelException {
+ return EMPTY_ITERATOR;
+ }
+
+ public TemplateModel get(int index) throws TemplateModelException {
+ return null;
+ }
+
+ public int size() throws TemplateModelException {
+ return 0;
+ }
+
+ public TemplateCollectionModel keys() throws TemplateModelException {
+ return EMPTY_COLLECTION;
+ }
+
+ public TemplateCollectionModel values() throws TemplateModelException {
+ return EMPTY_COLLECTION;
+ }
+
+ public boolean getAsBoolean() throws TemplateModelException {
+ return true;
+ }
+
+ public Date getAsDate() throws TemplateModelException {
+ return new Date(0);
+ }
+
+ public int getDateType() {
+ return TemplateDateModel.DATETIME;
+ }
+
+ @SuppressWarnings("boxing")
+ public Number getAsNumber() throws TemplateModelException {
+ return 1;
+ }
+
+ public String getAsString() throws TemplateModelException {
+ return "s";
+ }
+
+}
diff --git a/src/test/java/freemarker/test/templatesuite/models/BooleanAndScalarModel.java b/src/test/java/freemarker/test/templatesuite/models/BooleanAndScalarModel.java
new file mode 100644
index 0000000..37994f9
--- /dev/null
+++ b/src/test/java/freemarker/test/templatesuite/models/BooleanAndScalarModel.java
@@ -0,0 +1,19 @@
+package freemarker.test.templatesuite.models;
+
+import freemarker.template.TemplateBooleanModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateScalarModel;
+
+public class BooleanAndScalarModel implements TemplateBooleanModel, TemplateScalarModel {
+
+ public static final BooleanAndScalarModel INSTANCE = new BooleanAndScalarModel();
+
+ public String getAsString() throws TemplateModelException {
+ return "s";
+ }
+
+ public boolean getAsBoolean() throws TemplateModelException {
+ return true;
+ }
+
+}
diff --git a/src/test/java/freemarker/test/templatesuite/models/HashAndScalarModel.java b/src/test/java/freemarker/test/templatesuite/models/HashAndScalarModel.java
new file mode 100644
index 0000000..2ab79af
--- /dev/null
+++ b/src/test/java/freemarker/test/templatesuite/models/HashAndScalarModel.java
@@ -0,0 +1,56 @@
+package freemarker.test.templatesuite.models;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateCollectionModel;
+import freemarker.template.TemplateHashModelEx;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateModelIterator;
+import freemarker.template.TemplateScalarModel;
+
+public class HashAndScalarModel implements TemplateHashModelEx, TemplateScalarModel {
+
+ public static final HashAndScalarModel INSTANCE = new HashAndScalarModel();
+
+ private final TemplateCollectionModel EMPTY_COLLECTION = new TemplateCollectionModel() {
+
+ public TemplateModelIterator iterator() throws TemplateModelException {
+ return new TemplateModelIterator() {
+
+ public TemplateModel next() throws TemplateModelException {
+ return null;
+ }
+
+ public boolean hasNext() throws TemplateModelException {
+ return false;
+ }
+
+ };
+ }
+ };
+
+ public String getAsString() throws TemplateModelException {
+ return "scalarValue";
+ }
+
+ public TemplateModel get(String key) throws TemplateModelException {
+ return new SimpleScalar("mapValue for " + key);
+ }
+
+ public boolean isEmpty() throws TemplateModelException {
+ return true;
+ }
+
+ public int size() throws TemplateModelException {
+ return 0;
+ }
+
+ public TemplateCollectionModel keys() throws TemplateModelException {
+ return EMPTY_COLLECTION;
+ }
+
+ public TemplateCollectionModel values() throws TemplateModelException {
+ return EMPTY_COLLECTION;
+ }
+
+}
diff --git a/src/test/java/freemarker/test/templatesuite/models/OverloadedMethods2.java b/src/test/java/freemarker/test/templatesuite/models/OverloadedMethods2.java
index 995828e..26c99ab 100644
--- a/src/test/java/freemarker/test/templatesuite/models/OverloadedMethods2.java
+++ b/src/test/java/freemarker/test/templatesuite/models/OverloadedMethods2.java
@@ -4,14 +4,25 @@
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import freemarker.core.Environment;
import freemarker.ext.beans.RationalNumber;
import freemarker.ext.util.WrapperTemplateModel;
import freemarker.template.AdapterTemplateModel;
+import freemarker.template.ObjectWrapper;
import freemarker.template.TemplateBooleanModel;
+import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNumberModel;
import freemarker.template.utility.StringUtil;
+import freemarker.test.utility.Helpers;
public class OverloadedMethods2 {
@@ -293,107 +304,80 @@
}
public String varargs1(String s, int... xs) {
- return "varargs1(String s = " + StringUtil.jQuote(s) + ", int... xs = [" + arrayToString(xs) + "])";
+ return "varargs1(String s = " + StringUtil.jQuote(s) + ", int... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs1(String s, double... xs) {
- return "varargs1(String s = " + StringUtil.jQuote(s) + ", double... xs = [" + arrayToString(xs) + "])";
+ return "varargs1(String s = " + StringUtil.jQuote(s) + ", double... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs1(String s, Object... xs) {
- return "varargs1(String s = " + StringUtil.jQuote(s) + ", Object... xs = [" + arrayToString(xs) + "])";
+ return "varargs1(String s = " + StringUtil.jQuote(s) + ", Object... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs1(Object s, Object... xs) {
- return "varargs1(Object s = " + s + ", Object... xs = [" + arrayToString(xs) + "])";
+ return "varargs1(Object s = " + s + ", Object... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs2(int... xs) {
- return "varargs2(int... xs = [" + arrayToString(xs) + "])";
+ return "varargs2(int... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs2(double... xs) {
- return "varargs2(double... xs = [" + arrayToString(xs) + "])";
+ return "varargs2(double... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs3(String... xs) {
- return "varargs3(String... xs = [" + arrayToString(xs) + "])";
+ return "varargs3(String... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs3(Comparable... xs) {
- return "varargs3(Comparable... xs = [" + arrayToString(xs) + "])";
+ return "varargs3(Comparable... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs3(Object... xs) {
- return "varargs3(Object... xs = [" + arrayToString(xs) + "])";
+ return "varargs3(Object... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs4(Integer... xs) {
- return "varargs4(Integer... xs = [" + arrayToString(xs) + "])";
+ return "varargs4(Integer... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs4(int... xs) {
- return "varargs4(int... xs = [" + arrayToString(xs) + "])";
+ return "varargs4(int... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs5(int... xs) {
- return "varargs5(int... xs = [" + arrayToString(xs) + "])";
+ return "varargs5(int... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs5(int a1, int... xs) {
- return "varargs5(int a1 = " + a1 + ", int... xs = [" + arrayToString(xs) + "])";
+ return "varargs5(int a1 = " + a1 + ", int... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs5(int a1, int a2, int... xs) {
- return "varargs5(int a1 = " + a1 + ", int a2 = " + a2 + ", int... xs = [" + arrayToString(xs) + "])";
+ return "varargs5(int a1 = " + a1 + ", int a2 = " + a2 + ", int... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs5(int a1, int a2, int a3, int... xs) {
return "varargs5(int a1 = " + a1 + ", int a2 = " + a2 + ", int a3 = " + a3
- + ", int... xs = [" + arrayToString(xs) + "])";
+ + ", int... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs6(String a1, int... xs) {
- return "varargs6(String a1 = " + a1 + ", int... xs = [" + arrayToString(xs) + "])";
+ return "varargs6(String a1 = " + a1 + ", int... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs6(Object a1, int a2, int... xs) {
- return "varargs6(Object a1 = " + a1 + ", int a2 = " + a2 + ", int... xs = [" + arrayToString(xs) + "])";
+ return "varargs6(Object a1 = " + a1 + ", int a2 = " + a2 + ", int... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs7(int... xs) {
- return "varargs7(int... xs = [" + arrayToString(xs) + "])";
+ return "varargs7(int... xs = " + Helpers.arrayToString(xs) + ")";
}
public String varargs7(short a1, int... xs) {
- return "varargs7(short a1 = " + a1 + ", int... xs = [" + arrayToString(xs) + "])";
- }
-
- private String arrayToString(int[] xs) {
- StringBuilder sb = new StringBuilder();
- for (int x : xs) {
- if (sb.length() != 0) sb.append(", ");
- sb.append(x);
- }
- return sb.toString();
- }
-
- private String arrayToString(double[] xs) {
- StringBuilder sb = new StringBuilder();
- for (double x : xs) {
- if (sb.length() != 0) sb.append(", ");
- sb.append(x);
- }
- return sb.toString();
- }
-
- private String arrayToString(Object[] xs) {
- StringBuilder sb = new StringBuilder();
- for (Object x : xs) {
- if (sb.length() != 0) sb.append(", ");
- sb.append(x);
- }
- return sb.toString();
+ return "varargs7(short a1 = " + a1 + ", int... xs = " + Helpers.arrayToString(xs) + ")";
}
public String mNullAmbiguous(String s) {
@@ -429,11 +413,11 @@
}
public String mVarargsIgnoredTail(int i, double... ds) {
- return "mVarargsIgnoredTail(int i = " + i + ", double... ds = [" + arrayToString(ds) + "])";
+ return "mVarargsIgnoredTail(int i = " + i + ", double... ds = " + Helpers.arrayToString(ds) + ")";
}
public String mVarargsIgnoredTail(int... is) {
- return "mVarargsIgnoredTail(int... is = [" + arrayToString(is) + "])";
+ return "mVarargsIgnoredTail(int... is = " + Helpers.arrayToString(is) + ")";
}
public String mLowRankWins(int x, int y, Object o) {
@@ -471,6 +455,604 @@
return new File("file");
}
+ public String mSeqToArrayNonOverloaded(String[] items, String s) {
+ return "mSeqToArrayNonOverloaded(String[] " + arrayToString(items) + ", String " + s + ")";
+ }
+
+ public String mSeqToArrayGoodHint(String[] items, String s) {
+ return "mSeqToArrayGoodHint(String[] " + arrayToString(items) + ", String " + s + ")";
+ }
+
+ public String mSeqToArrayGoodHint(String[] items, int i) {
+ return "mSeqToArrayGoodHint(String[] " + arrayToString(items) + ", int " + i + ")";
+ }
+
+ public String mSeqToArrayGoodHint2(String[] items, String s) {
+ return "mSeqToArrayGoodHint2(String[] " + arrayToString(items) + ", String " + s + ")";
+ }
+
+ public String mSeqToArrayGoodHint2(String item) {
+ return "mSeqToArrayGoodHint2(String " + item + ")";
+ }
+
+ public String mSeqToArrayPoorHint(String[] items, String s) {
+ return "mSeqToArrayPoorHint(String[] " + arrayToString(items) + ", String " + s + ")";
+ }
+
+ public String mSeqToArrayPoorHint(String item, int i) {
+ return "mSeqToArrayPoorHint(String " + item + ", int " + i + ")";
+ }
+
+ public String mSeqToArrayPoorHint2(String[] items) {
+ return "mSeqToArrayPoorHint2(String[] " + arrayToString(items) + ")";
+ }
+
+ public String mSeqToArrayPoorHint2(String item) {
+ return "mSeqToArrayPoorHint2(String " + item + ")";
+ }
+
+ public String mSeqToArrayPoorHint3(String[] items) {
+ return "mSeqToArrayPoorHint3(String[] " + arrayToString(items) + ")";
+ }
+
+ public String mSeqToArrayPoorHint3(int[] items) {
+ return "mSeqToArrayPoorHint3(int[] " + arrayToString(items) + ")";
+ }
+
+ public String mStringArrayVsListPreference(String[] items) {
+ return "mStringArrayVsListPreference(String[] " + arrayToString(items) + ")";
+ }
+
+ public String mStringArrayVsListPreference(List items) {
+ return "mStringArrayVsListPreference(List " + listToString(items) + ")";
+ }
+
+ public String mStringArrayVsObjectArrayPreference(String[] items) {
+ return "mStringArrayVsObjectArrayPreference(String[] " + arrayToString(items) + ")";
+ }
+
+ public String mStringArrayVsObjectArrayPreference(Object[] items) {
+ return "mStringArrayVsObjectArrayPreference(Object[] " + arrayToString(items) + ")";
+ }
+
+ public String mIntArrayVsIntegerArrayPreference(int[] items) {
+ return "mIntArrayVsIntegerArrayPreference(int[] " + arrayToString(items) + ")";
+ }
+
+ public String mIntArrayVsIntegerArrayPreference(Integer[] items) {
+ return "mIntArrayVsIntegerArrayPreference(Integer[] " + arrayToString(items) + ")";
+ }
+
+ public String mIntArrayNonOverloaded(int[] items) {
+ return "mIntArrayNonOverloaded(int[] " + arrayToString(items) + ")";
+ }
+
+ public String mIntegerArrayNonOverloaded(Integer[] items) {
+ return "mIntegerArrayNonOverloaded(Integer[] " + arrayToString(items) + ")";
+ }
+
+ public String mIntegerListNonOverloaded(List<Integer> items) {
+ return "mIntegerListNonOverloaded(List<Integer> " + items + ")";
+ }
+
+ public String mStringListNonOverloaded(List<String> items) {
+ return "mStringListNonOverloaded(List<String> " + items + ")";
+ }
+
+ public String mStringArrayNonOverloaded(String[] items) {
+ return "mStringArrayNonOverloaded(String[] " + arrayToString(items) + ")";
+ }
+
+ public String mObjectListNonOverloaded(List<Object> items) {
+ return "mObjectListNonOverloaded(List<Object> " + items + ")";
+ }
+
+ public String mObjectArrayNonOverloaded(Object[] items) {
+ return "mObjectArrayNonOverloaded(Object[] " + arrayToString(items) + ")";
+ }
+
+ public String mIntegerArrayOverloaded(Integer[] items, int i) {
+ return "mIntegerArrayOverloaded(Integer[] " + arrayToString(items) + ", int " + i + ")";
+ }
+
+ public String mIntegerArrayOverloaded(Object obj, boolean b) {
+ return "mIntegerArrayOverloaded(Object " + obj + ", boolean " + b + ")";
+ }
+
+ public String mStringArrayOverloaded(String[] items, int i) {
+ return "mStringArrayOverloaded(String[] " + arrayToString(items) + ", int " + i + ")";
+ }
+
+ public String mStringArrayOverloaded(Object obj, boolean b) {
+ return "mStringArrayOverloaded(Object " + obj + ", boolean " + b + ")";
+ }
+
+ public String mCharArrayOverloaded(char[] items, int i) {
+ return "mCharArrayOverloaded(char[] " + arrayToString(items) + ", int " + i + ")";
+ }
+
+ public String mCharArrayOverloaded(Character[] items, String s) {
+ return "mCharArrayOverloaded(Character[] " + arrayToString(items) + ", String " + s + ")";
+ }
+
+ public String mCharArrayOverloaded(Object obj, boolean b) {
+ return "mCharArrayOverloaded(Object " + obj + ", boolean " + b + ")";
+ }
+
+ public String mStringArrayArrayOverloaded(String[][] arrayArray, int i) {
+ return "mStringArrayArrayOverloaded(String[][] " + arrayToString(arrayArray) + ", int " + i + ")";
+ }
+
+ public String mStringArrayArrayOverloaded(Object obj, boolean b) {
+ return "mStringArrayArrayOverloaded(Object " + obj + ", boolean " + b + ")";
+ }
+
+ public String mIntArrayArrayOverloaded(int[][] xss) {
+ return "mIntArrayArrayOverloaded(" + arrayToString(xss) + ")";
+ }
+
+ public String mArrayOfListsOverloaded(List[] xss) {
+ return "mArrayOfListsOverloaded(" + arrayToString(xss) + ")";
+ }
+
+ public String mArrayOfListsOverloaded(String x) {
+ return "mArrayOfListsOverloaded(" + x + ")";
+ }
+
+ public String mIntArrayArrayOverloaded(String s) {
+ return "mIntArrayArrayOverloaded(" + s + ")";
+ }
+
+ public String mStringArrayVarargsNonOverloaded(String... items) {
+ return "mStringArrayVarargsNonOverloaded(String[] " + arrayToString(items) + ")";
+ }
+
+ public String mStringArrayVarargsOverloaded(String... items) {
+ return "mStringArrayVarargsNonOverloaded(String[] " + arrayToString(items) + ")";
+ }
+
+ public String mStringArrayVarargsOverloaded1(String... items) {
+ return "mStringArrayVarargsOverloaded1(String[] " + arrayToString(items) + ")";
+ }
+
+ public String mStringArrayVarargsOverloaded1(List<String> items) {
+ return "mStringArrayVarargsOverloaded1(List " + listToString(items) + ")";
+ }
+
+ public String mStringArrayVarargsOverloaded2(String... items) {
+ return "mStringArrayVarargsOverloaded2(String[] " + arrayToString(items) + ")";
+ }
+
+ public String mStringArrayVarargsOverloaded2(String item) {
+ return "mStringArrayVarargsOverloaded2(String " + item + ")";
+ }
+
+ public String mStringArrayVarargsOverloaded3(String... items) {
+ return "mStringArrayVarargsOverloaded3(String[] " + arrayToString(items) + ")";
+ }
+
+ public String mStringArrayVarargsOverloaded3(String item1, String item2) {
+ return "mStringArrayVarargsOverloaded3(String " + item1 + ", String " + item2 + ")";
+ }
+
+ public String mStringArrayVarargsOverloaded4(String... items) {
+ return "mStringArrayVarargsOverloaded4(String[] " + arrayToString(items) + ")";
+ }
+
+ public String mStringArrayVarargsOverloaded4(List... items) {
+ return "mStringArrayVarargsOverloaded4(List[] " + arrayToString(items) + ")";
+ }
+
+ public String mListOrString(List<String> items) {
+ return "mListOrString(List " + listToString(items) + ")";
+ }
+
+ public String mListOrString(String item) {
+ return "mListOrString(String " + item + ")";
+ }
+
+ public String mListListOrString(List<List<Object>> items) {
+ return "mListListOrString(List " + listToString(items) + ")";
+ }
+
+ public String mListListOrString(String item) {
+ return "mListListOrString(String " + item + ")";
+ }
+
+ public String mMapOrBoolean(Map v) {
+ return "mMapOrBoolean(Map " + v +")";
+ }
+
+ public String mMapOrBoolean(boolean v) {
+ return "mMapOrBoolean(boolean " + v + ")";
+ }
+
+ public String mMapOrBooleanVarargs(Map... v) {
+ return "mMapOrBooleanVarargs(Map... " + arrayToString(v) +")";
+ }
+
+ public String mMapOrBooleanVarargs(boolean... v) {
+ return "mMapOrBooleanVarargs(boolean... " + arrayToString(v) + ")";
+ }
+
+ public String mMapOrBooleanFixedAndVarargs(Map v) {
+ return "mMapOrBooleanFixedAndVarargs(Map " + v +")";
+ }
+
+ public String mMapOrBooleanFixedAndVarargs(boolean v) {
+ return "mMapOrBooleanFixedAndVarargs(boolean " + v + ")";
+ }
+
+ public String mMapOrBooleanFixedAndVarargs(Map... v) {
+ return "mMapOrBooleanFixedAndVarargs(Map... " + arrayToString(v) +")";
+ }
+
+ public String mMapOrBooleanFixedAndVarargs(boolean... v) {
+ return "mMapOrBooleanFixedAndVarargs(boolean... " + arrayToString(v) + ")";
+ }
+
+ public String mNumberOrArray(Number v) {
+ return "mNumberOrArray(Number " + v + ")";
+ }
+
+ public String mNumberOrArray(Object[] v) {
+ return "mNumberOrArray(Object[] " + arrayToString(v) + ")";
+ }
+
+ public String mIntOrArray(int v) {
+ return "mIntOrArray(int " + v + ")";
+ }
+
+ public String mIntOrArray(Object[] v) {
+ return "mIntOrArray(Object[] " + arrayToString(v) + ")";
+ }
+
+ public String mDateOrArray(Date v) {
+ return "mDateOrArray(Date " + v.getTime() + ")";
+ }
+
+ public String mDateOrArray(Object[] v) {
+ return "mDateOrArray(Object[] " + arrayToString(v) + ")";
+ }
+
+ public String mStringOrArray(String v) {
+ return "mStringOrArray(String " + v + ")";
+ }
+
+ public String mStringOrArray(Object[] v) {
+ return "mStringOrArray(Object[] " + arrayToString(v) + ")";
+ }
+
+ public String mBooleanOrArray(boolean v) {
+ return "mBooleanOrArray(boolean " + v + ")";
+ }
+
+ public String mBooleanOrArray(Object[] v) {
+ return "mBooleanOrArray(Object[] " + arrayToString(v) + ")";
+ }
+
+ public String mMapOrArray(Map v) {
+ return "mMapOrArray(Map " + v + ")";
+ }
+
+ public String mMapOrArray(Object[] v) {
+ return "mMapOrArray(Object[] " + arrayToString(v) + ")";
+ }
+
+ public String mListOrArray(List v) {
+ return "mListOrArray(List " + v + ")";
+ }
+
+ public String mListOrArray(Object[] v) {
+ return "mListOrArray(Object[] " + arrayToString(v) + ")";
+ }
+
+ public String mSetOrArray(Set v) {
+ return "mSetOrArray(Set " + v + ")";
+ }
+
+ public String mSetOrArray(Object[] v) {
+ return "mSetOrArray(Object[] " + arrayToString(v) + ")";
+ }
+
+ public String mCharNonOverloaded(char c) {
+ return "mCharNonOverloaded(char " + c + ")";
+ }
+
+ public String mCharacterNonOverloaded(Character c) {
+ return "mCharacterNonOverloaded(Character " + c + ")";
+ }
+
+ public String mCharOrCharacterOverloaded(char c) {
+ return "mCharOrCharacterOverloaded(char " + c + ")";
+ }
+
+ public String mCharOrCharacterOverloaded(Character c) {
+ return "mCharOrCharacterOverloaded(Character " + c + ")";
+ }
+
+ public String mCharOrBooleanOverloaded(char c) {
+ return "mCharOrBooleanOverloaded(char " + c + ")";
+ }
+
+ public String mCharOrBooleanOverloaded(boolean b) {
+ return "mCharOrBooleanOverloaded(boolean " + b + ")";
+ }
+
+ public String mCharOrStringOverloaded(char c, boolean b) {
+ return "mCharOrStringOverloaded(char " + c + ", boolean " + b + ")";
+ }
+
+ public String mCharOrStringOverloaded(String s, int i) {
+ return "mCharOrStringOverloaded(String " + s + ", int " + i + ")";
+ }
+
+ public String mCharacterOrStringOverloaded(Character c, boolean b) {
+ return "mCharacterOrStringOverloaded(Character " + c + ", boolean " + b + ")";
+ }
+
+ public String mCharacterOrStringOverloaded(String s, int i) {
+ return "mCharacterOrStringOverloaded(String " + s + ", int " + i + ")";
+ }
+
+ public String mCharOrStringOverloaded2(String s) {
+ return "mCharOrStringOverloaded2(String " + s + ")";
+ }
+
+ public String mCharOrStringOverloaded2(char c) {
+ return "mCharOrStringOverloaded2(char " + c + ")";
+ }
+
+ public String mCharacterOrStringOverloaded2(String s) {
+ return "mCharacterOrStringOverloaded2(String " + s + ")";
+ }
+
+ public String mCharacterOrStringOverloaded2(Character c) {
+ return "mCharacterOrStringOverloaded2(Character " + c + ")";
+ }
+
+
+ public String getJavaString() {
+ return "s";
+ }
+
+ public List getJavaStringList() {
+ List list = new ArrayList();
+ list.add("a");
+ list.add("b");
+ return list;
+ }
+
+ public List getJavaString2List() {
+ List list = new ArrayList();
+ list.add("aa");
+ list.add("bb");
+ return list;
+ }
+
+ public List getJavaStringListList() {
+ List listList = new ArrayList();
+ {
+ List list = new ArrayList();
+ list.add("a");
+ list.add("b");
+
+ listList.add(list);
+ }
+ {
+ List list = new ArrayList();
+ list.add("c");
+
+ listList.add(list);
+ }
+ return listList;
+ }
+
+ public List getJavaStringSequenceList() throws TemplateModelException {
+ ObjectWrapper ow = Environment.getCurrentEnvironment().getObjectWrapper();
+
+ List listList = new ArrayList();
+ {
+ List list = new ArrayList();
+ list.add("a");
+ list.add("b");
+
+ listList.add(ow.wrap(list));
+ }
+ {
+ List list = new ArrayList();
+ list.add("c");
+
+ listList.add(ow.wrap(list));
+ }
+ return listList;
+ }
+
+ public List<int[]> getJavaListOfIntArrays() {
+ List list = new ArrayList();
+ list.add(new int[] {1, 2, 3});
+ list.add(new int[] {});
+ list.add(new int[] {4});
+ return list;
+ }
+
+ @SuppressWarnings("boxing")
+ public List getJavaIntegerListList() {
+ List listList = new ArrayList();
+ {
+ List list = new ArrayList();
+ list.add(1);
+ list.add(2);
+
+ listList.add(list);
+ }
+ {
+ List list = new ArrayList();
+ list.add(3);
+
+ listList.add(list);
+ }
+ return listList;
+ }
+
+ @SuppressWarnings("boxing")
+ public List<Integer> getJavaIntegerList() {
+ List<Integer> list = new ArrayList<Integer>();
+ list.add(1);
+ list.add(2);
+ return list;
+ }
+
+ @SuppressWarnings("boxing")
+ public List<Byte> getJavaByteList() {
+ List<Byte> list = new ArrayList<Byte>();
+ list.add((byte) 1);
+ list.add((byte) 2);
+ return list;
+ }
+
+ @SuppressWarnings("boxing")
+ public List<Character> getJavaCharacterList() {
+ List<Character> list = new ArrayList<Character>();
+ list.add('c');
+ list.add('C');
+ return list;
+ }
+
+ public String[] getJavaStringArray() {
+ return new String[] { "a", "b" };
+ }
+
+ public int[] getJavaIntArray() {
+ return new int[] { 11, 22 };
+ }
+
+ public Integer[] getJavaIntegerArray() {
+ return new Integer[] { Integer.valueOf(11), Integer.valueOf(22) };
+ }
+
+ public String[] getJavaEmptyStringArray() {
+ return new String[] { };
+ }
+
+ public String[][] getJavaStringArrayArray() {
+ return new String[][] { new String[] { "a", "b" }, new String[] { }, new String[] { "c" } };
+ }
+
+ public Object[] getJavaObjectArray() {
+ return new Object[] { "a", "b" };
+ }
+
+ public TemplateModel getHashAndScalarModel() {
+ return HashAndScalarModel.INSTANCE;
+ }
+
+ public TemplateModel getBooleanAndScalarModel() {
+ return BooleanAndScalarModel.INSTANCE;
+ }
+
+ public TemplateModel getAllModels() {
+ return AllTemplateModels.INSTANCE;
+ }
+
+ private String arrayToString(Object[] array) {
+ return array != null ? listToString(Arrays.asList(array)) : "null";
+ }
+
+ private String arrayToString(Object[][] arrayArray) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ boolean first = true;
+ for (Object[] array : arrayArray) {
+ if (!first) {
+ sb.append(", ");
+ } else {
+ first = false;
+ }
+ sb.append(listToString(Arrays.asList(array)));
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private String arrayToString(int[] array) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ for (int i = 0; i < array.length; i++) {
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append(array[i]);
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private String arrayToString(int[][] xss) {
+ StringBuilder sb = new StringBuilder();
+ sb.append('[');
+ for (int i = 0; i < xss.length; i++) {
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append(arrayToString(xss[i]));
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+ private String arrayToString(char[] array) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ for (int i = 0; i < array.length; i++) {
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append(array[i]);
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private String arrayToString(boolean[] array) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ for (int i = 0; i < array.length; i++) {
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append(array[i]);
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private String collectionToString(String prefix, Collection<?> list) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(prefix);
+ sb.append("[");
+ boolean first = true;
+ for (Object item : list) {
+ if (!first) {
+ sb.append(", ");
+ } else {
+ first = false;
+ }
+ sb.append(item instanceof Object[] ? arrayToString((Object[]) item) : item);
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private String listToString(List<?> list) {
+ return collectionToString("", list);
+ }
+
+
+ private String setToString(Set<?> list) {
+ return collectionToString("Set", list);
+ }
+
public TemplateNumberModel getAdaptedNumber() {
return new MyAdapterNumberModel();
}
diff --git a/src/test/java/freemarker/test/utility/Helpers.java b/src/test/java/freemarker/test/utility/Helpers.java
new file mode 100644
index 0000000..e516fc6
--- /dev/null
+++ b/src/test/java/freemarker/test/utility/Helpers.java
@@ -0,0 +1,46 @@
+package freemarker.test.utility;
+
+public class Helpers {
+
+ private Helpers() { }
+
+ public static String arrayToString(int[] xs) {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append('[');
+ for (int x : xs) {
+ if (sb.length() != 1) sb.append(", ");
+ sb.append(x);
+ }
+ sb.append(']');
+
+ return sb.toString();
+ }
+
+ public static String arrayToString(double[] xs) {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append('[');
+ for (double x : xs) {
+ if (sb.length() != 1) sb.append(", ");
+ sb.append(x);
+ }
+ sb.append(']');
+
+ return sb.toString();
+ }
+
+ public static String arrayToString(Object[] xs) {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append('[');
+ for (Object x : xs) {
+ if (sb.length() != 1) sb.append(", ");
+ sb.append(x);
+ }
+ sb.append(']');
+
+ return sb.toString();
+ }
+
+}
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-common.ftl b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-common.ftl
new file mode 100644
index 0000000..14ef9dc
--- /dev/null
+++ b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-common.ftl
@@ -0,0 +1,71 @@
+<@assertEquals actual=obj.mStringArrayVsListPreference(obj.javaStringList) expected="mStringArrayVsListPreference(List [a, b])" />
+<@assertEquals actual=obj.mStringArrayVsListPreference(obj.javaStringArray) expected=dow?string("mStringArrayVsListPreference(List [a, b])", "mStringArrayVsListPreference(String[] [a, b])") />
+<#if dow>
+ <@assertEquals actual=obj.mStringArrayVsListPreference(obj.javaObjectArray) expected="mStringArrayVsListPreference(List [a, b])" />
+</#if>
+
+<#-- Check if non-overloaded calls still work; they share some code with overloaded methods: -->
+<@assertEquals actual=obj.mIntArrayNonOverloaded([1, 2, 3]) expected="mIntArrayNonOverloaded(int[] [1, 2, 3])" />
+<@assertEquals actual=obj.mIntegerArrayNonOverloaded([1, 2, 3]) expected="mIntegerArrayNonOverloaded(Integer[] [1, 2, 3])" />
+<@assertEquals actual=obj.mIntegerListNonOverloaded([1, 2, 3]) expected="mIntegerListNonOverloaded(List<Integer> [1, 2, 3])" />
+<@assertEquals actual=obj.mStringListNonOverloaded(['a', 'b', 'c']) expected="mStringListNonOverloaded(List<String> [a, b, c])" />
+<@assertEquals actual=obj.mStringListNonOverloaded(obj.javaStringList) expected="mStringListNonOverloaded(List<String> [a, b])" />
+<@assertEquals actual=obj.mStringListNonOverloaded(obj.javaStringArray) expected="mStringListNonOverloaded(List<String> [a, b])" />
+<@assertEquals actual=obj.mStringArrayNonOverloaded(['a', 'b', 'c']) expected="mStringArrayNonOverloaded(String[] [a, b, c])" />
+<@assertEquals actual=obj.mStringArrayNonOverloaded(obj.javaStringList) expected="mStringArrayNonOverloaded(String[] [a, b])" />
+<@assertEquals actual=obj.mStringArrayNonOverloaded(obj.javaStringArray) expected="mStringArrayNonOverloaded(String[] [a, b])" />
+<@assertEquals actual=obj.mObjectListNonOverloaded(['a', 'b', 3]) expected="mObjectListNonOverloaded(List<Object> [a, b, 3])" />
+<@assertEquals actual=obj.mObjectListNonOverloaded(obj.javaStringList) expected="mObjectListNonOverloaded(List<Object> [a, b])" />
+<@assertEquals actual=obj.mObjectListNonOverloaded(obj.javaStringArray) expected="mObjectListNonOverloaded(List<Object> [a, b])" />
+<@assertEquals actual=obj.mObjectArrayNonOverloaded(['a', 'b', 3]) expected="mObjectArrayNonOverloaded(Object[] [a, b, 3])" />
+<@assertEquals actual=obj.mObjectArrayNonOverloaded(obj.javaStringList) expected="mObjectArrayNonOverloaded(Object[] [a, b])" />
+<@assertEquals actual=obj.mObjectArrayNonOverloaded(obj.javaStringArray) expected="mObjectArrayNonOverloaded(Object[] [a, b])" />
+
+<@assertEquals actual=obj.mStringArrayVsListPreference(obj.javaStringArray) expected=dow?string("mStringArrayVsListPreference(List [a, b])", "mStringArrayVsListPreference(String[] [a, b])") />
+
+<@assertEquals actual=obj.mStringArrayVarargsNonOverloaded('a', 'b') expected="mStringArrayVarargsNonOverloaded(String[] [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsNonOverloaded(['a', 'b']) expected="mStringArrayVarargsNonOverloaded(String[] [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsNonOverloaded(obj.javaStringList) expected="mStringArrayVarargsNonOverloaded(String[] [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsNonOverloaded(obj.javaStringArray) expected="mStringArrayVarargsNonOverloaded(String[] [a, b])" />
+
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded1('a', 'b') expected="mStringArrayVarargsOverloaded1(String[] [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded1(['a', 'b']) expected="mStringArrayVarargsOverloaded1(List [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded1(obj.javaStringList) expected="mStringArrayVarargsOverloaded1(List [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded1(obj.javaStringArray) expected=dow?string("mStringArrayVarargsOverloaded1(List [a, b])", "mStringArrayVarargsOverloaded1(String[] [a, b])") />
+
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded2('a', 'b') expected="mStringArrayVarargsOverloaded2(String[] [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded2('a') expected="mStringArrayVarargsOverloaded2(String a)" />
+
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded3(['a']) expected="mStringArrayVarargsOverloaded3(String[] [a])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded3(['a', 'b']) expected="mStringArrayVarargsOverloaded3(String[] [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded3(['a', 'b', 'c']) expected="mStringArrayVarargsOverloaded3(String[] [a, b, c])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded3('a') expected="mStringArrayVarargsOverloaded3(String[] [a])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded3('a', 'b') expected="mStringArrayVarargsOverloaded3(String a, String b)" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded3('a', 'b', 'c') expected="mStringArrayVarargsOverloaded3(String[] [a, b, c])" />
+
+<@assertEquals actual=obj.mListOrString(['a', 'b']) expected="mListOrString(List [a, b])" />
+<@assertEquals actual=obj.mListOrString('a') expected="mListOrString(String a)" />
+<@assertEquals actual=obj.mListListOrString([['a'], 'b', 3]) expected="mListListOrString(List [[a], b, 3])" />
+<@assertEquals actual=obj.mListListOrString('s') expected="mListListOrString(String s)" />
+
+<#-- Because the fixed arg interpretations are ambiguous, it only considers the vararg interpretations: -->
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded4(['a', 'b', 'c']) expected="mStringArrayVarargsOverloaded4(List[] [[a, b, c]])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded4('a', 'b', 'c') expected="mStringArrayVarargsOverloaded4(String[] [a, b, c])" />
+
+<#-- Fixed arg solutions have priority: -->
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded4(obj.javaStringList) expected="mStringArrayVarargsOverloaded4(List[] [[a, b]])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded4(obj.javaStringArray) expected=dow?string("mStringArrayVarargsOverloaded4(List[] [[a, b]])", "mStringArrayVarargsOverloaded4(String[] [a, b])") />
+
+<#-- Choses between the vararg solutions: -->
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded4(obj.javaStringList, obj.javaStringList) expected="mStringArrayVarargsOverloaded4(List[] [[a, b], [a, b]])" />
+
+<#-- Until there's no overloading String->Character conversion work: -->
+<@assertEquals actual=obj.mCharNonOverloaded('c') expected="mCharNonOverloaded(char c)" />
+<@assertEquals actual=obj.mCharNonOverloaded(obj.javaString) expected="mCharNonOverloaded(char s)" />
+<@assertEquals actual=obj.mCharacterNonOverloaded('c') expected="mCharacterNonOverloaded(Character c)" />
+<@assertEquals actual=obj.mCharacterNonOverloaded(obj.javaString) expected="mCharacterNonOverloaded(Character s)" />
+
+<@assertEquals actual=obj.mCharOrStringOverloaded('s', 1) expected="mCharOrStringOverloaded(String s, int 1)" />
+<@assertEquals actual=obj.mCharacterOrStringOverloaded('s', 1) expected="mCharacterOrStringOverloaded(String s, int 1)" />
+<@assertEquals actual=obj.mCharOrStringOverloaded2('ss') expected="mCharOrStringOverloaded2(String ss)" />
+<@assertEquals actual=obj.mCharacterOrStringOverloaded2('ss') expected="mCharacterOrStringOverloaded2(String ss)" />
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-desc-ici-2.3.20.ftl b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-desc-ici-2.3.20.ftl
index 3200723..06fc12d 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-desc-ici-2.3.20.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-desc-ici-2.3.20.ftl
@@ -1,3 +1,5 @@
+<#-- Note that the point of 2.3.20 tests is to check if bugs fixed in 2.3.21 are still emulated in pre-2.3.21 mode -->
+
<@assertFails message="no compatible overloaded">${obj.mVarargs('a', obj.getNnS('b'), obj.getNnS('c'))}</@>
<@assertFails message="no compatible overloaded">${obj.mChar('a')}</@>
<@assertFails message="no compatible overloaded">${obj.mIntPrimVSBoxed(123?long)}</@>
@@ -17,4 +19,7 @@
<@assertFails message="no compatible overloaded">${obj.mRareWrappings(obj.adaptedNumber, 0, 0, 0, !obj.booleanWrappedAsAnotherBoolean)}</@>
<@assertFails message="no compatible overloaded">${obj.mRareWrappings(obj.booleanWrappedAsAnotherBoolean, 0, 0, 0, !obj.stringAdaptedToBoolean)}</@>
+<@assertFails message="no compatible overloaded">${obj.mCharOrCharacterOverloaded('c')}</@>
+<@assertFails message="no compatible overloaded">${obj.mCharOrCharacterOverloaded(obj.javaString)}</@>
+
<#include 'overloaded-methods-2-ici-2.3.20.ftl'>
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-ici-2.3.20.ftl b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-ici-2.3.20.ftl
index 2a1c34e..cb59514 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-ici-2.3.20.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-ici-2.3.20.ftl
@@ -1,4 +1,7 @@
<#-- The parts of the IcI 2.3.20 tests that give the same result regardless of method introspection order -->
+<#-- Note that the point of 2.3.20 tests is to check if bugs fixed in 2.3.21 are still emulated in pre-2.3.21 mode -->
+
+<#include "overloaded-methods-2-common.ftl">
<@assertFails message="no compatible overloaded">${obj.mNull1(null)}</@>
<@assertEquals actual=obj.mNull1(123) expected="mNull1(int a1 = 123)" />
@@ -151,3 +154,109 @@
expected='mRareWrappings(String s = yes, double d1 = 123.0001, Double d2 = 123.0001, double d3 = 123.0001, b = true)' />
<@assertFails message="no compatible overloaded">${obj.mRareWrappings2(obj.adaptedNumber)}</@>
+
+<#-- Test for List VS array problems due to too vague hinting: -->
+
+<@assertEquals actual=obj.mSeqToArrayNonOverloaded(['a', 'b'], 'c') expected='mSeqToArrayNonOverloaded(String[] [a, b], String c)' />
+
+<@assertEquals actual=obj.mSeqToArrayGoodHint(['a', 'b'], 'c') expected='mSeqToArrayGoodHint(String[] [a, b], String c)' />
+<@assertEquals actual=obj.mSeqToArrayGoodHint(['a', 'b'], 3) expected='mSeqToArrayGoodHint(String[] [a, b], int 3)' />
+
+<@assertEquals actual=obj.mSeqToArrayGoodHint2(['a', 'b'], 'c') expected='mSeqToArrayGoodHint2(String[] [a, b], String c)' />
+<@assertEquals actual=obj.mSeqToArrayGoodHint2('a') expected='mSeqToArrayGoodHint2(String a)' />
+
+<@assertFails message="no compatible overloaded"><@assertEquals actual=obj.mSeqToArrayPoorHint(['a', 'b'], 'c') expected='mSeqToArrayPoorHint(String[] [a, b], String c)' /></@>
+<@assertEquals actual=obj.mSeqToArrayPoorHint('a', 2) expected='mSeqToArrayPoorHint(String a, int 2)' />
+
+<@assertFails message="no compatible overloaded"><@assertEquals actual=obj.mSeqToArrayPoorHint2(['a', 'b']) expected='mSeqToArrayPoorHint2(String[] [a, b])' /></@>
+<@assertEquals actual=obj.mSeqToArrayPoorHint2('a') expected='mSeqToArrayPoorHint2(String a)' />
+
+<@assertFails message="no compatible overloaded"><@assertEquals actual=obj.mSeqToArrayPoorHint3(['a', 'b']) expected='mSeqToArrayPoorHint3(String[] [a, b])' /></@>
+<@assertFails message="no compatible overloaded"><@assertEquals actual=obj.mSeqToArrayPoorHint3([1, 2]) expected='mSeqToArrayPoorHint3(int[] [a, b])' /></@>
+
+<@assertEquals actual=obj.mStringArrayVsListPreference(['a', 'b']) expected="mStringArrayVsListPreference(List [a, b])" />
+<@assertFails message="no compatible overloaded">${obj.mStringArrayVsObjectArrayPreference(['a', 'b'])}</@>
+<#if !dow>
+ <@assertFails message="no compatible overloaded">${obj.mStringArrayVsListPreference(obj.javaObjectArray)}</@>
+</#if>
+<@assertFails message="no compatible overloaded">${obj.mIntArrayVsIntegerArrayPreference([1, 2])}</@>
+
+<#if dow>
+ <@assertFails message="no compatible overloaded">${obj.mStringArrayVsObjectArrayPreference(obj.javaStringArray)}</@>
+ <@assertFails message="no compatible overloaded">${obj.mStringArrayVsObjectArrayPreference(obj.javaIntArray)}</@>
+ <@assertFails message="no compatible overloaded">${obj.mStringArrayVsObjectArrayPreference(obj.javaIntegerArray)}</@>
+<#else>
+ <@assertEquals actual=obj.mStringArrayVsObjectArrayPreference(obj.javaStringArray) expected="mStringArrayVsObjectArrayPreference(String[] [a, b])" />
+ <@assertFails message="no compatible overloaded">${obj.mStringArrayVsObjectArrayPreference(obj.javaIntArray)}</@>
+ <@assertEquals actual=obj.mStringArrayVsObjectArrayPreference(obj.javaIntegerArray) expected="mStringArrayVsObjectArrayPreference(Object[] [11, 22])" />
+</#if>
+
+<@assertFails message="no compatible overloaded">${obj.mIntegerArrayOverloaded([1, 2], 3)}</@>
+<@assertFails message="no compatible overloaded">${obj.mIntegerArrayOverloaded([1?byte, 2?byte], 3)}</@>
+<@assertFails message="no compatible overloaded">${obj.mIntegerArrayOverloaded(obj.javaIntegerList, 3)}</@>
+<@assertFails message="no compatible overloaded">${obj.mIntegerArrayOverloaded(obj.javaByteList, 3)}</@>
+
+<@assertFails message="no compatible overloaded">${obj.mStringArrayVarargsOverloaded2(['a', 'b'])}</@>
+<#if dow>
+ <@assertFails message="no compatible overloaded">${obj.mStringArrayVarargsOverloaded2(obj.javaStringList)}</@>
+ <@assertFails message="no compatible overloaded">${obj.mStringArrayVarargsOverloaded2(obj.javaStringArray)}</@>
+<#else>
+ <@assertEquals actual=obj.mStringArrayVarargsOverloaded2(obj.javaStringList) expected="mStringArrayVarargsOverloaded2(String[] [[a, b]])" /> <#-- toString() accident... -->
+ <@assertEquals actual=obj.mStringArrayVarargsOverloaded2(obj.javaStringArray) expected="mStringArrayVarargsOverloaded2(String[] [a, b])" />
+</#if>
+<@assertFails message="no compatible overloaded">${obj.mStringArrayVarargsOverloaded2(['a'])}</@>
+
+<#if dow>
+ <#-- As with DOW we never end up with array-s after unwrapping, they just work like Lists: -->
+ <@assertEquals actual=obj.mListOrString(obj.javaStringArray) expected="mListOrString(List [a, b])" />
+ <@assertEquals actual=obj.mListListOrString(obj.javaStringArrayArray) expected="mListListOrString(List [[a, b], [], [c]])" />
+ <@assertEquals actual=obj.mListOrString(obj.javaIntArray) expected="mListOrString(List [11, 22])" />
+ <@assertEquals actual=obj.mStringArrayVarargsOverloaded4(obj.javaStringArray, obj.javaStringArray) expected="mStringArrayVarargsOverloaded4(List[] [[a, b], [a, b]])" />
+ <@assertEquals actual=obj.mStringArrayVarargsOverloaded4(obj.javaStringList, obj.javaStringArray) expected="mStringArrayVarargsOverloaded4(List[] [[a, b], [a, b]])" />
+ <@assertEquals actual=obj.mStringArrayVarargsOverloaded4(obj.javaStringArray, obj.javaStringList) expected="mStringArrayVarargsOverloaded4(List[] [[a, b], [a, b]])" />
+<#else>
+ <#-- Pure BeansWrapper unwraps to array-s, but before IcI 2.3.21 it couldn't treat them as Lists: -->
+ <@assertFails message="no compatible overloaded">${obj.mListOrString(obj.javaStringArray)}</@>
+ <@assertFails message="no compatible overloaded">${obj.mListListOrString(obj.javaStringArrayArray)}</@>
+ <@assertFails message="no compatible overloaded">${obj.mListOrString(obj.javaIntArray)}</@>
+ <@assertFails message="no compatible overloaded">${obj.mStringArrayVarargsOverloaded4(obj.javaStringArray, obj.javaStringArray)}</@>
+ <@assertFails message="no compatible overloaded">${obj.mStringArrayVarargsOverloaded4(obj.javaStringList, obj.javaStringArray)}</@>
+ <@assertFails message="no compatible overloaded">${obj.mStringArrayVarargsOverloaded4(obj.javaStringArray, obj.javaStringList)}</@>
+</#if>
+
+<@assertFails message="no compatible overloaded">${obj.mIntArrayArrayOverloaded(obj.javaListOfIntArrays)}</@>
+<@assertFails message="no compatible overloaded">${obj.mArrayOfListsOverloaded(obj.javaListOfIntArrays)}</@>
+
+<@assertFails message="no compatible overloaded">${obj.mMapOrBoolean(obj.hashAndScalarModel)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBoolean(obj.booleanAndScalarModel)}</@>
+
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanVarargs(obj.hashAndScalarModel)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanVarargs(obj.hashAndScalarModel, obj.hashAndScalarModel)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanVarargs(obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanVarargs(obj.allModels, obj.allModels)}</@>
+
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanFixedAndVarargs(obj.hashAndScalarModel)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanFixedAndVarargs(obj.hashAndScalarModel, obj.hashAndScalarModel)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanFixedAndVarargs(obj.hashAndScalarModel, obj.hashAndScalarModel, obj.hashAndScalarModel)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanFixedAndVarargs(obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanFixedAndVarargs(obj.allModels, obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanFixedAndVarargs(obj.allModels, obj.allModels, obj.allModels)}</@>
+
+<@assertEquals actual=obj.mNumberOrArray(obj.allModels) expected="mNumberOrArray(Number 1)" />
+<@assertFails message="no compatible overloaded"><@assertEquals actual=obj.mNumberOrArray([obj.allModels]) expected="mNumberOrArray(Object[] [1])" /></@>
+<@assertEquals actual=obj.mIntOrArray(obj.allModels) expected="mIntOrArray(int 1)" />
+<@assertFails message="no compatible overloaded">${obj.mDateOrArray(obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mStringOrArray(obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mBooleanOrArray(obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrArray(obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mListOrArray(obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mSetOrArray(obj.allModels)}</@>
+
+<@assertFails message="no compatible overloaded">${obj.mCharOrCharacterOverloaded(null)}</@>
+<@assertFails message="no compatible overloaded">${obj.mCharOrBooleanOverloaded('c')}</@>
+<@assertEquals actual=obj.mCharOrBooleanOverloaded(true) expected="mCharOrBooleanOverloaded(boolean true)" />
+
+<@assertFails message="no compatible overloaded">${obj.mCharOrStringOverloaded('c', true)}</@>
+<@assertFails message="no compatible overloaded">${obj.mCharacterOrStringOverloaded('c', true)}</@>
+<@assertEquals actual=obj.mCharOrStringOverloaded2('c') expected="mCharOrStringOverloaded2(String c)" />
+<@assertEquals actual=obj.mCharacterOrStringOverloaded2('c') expected="mCharacterOrStringOverloaded2(String c)" />
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-inc-ici-2.3.20.ftl b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-inc-ici-2.3.20.ftl
index f91d7d8..fa8e413 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-inc-ici-2.3.20.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-inc-ici-2.3.20.ftl
@@ -1,3 +1,5 @@
+<#-- Note that the point of 2.3.20 tests is to check if bugs fixed in 2.3.21 are still emulated in pre-2.3.21 mode -->
+
<@assertEquals actual=obj.mVarargs('a', obj.getNnS('b'), obj.getNnS('c')) expected='mVarargs(String... a1 = abc)' />
<@assertFails message="multiple compatible overload">${obj.mChar('a')}</@>
<@assertFails message="multiple compatible overload">${obj.mIntPrimVSBoxed(123?long)}</@>
@@ -21,4 +23,7 @@
<@assertEquals actual=obj.mRareWrappings(obj.booleanWrappedAsAnotherBoolean, 0, 0, 0, !obj.stringAdaptedToBoolean)
expected='mRareWrappings(Object o = true, double d1 = 0.0, Double d2 = 0.0, double d3 = 0.0, b = true)' />
+<@assertFails message="multiple compatible overloaded">${obj.mCharOrCharacterOverloaded('c')}</@>
+<@assertFails message="multiple compatible overloaded">${obj.mCharOrCharacterOverloaded(obj.javaString)}</@>
+
<#include 'overloaded-methods-2-ici-2.3.20.ftl'>