public class

ContactLookupKey

extends Object
/*
 * Copyright (C) 2009 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 com.android.providers.contacts;

import android.net.Uri;

import java.util.ArrayList;

/**
 * Contacts lookup key. Used for generation and parsing of contact lookup keys as well
 * as doing the actual lookup.
 */
public class ContactLookupKey {

    public static final int LOOKUP_TYPE_SOURCE_ID = 0;
    public static final int LOOKUP_TYPE_DISPLAY_NAME = 1;
    public static final int LOOKUP_TYPE_RAW_CONTACT_ID = 2;

    public static class LookupKeySegment implements Comparable<LookupKeySegment> {
        public int accountHashCode;
        public int lookupType;
        public String rawContactId;
        public String key;
        public long contactId;

        public int compareTo(LookupKeySegment another) {
            if (contactId > another.contactId) {
                return -1;
            }
            if (contactId < another.contactId) {
                return 1;
            }
            return 0;
        }
    }

    /**
     * Returns a short hash code that functions as an additional precaution against the exceedingly
     * improbable collision between sync IDs in different accounts.
     */
    public static int getAccountHashCode(String accountType, String accountName) {
        if (accountType == null || accountName == null) {
            return 0;
        }

        return (accountType.hashCode() ^ accountName.hashCode()) & 0xFFF;
    }

    public static void appendToLookupKey(StringBuilder lookupKey, String accountType,
            String accountName, long rawContactId, String sourceId, String displayName) {
        if (displayName == null) {
            displayName = "";
        }

        if (lookupKey.length() != 0) {
            lookupKey.append(".");
        }

        lookupKey.append(getAccountHashCode(accountType, accountName));
        if (sourceId == null) {
            lookupKey.append('r').append(rawContactId).append('-').append(
                    NameNormalizer.normalize(displayName));
        } else {
            int pos = lookupKey.length();
            lookupKey.append('i');
            if (appendEscapedSourceId(lookupKey, sourceId)) {
                lookupKey.setCharAt(pos, 'e');
            }
        }
    }

    private static boolean appendEscapedSourceId(StringBuilder sb, String sourceId) {
        boolean escaped = false;
        int start = 0;
        while (true) {
            int index = sourceId.indexOf('.', start);
            if (index == -1) {
                sb.append(sourceId, start, sourceId.length());
                break;
            }

            escaped = true;
            sb.append(sourceId, start, index);
            sb.append("..");
            start = index + 1;
        }
        return escaped;
    }

    public ArrayList<LookupKeySegment> parse(String lookupKey) {
        ArrayList<LookupKeySegment> list = new ArrayList<LookupKeySegment>();

        String string = Uri.decode(lookupKey);
        int offset = 0;
        int length = string.length();
        int hashCode = 0;
        int lookupType = -1;
        boolean escaped = false;
        String rawContactId = null;
        String key;

        while (offset < length) {
            char c = 0;

            // Parse account hash code
            hashCode = 0;
            while (offset < length) {
                c = string.charAt(offset++);
                if (c < '0' || c > '9') {
                    break;
                }
                hashCode = hashCode * 10 + (c - '0');
            }

            // Parse segment type
            if (c == 'i') {
                lookupType = LOOKUP_TYPE_SOURCE_ID;
                escaped = false;
            } else if (c == 'e') {
                lookupType = LOOKUP_TYPE_SOURCE_ID;
                escaped = true;
            } else if (c == 'n') {
                lookupType = LOOKUP_TYPE_DISPLAY_NAME;
            } else if (c == 'r') {
                lookupType = LOOKUP_TYPE_RAW_CONTACT_ID;
            } else {
                throw new IllegalArgumentException("Invalid lookup id: " + lookupKey);
            }

            // Parse the source ID or normalized display name
            switch (lookupType) {
                case LOOKUP_TYPE_SOURCE_ID: {
                    if (escaped) {
                        StringBuffer sb = new StringBuffer();
                        while (offset < length) {
                            c = string.charAt(offset++);

                            if (c == '.') {
                                if (offset == length) {
                                    throw new IllegalArgumentException("Invalid lookup id: " +
                                            lookupKey);
                                }
                                c = string.charAt(offset);

                                if (c == '.') {
                                    sb.append('.');
                                    offset++;
                                } else {
                                    break;
                                }
                            } else {
                                sb.append(c);
                            }
                        }
                        key = sb.toString();
                    } else {
                        int start = offset;
                        while (offset < length) {
                            c = string.charAt(offset++);
                            if (c == '.') {
                                break;
                            }
                        }
                        if (offset == length) {
                            key = string.substring(start);
                        } else {
                            key = string.substring(start, offset - 1);
                        }
                    }
                    break;
                }
                case LOOKUP_TYPE_DISPLAY_NAME: {
                    int start = offset;
                    while (offset < length) {
                        c = string.charAt(offset++);
                        if (c == '.') {
                            break;
                        }
                    }
                    if (offset == length) {
                        key = string.substring(start);
                    } else {
                        key = string.substring(start, offset - 1);
                    }
                    break;
                }
                case LOOKUP_TYPE_RAW_CONTACT_ID: {
                    int dash = -1;
                    int start = offset;
                    while (offset < length) {
                        c = string.charAt(offset);
                        if (c == '-' && dash == -1) {
                            dash = offset;
                        }
                        offset++;
                        if (c == '.') {
                            break;
                        }
                    }
                    if (dash != -1) {
                        rawContactId = string.substring(start, dash);
                        start = dash + 1;
                    }
                    if (offset == length) {
                        key = string.substring(start);
                    } else {
                        key = string.substring(start, offset - 1);
                    }
                    break;
                }
                default:
                    // Will never happen
                    throw new IllegalStateException();
            }

            LookupKeySegment segment = new LookupKeySegment();
            segment.accountHashCode = hashCode;
            segment.lookupType = lookupType;
            segment.rawContactId = rawContactId;
            segment.key = key;
            segment.contactId = -1;
            list.add(segment);
        }

        return list;
    }
}