private StringBuilder unifiedTruncateWithoutTerminatorAdded()

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;
    }