12010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan/*
22010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan * Copyright (C) 2016 The Android Open Source Project
32010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan *
42010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan * Licensed under the Apache License, Version 2.0 (the "License");
52010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan * you may not use this file except in compliance with the License.
62010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan * You may obtain a copy of the License at
72010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan *
82010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan *      http://www.apache.org/licenses/LICENSE-2.0
92010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan *
102010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan * Unless required by applicable law or agreed to in writing, software
112010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan * distributed under the License is distributed on an "AS IS" BASIS,
122010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan * See the License for the specific language governing permissions and
142010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan * limitations under the License.
152010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan */
162010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
172010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tanpackage com.android.documentsui.sorting;
182010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
192010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tanimport static com.android.documentsui.base.DocumentInfo.getCursorLong;
202010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tanimport static com.android.documentsui.base.DocumentInfo.getCursorString;
212010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
222010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tanimport android.database.AbstractCursor;
232010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tanimport android.database.Cursor;
24b324dfd842a8412509b0e02626e1ebfafd00c44bSteve McKayimport android.os.Bundle;
252010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tanimport android.provider.DocumentsContract.Document;
262010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
27b47b4b560c46336a0e6ba3e86db3f2d76b4b2bc3Garfield Tanimport com.android.documentsui.base.Lookup;
282010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tanimport com.android.documentsui.base.Shared;
292010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tanimport com.android.documentsui.sorting.SortModel.SortDimensionId;
302010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
312010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan/**
322010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan * Cursor wrapper that presents a sorted view of the underlying cursor. Handles
332010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan * common {@link Document} sorting modes, such as ordering directories first.
342010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan */
352010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tanclass SortingCursorWrapper extends AbstractCursor {
362010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    private final Cursor mCursor;
372010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
382010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    private final int[] mPosition;
392010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
40b47b4b560c46336a0e6ba3e86db3f2d76b4b2bc3Garfield Tan    public SortingCursorWrapper(
41b47b4b560c46336a0e6ba3e86db3f2d76b4b2bc3Garfield Tan            Cursor cursor, SortDimension dimension, Lookup<String, String> fileTypeLookup) {
422010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        mCursor = cursor;
432010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
442010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        final int count = cursor.getCount();
452010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        mPosition = new int[count];
462010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        boolean[] isDirs = new boolean[count];
47b47b4b560c46336a0e6ba3e86db3f2d76b4b2bc3Garfield Tan        String[] stringValues = null;
482010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        long[] longValues = null;
4998d745d90b2bf8232593f2d5f9092536298d1147Garfield Tan        String[] ids = new String[count];
502010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
512010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        final @SortDimensionId int id = dimension.getId();
522010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        switch (id) {
532010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            case SortModel.SORT_DIMENSION_ID_TITLE:
54b47b4b560c46336a0e6ba3e86db3f2d76b4b2bc3Garfield Tan            case SortModel.SORT_DIMENSION_ID_FILE_TYPE:
55b47b4b560c46336a0e6ba3e86db3f2d76b4b2bc3Garfield Tan                stringValues = new String[count];
562010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                break;
572010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            case SortModel.SORT_DIMENSION_ID_DATE:
582010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            case SortModel.SORT_DIMENSION_ID_SIZE:
592010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                longValues = new long[count];
602010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                break;
612010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        }
622010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
632010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        cursor.moveToPosition(-1);
642010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        for (int i = 0; i < count; i++) {
652010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            cursor.moveToNext();
662010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            mPosition[i] = i;
672010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
682010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            final String mimeType = getCursorString(mCursor, Document.COLUMN_MIME_TYPE);
692010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            isDirs[i] = Document.MIME_TYPE_DIR.equals(mimeType);
7098d745d90b2bf8232593f2d5f9092536298d1147Garfield Tan            ids[i] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
712010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
722010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            switch(id) {
732010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                case SortModel.SORT_DIMENSION_ID_TITLE:
742010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    final String displayName = getCursorString(
752010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                            mCursor, Document.COLUMN_DISPLAY_NAME);
76b47b4b560c46336a0e6ba3e86db3f2d76b4b2bc3Garfield Tan                    stringValues[i] = displayName;
77b47b4b560c46336a0e6ba3e86db3f2d76b4b2bc3Garfield Tan                    break;
78b47b4b560c46336a0e6ba3e86db3f2d76b4b2bc3Garfield Tan                case SortModel.SORT_DIMENSION_ID_FILE_TYPE:
79b47b4b560c46336a0e6ba3e86db3f2d76b4b2bc3Garfield Tan                    stringValues[i] = fileTypeLookup.lookup(mimeType);
802010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    break;
812010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                case SortModel.SORT_DIMENSION_ID_DATE:
822010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    longValues[i] = getLastModified(mCursor);
832010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    break;
842010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                case SortModel.SORT_DIMENSION_ID_SIZE:
852010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    longValues[i] = getCursorLong(mCursor, Document.COLUMN_SIZE);
862010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    break;
872010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            }
882010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
892010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        }
902010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
912010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        switch (id) {
922010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            case SortModel.SORT_DIMENSION_ID_TITLE:
93b47b4b560c46336a0e6ba3e86db3f2d76b4b2bc3Garfield Tan            case SortModel.SORT_DIMENSION_ID_FILE_TYPE:
9498d745d90b2bf8232593f2d5f9092536298d1147Garfield Tan                binarySort(stringValues, isDirs, mPosition, ids, dimension.getSortDirection());
952010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                break;
962010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            case SortModel.SORT_DIMENSION_ID_DATE:
972010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            case SortModel.SORT_DIMENSION_ID_SIZE:
982010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                binarySort(longValues, isDirs, mPosition, ids, dimension.getSortDirection());
992010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                break;
1002010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        }
1012010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1022010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
1032010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1042010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    @Override
1052010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    public void close() {
1062010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        super.close();
1072010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        mCursor.close();
1082010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
1092010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1102010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    @Override
1112010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    public boolean onMove(int oldPosition, int newPosition) {
1122010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        return mCursor.moveToPosition(mPosition[newPosition]);
1132010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
1142010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1152010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    @Override
1162010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    public String[] getColumnNames() {
1172010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        return mCursor.getColumnNames();
1182010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
1192010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1202010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    @Override
1212010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    public int getCount() {
1222010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        return mCursor.getCount();
1232010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
1242010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1252010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    @Override
1262010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    public double getDouble(int column) {
1272010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        return mCursor.getDouble(column);
1282010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
1292010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1302010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    @Override
1312010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    public float getFloat(int column) {
1322010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        return mCursor.getFloat(column);
1332010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
1342010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1352010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    @Override
1362010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    public int getInt(int column) {
1372010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        return mCursor.getInt(column);
1382010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
1392010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1402010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    @Override
1412010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    public long getLong(int column) {
1422010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        return mCursor.getLong(column);
1432010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
1442010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1452010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    @Override
1462010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    public short getShort(int column) {
1472010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        return mCursor.getShort(column);
1482010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
1492010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1502010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    @Override
1512010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    public String getString(int column) {
1522010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        return mCursor.getString(column);
1532010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
1542010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1552010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    @Override
1562010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    public int getType(int column) {
1572010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        return mCursor.getType(column);
1582010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
1592010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1602010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    @Override
1612010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    public boolean isNull(int column) {
1622010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        return mCursor.isNull(column);
1632010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
1642010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
165b324dfd842a8412509b0e02626e1ebfafd00c44bSteve McKay    @Override
166b324dfd842a8412509b0e02626e1ebfafd00c44bSteve McKay    public Bundle getExtras() {
167b324dfd842a8412509b0e02626e1ebfafd00c44bSteve McKay        return mCursor.getExtras();
168b324dfd842a8412509b0e02626e1ebfafd00c44bSteve McKay    }
169b324dfd842a8412509b0e02626e1ebfafd00c44bSteve McKay
1702010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    /**
1712010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan     * @return Timestamp for the given document. Some docs (e.g. active downloads) have a null
1722010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan     * timestamp - these will be replaced with MAX_LONG so that such files get sorted to the top
1732010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan     * when sorting descending by date.
1742010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan     */
1752010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    private static long getLastModified(Cursor cursor) {
1762010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        long l = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
1772010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        return (l == -1) ? Long.MAX_VALUE : l;
1782010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
1792010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1802010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    /**
1812010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan     * Borrowed from TimSort.binarySort(), but modified to sort two column
1822010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan     * dataset.
1832010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan     */
1842010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    private static void binarySort(
1852010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            String[] sortKey,
1862010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            boolean[] isDirs,
1872010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            int[] positions,
18898d745d90b2bf8232593f2d5f9092536298d1147Garfield Tan            String[] ids,
1892010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            @SortDimension.SortDirection int direction) {
1902010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        final int count = positions.length;
1912010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        for (int start = 1; start < count; start++) {
1922010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            final int pivotPosition = positions[start];
1932010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            final String pivotValue = sortKey[start];
1942010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            final boolean pivotIsDir = isDirs[start];
19598d745d90b2bf8232593f2d5f9092536298d1147Garfield Tan            final String pivotId = ids[start];
1962010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
1972010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            int left = 0;
1982010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            int right = start;
1992010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
2002010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            while (left < right) {
2012010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                int mid = (left + right) >>> 1;
2022010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
2032010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                // Directories always go in front.
2042010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                int compare = 0;
2052010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                final boolean rhsIsDir = isDirs[mid];
2062010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                if (pivotIsDir && !rhsIsDir) {
2072010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    compare = -1;
2082010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                } else if (!pivotIsDir && rhsIsDir) {
2092010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    compare = 1;
2102010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                } else {
2112010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    final String lhs = pivotValue;
2122010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    final String rhs = sortKey[mid];
2132010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    switch (direction) {
2142010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                        case SortDimension.SORT_DIRECTION_ASCENDING:
2152010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                            compare = Shared.compareToIgnoreCaseNullable(lhs, rhs);
2162010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                            break;
2172010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                        case SortDimension.SORT_DIRECTION_DESCENDING:
2182010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                            compare = -Shared.compareToIgnoreCaseNullable(lhs, rhs);
2192010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                            break;
2202010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                        default:
2212010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                            throw new IllegalArgumentException(
2222010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                                    "Unknown sorting direction: " + direction);
2232010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    }
2242010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                }
2252010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
22698d745d90b2bf8232593f2d5f9092536298d1147Garfield Tan                // Use document ID as a tie breaker to achieve stable sort result.
22798d745d90b2bf8232593f2d5f9092536298d1147Garfield Tan                if (compare == 0) {
22898d745d90b2bf8232593f2d5f9092536298d1147Garfield Tan                    compare = pivotId.compareTo(ids[mid]);
22998d745d90b2bf8232593f2d5f9092536298d1147Garfield Tan                }
23098d745d90b2bf8232593f2d5f9092536298d1147Garfield Tan
2312010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                if (compare < 0) {
2322010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    right = mid;
2332010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                } else {
2342010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    left = mid + 1;
2352010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                }
2362010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            }
2372010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
2382010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            int n = start - left;
2392010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            switch (n) {
2402010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                case 2:
2412010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    positions[left + 2] = positions[left + 1];
2422010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    sortKey[left + 2] = sortKey[left + 1];
2432010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    isDirs[left + 2] = isDirs[left + 1];
2442010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                case 1:
2452010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    positions[left + 1] = positions[left];
2462010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    sortKey[left + 1] = sortKey[left];
2472010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    isDirs[left + 1] = isDirs[left];
2482010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    break;
2492010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                default:
2502010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    System.arraycopy(positions, left, positions, left + 1, n);
2512010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    System.arraycopy(sortKey, left, sortKey, left + 1, n);
2522010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    System.arraycopy(isDirs, left, isDirs, left + 1, n);
2532010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            }
2542010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
2552010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            positions[left] = pivotPosition;
2562010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            sortKey[left] = pivotValue;
2572010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            isDirs[left] = pivotIsDir;
2582010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        }
2592010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
2602010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
2612010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    /**
2622010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan     * Borrowed from TimSort.binarySort(), but modified to sort two column
2632010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan     * dataset.
2642010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan     */
2652010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    private static void binarySort(
2662010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            long[] sortKey,
2672010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            boolean[] isDirs,
2682010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            int[] positions,
2692010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            String[] ids,
2702010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            @SortDimension.SortDirection int direction) {
2712010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        final int count = positions.length;
2722010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        for (int start = 1; start < count; start++) {
2732010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            final int pivotPosition = positions[start];
2742010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            final long pivotValue = sortKey[start];
2752010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            final boolean pivotIsDir = isDirs[start];
2762010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            final String pivotId = ids[start];
2772010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
2782010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            int left = 0;
2792010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            int right = start;
2802010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
2812010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            while (left < right) {
2822010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                int mid = ((left + right) >>> 1);
2832010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
2842010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                // Directories always go in front.
2852010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                int compare = 0;
2862010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                final boolean rhsIsDir = isDirs[mid];
2872010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                if (pivotIsDir && !rhsIsDir) {
2882010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    compare = -1;
2892010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                } else if (!pivotIsDir && rhsIsDir) {
2902010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    compare = 1;
2912010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                } else {
2922010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    final long lhs = pivotValue;
2932010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    final long rhs = sortKey[mid];
2942010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    switch (direction) {
2952010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                        case SortDimension.SORT_DIRECTION_ASCENDING:
2962010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                            compare = Long.compare(lhs, rhs);
2972010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                            break;
2982010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                        case SortDimension.SORT_DIRECTION_DESCENDING:
2992010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                            compare = -Long.compare(lhs, rhs);
3002010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                            break;
3012010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                        default:
3022010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                            throw new IllegalArgumentException(
3032010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                                    "Unknown sorting direction: " + direction);
3042010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    }
3052010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                }
3062010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
3072010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                // If numerical comparison yields a tie, use document ID as a tie breaker.  This
3082010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                // will yield stable results even if incoming items are continually shuffling and
3092010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                // have identical numerical sort keys.  One common example of this scenario is seen
3102010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                // when sorting a set of active downloads by mod time.
3112010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                if (compare == 0) {
3122010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    compare = pivotId.compareTo(ids[mid]);
3132010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                }
3142010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
3152010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                if (compare < 0) {
3162010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    right = mid;
3172010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                } else {
3182010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    left = mid + 1;
3192010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                }
3202010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            }
3212010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
3222010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            int n = start - left;
3232010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            switch (n) {
3242010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                case 2:
3252010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    positions[left + 2] = positions[left + 1];
3262010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    sortKey[left + 2] = sortKey[left + 1];
3272010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    isDirs[left + 2] = isDirs[left + 1];
3282010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    ids[left + 2] = ids[left + 1];
3292010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                case 1:
3302010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    positions[left + 1] = positions[left];
3312010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    sortKey[left + 1] = sortKey[left];
3322010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    isDirs[left + 1] = isDirs[left];
3332010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    ids[left + 1] = ids[left];
3342010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    break;
3352010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                default:
3362010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    System.arraycopy(positions, left, positions, left + 1, n);
3372010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    System.arraycopy(sortKey, left, sortKey, left + 1, n);
3382010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    System.arraycopy(isDirs, left, isDirs, left + 1, n);
3392010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan                    System.arraycopy(ids, left, ids, left + 1, n);
3402010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            }
3412010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan
3422010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            positions[left] = pivotPosition;
3432010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            sortKey[left] = pivotValue;
3442010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            isDirs[left] = pivotIsDir;
3452010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan            ids[left] = pivotId;
3462010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan        }
3472010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan    }
3482010ff7828f2d68de0be24c975f5c48ec3151f96Garfield Tan}
349