AlphabetIndexer.java :  » Music-Sound » five » org » devtcg » five » widget » Android Open Source

Android Open Source » Music Sound » five 
five » org » devtcg » five » widget » AlphabetIndexer.java
/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.devtcg.five.widget;

import org.devtcg.five.R;

import android.content.Context;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.util.SparseIntArray;

/**
 * This class essentially helps in building an index of section boundaries of a
 * sorted column of a cursor. For instance, if a cursor contains a data set 
 * sorted by first name of a person or the title of a song, this class will 
 * perform a binary search to identify the first row that begins with a 
 * particular letter. The search is case-insensitive. The class caches the index 
 * such that subsequent queries for the same letter will return right away.
 */
public class AlphabetIndexer extends DataSetObserver {

    protected Cursor mDataCursor;
    protected int mColumnIndex;
    protected Object[] mAlphabetArray;
    private SparseIntArray mAlphaMap;
    private java.text.Collator mCollator;

    /**
     * Constructs the indexer.
     * @param cursor the cursor containing the data set
     * @param columnIndex the column number in the cursor that is sorted 
     *        alphabetically
     * @param sections the array of objects that represent the sections. The
     * toString() method of each item is called and the first letter of the 
     * String is used as the letter to search for.
     */
    public AlphabetIndexer(Cursor cursor, int columnIndex, Object[] sections) {
        mDataCursor = cursor;
        mColumnIndex = columnIndex;
        mAlphabetArray = sections;
        mAlphaMap = new SparseIntArray(26 /* Optimize for English */);
        if (cursor != null) {
            cursor.registerDataSetObserver(this);
        }
        // Get a Collator for the current locale for string comparisons.
        mCollator = java.text.Collator.getInstance();
        mCollator.setStrength(java.text.Collator.PRIMARY);
    }
    
    public AlphabetIndexer(Context ctx, Cursor cursor, int columnIdx)
    {
      this(cursor, columnIdx, null);
      String alphaStr = ctx.getResources().getString(R.string.alphabet);

      String[] alpha = new String[alphaStr.length()];
      for (int i = 0; i < alpha.length; i++)
        alpha[i] = String.valueOf(alphaStr.charAt(i));

      mAlphabetArray = alpha;
    }

    public Object[] getSections()
    {
      return mAlphabetArray;
    }

    /**
     * Sets a new cursor as the data set and resets the cache of indices.
     * @param cursor the new cursor to use as the data set
     */
    public void setCursor(Cursor cursor) {
        if (mDataCursor != null) {
            mDataCursor.unregisterDataSetObserver(this);
        }
        mDataCursor = cursor;
        if (cursor != null) {
            mDataCursor.registerDataSetObserver(this);
        }
        mAlphaMap.clear();
    }

    /**
     * Performs a binary search or cache lookup to find the first row that
     * matches a given section's starting letter.
     * @param sectionIndex the section to search for
     * @return the row index of the first occurrence, or the nearest next letter.
     * For instance, if searching for "T" and no "T" is found, then the first
     * row starting with "U" or any higher letter is returned. If there is no
     * data following "T" at all, then the list size is returned.
     */
    public int indexOf(int sectionIndex) {
        final SparseIntArray alphaMap = mAlphaMap;
        final Cursor cursor = mDataCursor;

        if (cursor == null || mAlphabetArray == null) {
            return 0;
        }
        
        // Check bounds
        if (sectionIndex <= 0) {
            return 0;
        }
        if (sectionIndex >= mAlphabetArray.length) {
            sectionIndex = mAlphabetArray.length - 1;
        }

        int savedCursorPos = cursor.getPosition();

        int count = cursor.getCount();
        int start = 0;
        int end = count;
        int pos;

        String letter = mAlphabetArray[sectionIndex].toString();
        letter = letter.toUpperCase();
        int key = letter.charAt(0);
        // Check map
        if (Integer.MIN_VALUE != (pos = alphaMap.get(key, Integer.MIN_VALUE))) {
            // Is it approximate? Using negative value to indicate that it's 
            // an approximation and positive value when it is the accurate
            // position.
            if (pos < 0) {
                pos = -pos;
                end = pos;
            } else {
                // Not approximate, this is the confirmed start of section, return it
                return pos;
            }
        }

        // Do we have the position of the previous section?
        if (sectionIndex > 0) {
            int prevLetter =
                    mAlphabetArray[sectionIndex - 1].toString().charAt(0);
            int prevLetterPos = alphaMap.get(prevLetter, Integer.MIN_VALUE);
            if (prevLetterPos != Integer.MIN_VALUE) {
                start = Math.abs(prevLetterPos);
            }
        }

        // Now that we have a possibly optimized start and end, let's binary search

        pos = (end + start) / 2;

        while (pos < end) {
            // Get letter at pos
            cursor.moveToPosition(pos);
            String curName = cursor.getString(mColumnIndex);
            if (curName == null) {
                if (pos == 0) {
                    break;
                } else {
                    pos--;
                    continue;
                }
            }
            int curLetter = Character.toUpperCase(curName.charAt(0));

            if (curLetter != key) {
                // Enter approximation in hash if a better solution doesn't exist
                int curPos = alphaMap.get(curLetter, Integer.MIN_VALUE);
                if (curPos == Integer.MIN_VALUE || Math.abs(curPos) > pos) {
                    // Negative pos indicates that it is an approximation
                    alphaMap.put(curLetter, -pos);
                }
                if (mCollator.compare(curName, letter) < 0) {
                    start = pos + 1;
                    if (start >= count) {
                        pos = count;
                        break;
                    }
                } else {
                    end = pos;
                }
            } else {
                // They're the same, but that doesn't mean it's the start
                if (start == pos) {
                    // This is it
                    break;
                } else {
                    // Need to go further lower to find the starting row
                    end = pos;
                }
            }
            pos = (start + end) / 2;
        }
        alphaMap.put(key, pos);
        cursor.moveToPosition(savedCursorPos);
        return pos;
    }

    @Override
    public void onChanged() {
        super.onChanged();
        mAlphaMap.clear();
    }

    @Override
    public void onInvalidated() {
        super.onInvalidated();
        mAlphaMap.clear();
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.