| /* |
| * 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 com.android.providers.contacts.util.Hex; |
| import com.google.common.annotations.VisibleForTesting; |
| |
| import java.text.CollationKey; |
| import java.text.Collator; |
| import java.text.RuleBasedCollator; |
| import java.util.Locale; |
| |
| /** |
| * Converts a name to a normalized form by removing all non-letter characters and normalizing |
| * UNICODE according to http://unicode.org/unicode/reports/tr15 |
| */ |
| public class NameNormalizer { |
| |
| private static final Object sCollatorLock = new Object(); |
| |
| private static Locale sCollatorLocale; |
| |
| private static RuleBasedCollator sCachedCompressingCollator; |
| private static RuleBasedCollator sCachedComplexityCollator; |
| |
| /** |
| * Ensure that the cached collators are for the current locale. |
| */ |
| private static void ensureCollators() { |
| final Locale locale = Locale.getDefault(); |
| if (locale.equals(sCollatorLocale)) { |
| return; |
| } |
| sCollatorLocale = locale; |
| |
| sCachedCompressingCollator = (RuleBasedCollator) Collator.getInstance(locale); |
| sCachedCompressingCollator.setStrength(Collator.PRIMARY); |
| sCachedCompressingCollator.setDecomposition(Collator.CANONICAL_DECOMPOSITION); |
| |
| sCachedComplexityCollator = (RuleBasedCollator) Collator.getInstance(locale); |
| sCachedComplexityCollator.setStrength(Collator.SECONDARY); |
| } |
| |
| @VisibleForTesting |
| static RuleBasedCollator getCompressingCollator() { |
| synchronized (sCollatorLock) { |
| ensureCollators(); |
| return sCachedCompressingCollator; |
| } |
| } |
| |
| @VisibleForTesting |
| static RuleBasedCollator getComplexityCollator() { |
| synchronized (sCollatorLock) { |
| ensureCollators(); |
| return sCachedComplexityCollator; |
| } |
| } |
| |
| /** |
| * Converts the supplied name to a string that can be used to perform approximate matching |
| * of names. It ignores non-letter, non-digit characters, and removes accents. |
| */ |
| public static String normalize(String name) { |
| CollationKey key = getCompressingCollator().getCollationKey(lettersAndDigitsOnly(name)); |
| return Hex.encodeHex(key.toByteArray(), true); |
| } |
| |
| /** |
| * Compares "complexity" of two names, which is determined by the presence |
| * of mixed case characters, accents and, if all else is equal, length. |
| */ |
| public static int compareComplexity(String name1, String name2) { |
| String clean1 = lettersAndDigitsOnly(name1); |
| String clean2 = lettersAndDigitsOnly(name2); |
| int diff = getComplexityCollator().compare(clean1, clean2); |
| if (diff != 0) { |
| return diff; |
| } |
| // compareTo sorts uppercase first. We know that there are no non-case |
| // differences from the above test, so we can negate here to get the |
| // lowercase-first comparison we really want... |
| diff = -clean1.compareTo(clean2); |
| if (diff != 0) { |
| return diff; |
| } |
| return name1.length() - name2.length(); |
| } |
| |
| /** |
| * Returns a string containing just the letters and digits from the original string. |
| * Returns empty string if the original string is null. |
| */ |
| private static String lettersAndDigitsOnly(String name) { |
| if (name == null) { |
| return ""; |
| } |
| char[] letters = name.toCharArray(); |
| int length = 0; |
| for (int i = 0; i < letters.length; i++) { |
| final char c = letters[i]; |
| if (Character.isLetterOrDigit(c)) { |
| letters[length++] = c; |
| } |
| } |
| |
| if (length != letters.length) { |
| return new String(letters, 0, length); |
| } |
| |
| return name; |
| } |
| } |