in freemarker-core/src/main/java/freemarker/core/BuiltInsForSequences.java [703:839]
static TemplateSequenceModel sort(TemplateSequenceModel seq, String[] keyNames)
throws TemplateModelException {
int ln = seq.size();
if (ln == 0) return seq;
ArrayList res = new ArrayList(ln);
int keyNamesLn = keyNames == null ? 0 : keyNames.length;
// Copy the Seq into a Java List[KVP] (also detects key type at the 1st item):
int keyType = KEY_TYPE_NOT_YET_DETECTED;
Comparator keyComparator = null;
for (int i = 0; i < ln; i++) {
final TemplateModel item = seq.get(i);
TemplateModel key = item;
for (int keyNameI = 0; keyNameI < keyNamesLn; keyNameI++) {
try {
key = ((TemplateHashModel) key).get(keyNames[keyNameI]);
} catch (ClassCastException e) {
if (!(key instanceof TemplateHashModel)) {
throw new _TemplateModelException(
startErrorMessage(keyNamesLn, i),
(keyNameI == 0
? "Sequence items must be hashes when using ?sort_by. "
: "The " + StringUtil.jQuote(keyNames[keyNameI - 1])),
" subvariable is not a hash, so ?sort_by ",
"can't proceed with getting the ",
new _DelayedJQuote(keyNames[keyNameI]),
" subvariable.");
} else {
throw e;
}
}
if (key == null) {
throw new _TemplateModelException(
startErrorMessage(keyNamesLn, i),
"The " + StringUtil.jQuote(keyNames[keyNameI]), " subvariable was null or missing.");
}
} // for each key
if (keyType == KEY_TYPE_NOT_YET_DETECTED) {
if (key instanceof TemplateScalarModel) {
keyType = KEY_TYPE_STRING;
keyComparator = new LexicalKVPComparator(
Environment.getCurrentEnvironment().getCollator());
} else if (key instanceof TemplateNumberModel) {
keyType = KEY_TYPE_NUMBER;
keyComparator = new NumericalKVPComparator(
Environment.getCurrentEnvironment()
.getArithmeticEngine());
} else if (key instanceof TemplateDateModel) {
keyType = KEY_TYPE_DATE;
keyComparator = new DateKVPComparator();
} else if (key instanceof TemplateBooleanModel) {
keyType = KEY_TYPE_BOOLEAN;
keyComparator = new BooleanKVPComparator();
} else {
throw new _TemplateModelException(
startErrorMessage(keyNamesLn, i),
"Values used for sorting must be numbers, strings, date/times or booleans.");
}
}
switch(keyType) {
case KEY_TYPE_STRING:
try {
res.add(new KVP(
((TemplateScalarModel) key).getAsString(),
item));
} catch (ClassCastException e) {
if (!(key instanceof TemplateScalarModel)) {
throw newInconsistentSortKeyTypeException(
keyNamesLn, "string", "strings", i, key);
} else {
throw e;
}
}
break;
case KEY_TYPE_NUMBER:
try {
res.add(new KVP(
((TemplateNumberModel) key).getAsNumber(),
item));
} catch (ClassCastException e) {
if (!(key instanceof TemplateNumberModel)) {
throw newInconsistentSortKeyTypeException(
keyNamesLn, "number", "numbers", i, key);
}
}
break;
case KEY_TYPE_DATE:
try {
res.add(new KVP(
((TemplateDateModel) key).getAsDate(),
item));
} catch (ClassCastException e) {
if (!(key instanceof TemplateDateModel)) {
throw newInconsistentSortKeyTypeException(
keyNamesLn, "date/time", "date/times", i, key);
}
}
break;
case KEY_TYPE_BOOLEAN:
try {
res.add(new KVP(
Boolean.valueOf(((TemplateBooleanModel) key).getAsBoolean()),
item));
} catch (ClassCastException e) {
if (!(key instanceof TemplateBooleanModel)) {
throw newInconsistentSortKeyTypeException(
keyNamesLn, "boolean", "booleans", i, key);
}
}
break;
default:
throw new BugException("Unexpected key type");
}
}
// Sort the List[KVP]:
try {
Collections.sort(res, keyComparator);
} catch (Exception exc) {
throw new _TemplateModelException(exc,
startErrorMessage(keyNamesLn), "Unexpected error while sorting:" + exc);
}
// Convert the List[KVP] to List[V]:
for (int i = 0; i < ln; i++) {
res.set(i, ((KVP) res.get(i)).value);
}
return new TemplateModelListSequence(res);
}