alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Android example source code file (AlphabetIndexer.java)

This example Android source code file (AlphabetIndexer.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Android by Example" TM.

Java - Android tags/keywords

alphabetindexer, android, cursor, database, datasetobserver, english, object, optimize, override, sparseintarray, string, util, utilities, utils

The AlphabetIndexer.java Android example source code

/*
 * Copyright (C) 2008 Google Inc.
 *
 * 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 com.example.android.rings_extended;

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.
 * 
 * <p>This file was copied from the Contacts application.  In the future it
 * should be provided as a standard part of the Android framework.
 */
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);
    }

    /**
     * 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();
    }
}

Other Android examples (source code examples)

Here is a short list of links related to this Android AlphabetIndexer.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.