in freemarker-core/src/main/java/freemarker/core/DefaultTruncateBuiltinAlgorithm.java [460:572]
private StringBuilder unifiedTruncateWithoutTerminatorAdded(
String s, int maxLength,
TemplateModel terminator, int terminatorLength, Boolean terminatorRemovesDots,
TruncationMode mode) throws TemplateModelException {
final int cbInitialLastCIdx = maxLength - terminatorLength - 1;
int cbLastCIdx = cbInitialLastCIdx;
// Why we do this here: If both Word Boundary and Character Boundary truncation will be attempted, then this way
// we don't have to skip the WS twice.
cbLastCIdx = skipTrailingWS(s, cbLastCIdx);
if (cbLastCIdx < 0) {
return null;
}
boolean addSpaceAtWordBoundary = this.addSpaceAtWordBoundary && terminatorLength != 0;
if (mode == TruncationMode.AUTO && wordBoundaryMinLength < 1.0 || mode == TruncationMode.WORD_BOUNDARY) {
// Do word boundary truncation. Might not be possible due to minLength restriction (see below), in which
// case truncedS stays null.
StringBuilder truncedS = null;
{
final int wordTerminatorLength = addSpaceAtWordBoundary ? terminatorLength + 1 : terminatorLength;
final int minIdx = mode == TruncationMode.AUTO
? Math.max(((int) Math.ceil(maxLength * wordBoundaryMinLength)) - wordTerminatorLength - 1, 0)
: 0;
int wbLastCIdx = Math.min(maxLength - wordTerminatorLength - 1, cbLastCIdx);
boolean followingCIsWS
= s.length() > wbLastCIdx + 1 ? Character.isWhitespace(s.charAt(wbLastCIdx + 1)) : true;
executeTruncateWB:
while (wbLastCIdx >= minIdx) {
char curC = s.charAt(wbLastCIdx);
boolean curCIsWS = Character.isWhitespace(curC);
if (!curCIsWS && followingCIsWS) {
// Note how we avoid getMTerminatorRemovesDots until we absolutely need its result.
if (!addSpaceAtWordBoundary && isDot(curC)) {
if (terminatorRemovesDots == null) {
terminatorRemovesDots = getTerminatorRemovesDots(terminator);
}
if (terminatorRemovesDots) {
while (wbLastCIdx >= minIdx && isDotOrWS(s.charAt(wbLastCIdx))) {
wbLastCIdx--;
}
if (wbLastCIdx < minIdx) {
break executeTruncateWB;
}
}
}
truncedS = new StringBuilder(wbLastCIdx + 1 + wordTerminatorLength);
truncedS.append(s, 0, wbLastCIdx + 1);
if (addSpaceAtWordBoundary) {
truncedS.append(' ');
}
break executeTruncateWB;
}
followingCIsWS = curCIsWS;
wbLastCIdx--;
} // executeTruncateWB: while (...)
}
if (truncedS != null
|| mode == TruncationMode.WORD_BOUNDARY
|| mode == TruncationMode.AUTO && wordBoundaryMinLength == 0.0) {
return truncedS;
}
// We are in TruncationMode.AUTO. truncateW wasn't possible, so fall back to character boundary truncation.
}
// Do character boundary truncation.
// If the truncation point is a word boundary, and thus we add a space before the terminator, then we may run
// out of the maxLength by 1. In that case we have to truncate one character earlier.
if (cbLastCIdx == cbInitialLastCIdx && addSpaceAtWordBoundary && isWordEnd(s, cbLastCIdx)) {
cbLastCIdx--;
if (cbLastCIdx < 0) {
return null;
}
}
// Skip trailing WS, also trailing dots if necessary.
boolean skippedDots;
do {
skippedDots = false;
cbLastCIdx = skipTrailingWS(s, cbLastCIdx);
if (cbLastCIdx < 0) {
return null;
}
// Note how we avoid getMTerminatorRemovesDots until we absolutely need its result.
if (isDot(s.charAt(cbLastCIdx)) && !(addSpaceAtWordBoundary && isWordEnd(s, cbLastCIdx))) {
if (terminatorRemovesDots == null) {
terminatorRemovesDots = getTerminatorRemovesDots(terminator);
}
if (terminatorRemovesDots) {
cbLastCIdx = skipTrailingDots(s, cbLastCIdx);
if (cbLastCIdx < 0) {
return null;
}
skippedDots = true;
}
}
} while (skippedDots);
boolean addWordBoundarySpace = addSpaceAtWordBoundary && isWordEnd(s, cbLastCIdx);
StringBuilder truncatedS = new StringBuilder(cbLastCIdx + 1 + (addWordBoundarySpace ? 1 : 0) + terminatorLength);
truncatedS.append(s, 0, cbLastCIdx + 1);
if (addWordBoundarySpace) {
truncatedS.append(' ');
}
return truncatedS;
}