*            The JoinPolicy that allocates records between the two pages.
     * @return <i>true</i> if the result is a rebalanced pair of pages or
     *         <i>false</i> if the two pages were joined into a single page.
     * @throws RebalanceException
     *             in the rare case where no rearrangement of the records is
     *             possible.
     */
    final boolean join(Buffer buffer, int foundAt1, int foundAt2, Key indexKey, Key spareKey, JoinPolicy policy)
            throws RebalanceException {
        foundAt1 &= P_MASK;
        foundAt2 &= P_MASK;

        if (buffer == this || foundAt1 <= KEY_BLOCK_START || foundAt1 >= _keyBlockEnd || foundAt2 <= KEY_BLOCK_START
                || foundAt2 >= buffer._keyBlockEnd) {
            Debug.$assert0.t(false);
            throw new IllegalArgumentException("foundAt1=" + foundAt1 + " foundAt2=" + foundAt2 + " _keyBlockEnd="
                    + _keyBlockEnd + " buffer._keyBlockEnd=" + buffer._keyBlockEnd);
        }

        if (Debug.ENABLED) {
            spareKey.clear();
            assertVerify();
            buffer.assertVerify();
        }

        final boolean hasMVV = (_mvvCount > 0) || (buffer.getMvvCount() > 0);
        //
        // Working variables
        //
        int kbData; // content of a key block
        int tbData; // content of a tail block header
        int tail; // offset within page of a tail block

        final byte[] spareKeyBytes = spareKey.getEncodedBytes();
        final byte[] indexKeyBytes = indexKey.getEncodedBytes();
        //
        // Initialize spareKey to contain the first key of the right
        // page.
        //
        buffer.keyAt(foundAt2, spareKey);

        long measureLeft = joinMeasure(foundAt1, _keyBlockEnd);
        long measureRight = buffer.joinMeasure(KEY_BLOCK_START, foundAt2);
        kbData = buffer.getInt(foundAt2);
        int oldEbc = decodeKeyBlockEbc(kbData);
        int newEbc = Math.min(oldEbc, Math.min((int) (measureLeft >>> 32), (int) (measureRight >>> 32)));
        tail = decodeKeyBlockTail(kbData);
        tbData = buffer.getInt(tail);

        int oldSize = decodeTailBlockSize(tbData);
        int newSize = oldSize + (oldEbc - newEbc);
        int adjustmentForNewEbc = ((newSize + ~TAILBLOCK_MASK) & TAILBLOCK_MASK)
                - ((oldSize + ~TAILBLOCK_MASK) & TAILBLOCK_MASK);

        /*
         * Size and number of keys in the virtual page that would result from
         * joining all non-removed records.
         */
        final int virtualSize = inUseSize() + buffer.inUseSize() - (int) measureLeft - (int) measureRight
                + adjustmentForNewEbc + KEY_BLOCK_START;

        final int virtualKeyCount = ((foundAt1 - KEY_BLOCK_START) + (buffer.getKeyBlockEnd() - foundAt2))
                / KEYBLOCK_LENGTH;

        boolean okayToRejoin = virtualKeyCount < _pool.getMaxKeys() && policy.acceptJoin(this, virtualSize);

        boolean result;

        if (okayToRejoin) {
            /*
             * REJOIN CASE
             * 
             * -----------
             * 
             * This is the case where according to the JoinPolicy, the records
             * from the right page can be merged into the left page. The caller
             * will then deallocate the right page.
             */
            Debug.$assert0.t(virtualSize <= _bufferSize);
            /*
             * Deallocate the records being removed from the left page
             */
            joinDeallocateTails(foundAt1, _keyBlockEnd);

            if (newEbc < oldEbc) {
                /*
                 * Deallocate the records being removed from the right page and
                 * then reduce the ebc of the first remaining key as needed. The
                 * only reason for deallocating tail blocks in the right page is
                 * that reduceEbc may need some addition space. Later the right
                 * page will be cleared.
                 */
                buffer.joinDeallocateTails(KEY_BLOCK_START, foundAt2);
                buffer.reduceEbc(foundAt2, newEbc, spareKeyBytes);
            }

            setKeyBlockEnd(foundAt1);
            /*
             * Move non-removed records from the right page to the left page.
             */
            moveRecords(buffer, foundAt2, buffer._keyBlockEnd, foundAt1, false);

            /*
             * Now set the right page to have no key blocks; this allows all the
             * remaining tail block space to be deallocated if the page is
             * subsequently written to disk.
             */
            buffer.setKeyBlockEnd(KEY_BLOCK_START);
            buffer.clearBytes(KEY_BLOCK_START, _bufferSize);
            buffer.setAlloc(_bufferSize);
            /*
             * unsplice the right buffer from the right sibling chain.
             */
            long rightSibling = buffer.getRightSibling();
            setRightSibling(rightSibling);
            if (hasMVV) {
                _mvvCount = Integer.MAX_VALUE;
            }
            invalidateFastIndex();
            result = false;
        }

        else {
            /*
             * REBALANCE CASE
             * 
             * --------------
             * 
             * The remaining records still require two pages. Some key must be
             * chosen to serve as the edge between the pages. Use the JoinPolicy
             * to find the optimal location.
             */
            int joinOffset = joinMeasureRebalanceOffset(buffer, virtualSize, foundAt1, foundAt2, adjustmentForNewEbc,
                    policy);

            if (joinOffset == 0) {
                /*
                 * Rebalancing is infeasible
                 */
                throw new RebalanceException();
            } else if (joinOffset < 0) {
                /*
                 * Move records from the right page to the left page.
                 */
                joinOffset = -joinOffset;
                buffer.keyAt(joinOffset, indexKey);
                /*
                 * Remove records from the left page
                 */
                joinDeallocateTails(foundAt1, _keyBlockEnd);
                clearBytes(foundAt1, _keyBlockEnd);
                setKeyBlockEnd(foundAt1);

                /*
                 * Deallocate the records being removed from the right page and
                 * then reduce the ebc of the first remaining key as needed.
                 */
                buffer.joinDeallocateTails(KEY_BLOCK_START, foundAt2);
                buffer.clearBytes(KEY_BLOCK_START, foundAt2);
                buffer.reduceEbc(foundAt2, newEbc, spareKeyBytes);
                int rightSize = buffer._keyBlockEnd - joinOffset;

                moveRecords(buffer, foundAt2, joinOffset, _keyBlockEnd, true);

                System.arraycopy(buffer._bytes, joinOffset, buffer._bytes, KEY_BLOCK_START, rightSize);
                buffer.clearBytes(KEY_BLOCK_START + rightSize, buffer._keyBlockEnd);
                buffer.setKeyBlockEnd(KEY_BLOCK_START + rightSize);
                buffer.reduceEbc(KEY_BLOCK_START, 0, indexKeyBytes);

            } else {
                /*
                 * We will move records from the left page to the right page.
                 */
                keyAt(joinOffset, indexKey);
                joinDeallocateTails(foundAt1, _keyBlockEnd);
                clearBytes(foundAt1, _keyBlockEnd);
                setKeyBlockEnd(foundAt1);

                buffer.joinDeallocateTails(KEY_BLOCK_START, foundAt2);

                int rightSize = buffer._keyBlockEnd - foundAt2;
                System.arraycopy(buffer._bytes, foundAt2, buffer._bytes, KEY_BLOCK_START, rightSize);
                buffer.clearBytes(KEY_BLOCK_START + rightSize, buffer._keyBlockEnd);
                buffer.setKeyBlockEnd(KEY_BLOCK_START + rightSize);
                buffer.reduceEbc(KEY_BLOCK_START, newEbc, spareKeyBytes);

                if (joinOffset != foundAt1) {
                    buffer.moveRecords(this, joinOffset, foundAt1, KEY_BLOCK_START, false);
                    setKeyBlockEnd(joinOffset);
                }
                /*
                 * Set up the edge key in the left page.
                 */
                moveRecords(buffer, KEY_BLOCK_START, KEY_BLOCK_START, joinOffset, true);
                /*
                 * Fix the left key of the right page so that its ebc is zero.
                 */
                buffer.reduceEbc(KEY_BLOCK_START, 0, indexKeyBytes);
            }

            setRightSibling(buffer.getPageAddress());

            invalidateFastIndex();
            buffer.invalidateFastIndex();

            if (hasMVV) {
                _mvvCount = Integer.MAX_VALUE;
                buffer._mvvCount = Integer.MAX_VALUE;
            }
            result = true;
        }

        Debug.$assert0.t(KEY_BLOCK_START + KEYBLOCK_LENGTH < _keyBlockEnd);

        if (result) {
            Debug.$assert0.t(KEY_BLOCK_START + KEYBLOCK_LENGTH < buffer._keyBlockEnd);
        }
        /*
         * Indicate that both buffers have changed
         */
        bumpGeneration();
        buffer.bumpGeneration();

        if (Debug.ENABLED) {
            assertVerify();
            if (result) {
                buffer.assertVerify();
            }
        }

        return result;
    }