ContactsProvider2: Parsing display name into structural parts.
Based on an algorithm by Ariel Gertzen.
diff --git a/res/values-en-rUS/strings.xml b/res/values-en-rUS/strings.xml
index 542d811..7f4a168 100644
--- a/res/values-en-rUS/strings.xml
+++ b/res/values-en-rUS/strings.xml
@@ -96,6 +96,7 @@
<item>Maureen, Mo</item>
<item>Maurice, Mo</item>
<item>Megan, Meg</item>
+ <item>Michael, Mickey, Mick, Mike, Mikey</item>
<item>Morris, Mo</item>
<item>Nancy, Nan</item>
<item>Nathan, Nat, Nate</item>
@@ -138,4 +139,19 @@
<item>Yvonna, Vonna</item>
<item>Zachary, Zach, Zack, Zac</item>
</string-array>
+ <string name="common_name_prefixes">
+ 1LT, 1ST, 2LT, 2ND, 3RD, ADMIRAL, CAPT, CAPTAIN, COL, CPT, DR,
+ GEN, GENERAL, LCDR, LT, LTC, LTG, LTJG, MAJ, MAJOR, MG, MR,
+ MRS, MS, PASTOR, PROF, REP, REVEREND, REV, SEN, ST
+ </string>
+ <string name="common_name_suffixes">
+ B.A., BA, D.D.S., DDS, I, II, III, IV, IX, JR, M.A., M.D, MA,
+ MD, MS, PH.D., PHD, SR, V, VI, VII, VIII, X
+ </string>
+ <string name="common_last_name_prefixes">
+ D', DE, DEL, DI, LA, LE, MC, SAN, ST, TER, VAN, VON
+ </string>
+ <string name="common_name_conjunctions">
+ &, AND, OR
+ </string>
</resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..90c9d5f
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="common_nicknames"></string-array>
+ <string name="common_name_prefixes"></string>
+ <string name="common_name_suffixes"></string>
+ <string name="common_last_name_prefixes"></string>
+ <string name="common_name_conjunctions"></string>
+</resources>
\ No newline at end of file
diff --git a/src/com/android/providers/contacts2/ContactsProvider2.java b/src/com/android/providers/contacts2/ContactsProvider2.java
index 690783a..eaadb1a 100644
--- a/src/com/android/providers/contacts2/ContactsProvider2.java
+++ b/src/com/android/providers/contacts2/ContactsProvider2.java
@@ -62,6 +62,7 @@
import android.provider.ContactsContract.Aggregates.AggregationSuggestions;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Postal;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;
@@ -371,6 +372,7 @@
private static final AccountComparator sAccountComparator = new AccountComparator();
private ContactAggregator mContactAggregator;
+ private NameSplitter mNameSplitter;
public ContactsProvider2() {
this(new ContactAggregationScheduler());
@@ -400,6 +402,11 @@
"UPDATE " + Tables.DATA + " SET " + Data.IS_SUPER_PRIMARY
+ "=(_id=?) WHERE " + sSetSuperPrimaryWhere);
+ mNameSplitter = new NameSplitter(context.getString(R.string.common_name_prefixes),
+ context.getString(R.string.common_last_name_prefixes),
+ context.getString(R.string.common_name_suffixes),
+ context.getString(R.string.common_name_conjunctions));
+
return (db != null);
}
@@ -712,11 +719,15 @@
values.put(DataColumns.MIMETYPE_ID, mOpenHelper.getMimeTypeId(mimeType));
values.remove(Data.MIMETYPE);
+ if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ parseStructuredName(values);
+ }
+
// Insert the data row itself
id = db.insert(Tables.DATA, Data.DATA1, values);
// If it's a phone number add the normalized version to the lookup table
- if (CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
final ContentValues phoneValues = new ContentValues();
final String number = values.getAsString(Phone.NUMBER);
phoneValues.put(PhoneLookupColumns.NORMALIZED_NUMBER,
@@ -742,6 +753,31 @@
}
/**
+ * Parse the supplied display name, but only if the incoming values do not already contain
+ * structured name parts.
+ */
+ private void parseStructuredName(ContentValues values) {
+ final String fullName = values.getAsString(StructuredName.DISPLAY_NAME);
+ if (TextUtils.isEmpty(fullName)
+ || !TextUtils.isEmpty(values.getAsString(StructuredName.PREFIX))
+ || !TextUtils.isEmpty(values.getAsString(StructuredName.GIVEN_NAME))
+ || !TextUtils.isEmpty(values.getAsString(StructuredName.MIDDLE_NAME))
+ || !TextUtils.isEmpty(values.getAsString(StructuredName.FAMILY_NAME))
+ || !TextUtils.isEmpty(values.getAsString(StructuredName.SUFFIX))) {
+ return;
+ }
+
+ NameSplitter.Name name = new NameSplitter.Name();
+ mNameSplitter.split(name, fullName);
+
+ values.put(StructuredName.PREFIX, name.getPrefix());
+ values.put(StructuredName.GIVEN_NAME, name.getGivenNames());
+ values.put(StructuredName.MIDDLE_NAME, name.getMiddleName());
+ values.put(StructuredName.FAMILY_NAME, name.getFamilyName());
+ values.put(StructuredName.SUFFIX, name.getSuffix());
+ }
+
+ /**
* Inserts a presence update.
*/
private long insertPresence(ContentValues values) {
diff --git a/src/com/android/providers/contacts2/NameSplitter.java b/src/com/android/providers/contacts2/NameSplitter.java
new file mode 100644
index 0000000..803488d
--- /dev/null
+++ b/src/com/android/providers/contacts2/NameSplitter.java
@@ -0,0 +1,297 @@
+/*
+ * 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.contacts2;
+
+import java.util.HashSet;
+import java.util.StringTokenizer;
+
+/**
+ * The purpose of this class is to split a full name into given names and last
+ * name. The logic only supports having a single last name. If the full name has
+ * multiple last names the output will be incorrect.
+ * <p>
+ * Core algorithm:
+ * <ol>
+ * <li>Remove the suffixes (III, Ph.D., M.D.).</li>
+ * <li>Remove the prefixes (Mr., Pastor, Reverend, Sir).</li>
+ * <li>Assign the last remaining token as the last name.</li>
+ * <li>If the previous word to the last name is one from LASTNAME_PREFIXES, use
+ * this word also as the last name.</li>
+ * <li>Assign the rest of the words as the "given names".</li>
+ * </ol>
+ */
+public class NameSplitter {
+
+ private final HashSet<String> mPrefixesSet;
+ private final HashSet<String> mSuffixesSet;
+ private final int mMaxSuffixLength;
+ private final HashSet<String> mLastNamePrefixesSet;
+ private final HashSet<String> mConjuctions;
+
+ public static class Name {
+ private String prefix;
+ private String givenNames;
+ private String middleName;
+ private String familyName;
+ private String suffix;
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public String getGivenNames() {
+ return givenNames;
+ }
+
+ public String getMiddleName() {
+ return middleName;
+ }
+
+ public String getFamilyName() {
+ return familyName;
+ }
+
+ public String getSuffix() {
+ return suffix;
+ }
+ }
+
+ private static class NameTokenizer extends StringTokenizer {
+ private static final int MAX_TOKENS = 10;
+ private final String[] mTokens;
+ private int mDotBitmask;
+ private int mStartPointer;
+ private int mEndPointer;
+
+ public NameTokenizer(String fullName) {
+ super(fullName, " .,", true);
+
+ mTokens = new String[MAX_TOKENS];
+
+ // Iterate over tokens, skipping over empty ones and marking tokens that
+ // are followed by dots.
+ while (hasMoreTokens() && mEndPointer < MAX_TOKENS) {
+ final String token = nextToken();
+ if (token.length() > 0) {
+ final char c = token.charAt(0);
+ if (c == ' ' || c == ',') {
+ continue;
+ }
+ }
+
+ if (mEndPointer > 0 && token.charAt(0) == '.') {
+ mDotBitmask |= (1 << (mEndPointer - 1));
+ } else {
+ mTokens[mEndPointer] = token;
+ mEndPointer++;
+ }
+ }
+ }
+
+ /**
+ * Returns true if the token is followed by a dot in the original full name.
+ */
+ public boolean hasDot(int index) {
+ return (mDotBitmask & (1 << index)) != 0;
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param commonPrefixes comma-separated list of common prefixes,
+ * e.g. "Mr, Ms, Mrs"
+ * @param commonLastNamePrefixes comma-separated list of common last name prefixes,
+ * e.g. "d', st, st., von"
+ * @param commonSuffixes comma-separated list of common suffixes,
+ * e.g. "Jr, M.D., MD, D.D.S."
+ * @param commonConjunctions comma-separated list of common conjuctions,
+ * e.g. "AND, Or"
+ */
+ public NameSplitter(String commonPrefixes, String commonLastNamePrefixes,
+ String commonSuffixes, String commonConjunctions) {
+ mPrefixesSet = convertToSet(commonPrefixes);
+ mLastNamePrefixesSet = convertToSet(commonLastNamePrefixes);
+ mSuffixesSet = convertToSet(commonSuffixes);
+ mConjuctions = convertToSet(commonConjunctions);
+
+ int maxLength = 0;
+ for (String suffix : mSuffixesSet) {
+ if (suffix.length() > maxLength) {
+ maxLength = suffix.length();
+ }
+ }
+
+ mMaxSuffixLength = maxLength;
+ }
+
+ /**
+ * Converts a comma-separated list of Strings to a set of Strings. Trims strings
+ * and converts them to upper case.
+ */
+ private static HashSet<String> convertToSet(String strings) {
+ HashSet<String> set = new HashSet<String>();
+ if (strings != null) {
+ String[] split = strings.split(",");
+ for (int i = 0; i < split.length; i++) {
+ set.add(split[i].trim().toUpperCase());
+ }
+ }
+ return set;
+ }
+
+ /**
+ * Parses a full name and returns parsed components in the Name object.
+ */
+ public void split(Name name, String fullName) {
+ if (fullName == null) {
+ return;
+ }
+
+ NameTokenizer tokens = new NameTokenizer(fullName);
+ parsePrefix(name, tokens);
+ parseSuffix(name, tokens);
+ parseLastName(name, tokens);
+ parseMiddleName(name, tokens);
+ parseGivenNames(name, tokens);
+ }
+
+ /**
+ * Parses the first word from the name if it is a prefix.
+ */
+ private void parsePrefix(Name name, NameTokenizer tokens) {
+ if (tokens.mStartPointer == tokens.mEndPointer) {
+ return;
+ }
+
+ String firstToken = tokens.mTokens[tokens.mStartPointer];
+ if (mPrefixesSet.contains(firstToken.toUpperCase())) {
+ name.prefix = firstToken;
+ tokens.mStartPointer++;
+ }
+ }
+
+ /**
+ * Parses the last word(s) from the name if it is a suffix.
+ */
+ private void parseSuffix(Name name, NameTokenizer tokens) {
+ if (tokens.mStartPointer == tokens.mEndPointer) {
+ return;
+ }
+
+ String lastToken = tokens.mTokens[tokens.mEndPointer - 1];
+ if (lastToken.length() > mMaxSuffixLength) {
+ return;
+ }
+
+ String normalized = lastToken.toUpperCase();
+ if (mSuffixesSet.contains(normalized)) {
+ name.suffix = lastToken;
+ tokens.mEndPointer--;
+ return;
+ }
+
+ if (tokens.hasDot(tokens.mEndPointer - 1)) {
+ lastToken += '.';
+ }
+ normalized += ".";
+
+ // Take care of suffixes like M.D. and D.D.S.
+ int pos = tokens.mEndPointer - 1;
+ while (normalized.length() <= mMaxSuffixLength) {
+
+ if (mSuffixesSet.contains(normalized)) {
+ name.suffix = lastToken;
+ tokens.mEndPointer = pos;
+ return;
+ }
+
+ if (pos == tokens.mStartPointer) {
+ break;
+ }
+
+ pos--;
+ if (tokens.hasDot(pos)) {
+ lastToken = tokens.mTokens[pos] + "." + lastToken;
+ } else {
+ lastToken = tokens.mTokens[pos] + " " + lastToken;
+ }
+
+ normalized = tokens.mTokens[pos].toUpperCase() + "." + normalized;
+ }
+ }
+
+ private void parseLastName(Name name, NameTokenizer tokens) {
+ if (tokens.mStartPointer == tokens.mEndPointer) {
+ return;
+ }
+
+ name.familyName = tokens.mTokens[tokens.mEndPointer - 1];
+ tokens.mEndPointer--;
+
+ // Take care of last names like "D'Onofrio" and "von Cliburn"
+ if ((tokens.mEndPointer - tokens.mStartPointer) > 0) {
+ String lastNamePrefix = tokens.mTokens[tokens.mEndPointer - 1];
+ final String normalized = lastNamePrefix.toUpperCase();
+ if (mLastNamePrefixesSet.contains(normalized)
+ || mLastNamePrefixesSet.contains(normalized + ".")) {
+ if (tokens.hasDot(tokens.mEndPointer - 1)) {
+ lastNamePrefix += '.';
+ }
+ name.familyName = lastNamePrefix + " " + name.familyName;
+ tokens.mEndPointer--;
+ }
+ }
+ }
+
+
+ private void parseMiddleName(Name name, NameTokenizer tokens) {
+ if (tokens.mStartPointer == tokens.mEndPointer) {
+ return;
+ }
+
+ if ((tokens.mEndPointer - tokens.mStartPointer) > 1) {
+ if ((tokens.mEndPointer - tokens.mStartPointer) == 2
+ || !mConjuctions.contains(tokens.mTokens[tokens.mEndPointer - 2].
+ toUpperCase())) {
+ name.middleName = tokens.mTokens[tokens.mEndPointer - 1];
+ tokens.mEndPointer--;
+ }
+ }
+ }
+
+ private void parseGivenNames(Name name, NameTokenizer tokens) {
+ if (tokens.mStartPointer == tokens.mEndPointer) {
+ return;
+ }
+
+ if ((tokens.mEndPointer - tokens.mStartPointer) == 1) {
+ name.givenNames = tokens.mTokens[tokens.mStartPointer];
+ } else {
+ StringBuilder sb = new StringBuilder();
+ for (int i = tokens.mStartPointer; i < tokens.mEndPointer; i++) {
+ if (i != tokens.mStartPointer) {
+ sb.append(' ');
+ }
+ sb.append(tokens.mTokens[i]);
+ if (tokens.hasDot(i)) {
+ sb.append('.');
+ }
+ }
+ name.givenNames = sb.toString();
+ }
+ }
+}
diff --git a/tests/src/com/android/providers/contacts2/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts2/BaseContactsProvider2Test.java
index 313d16e..3ba2ab4 100644
--- a/tests/src/com/android/providers/contacts2/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts2/BaseContactsProvider2Test.java
@@ -61,8 +61,6 @@
protected Uri insertStructuredName(long contactId, String givenName, String familyName) {
ContentValues values = new ContentValues();
- values.put(Data.CONTACT_ID, contactId);
- values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
StringBuilder sb = new StringBuilder();
if (givenName != null) {
sb.append(givenName);
@@ -77,6 +75,12 @@
values.put(StructuredName.GIVEN_NAME, givenName);
values.put(StructuredName.FAMILY_NAME, familyName);
+ return insertStructuredName(contactId, values);
+ }
+
+ protected Uri insertStructuredName(long contactId, ContentValues values) {
+ values.put(Data.CONTACT_ID, contactId);
+ values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
return resultUri;
}
@@ -165,4 +169,26 @@
long aggregateId2 = queryAggregateId(contactId2);
assertTrue(aggregateId1 != aggregateId2);
}
+
+ protected void assertStructuredName(long contactId, String prefix, String givenName,
+ String middleName, String familyName, String suffix) {
+ Uri uri = Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
+ Contacts.Data.CONTENT_DIRECTORY);
+
+ final String[] projection = new String[] {
+ StructuredName.PREFIX, StructuredName.GIVEN_NAME, StructuredName.MIDDLE_NAME,
+ StructuredName.FAMILY_NAME, StructuredName.SUFFIX
+ };
+
+ Cursor c = mResolver.query(uri, projection, Data.MIMETYPE + "='"
+ + StructuredName.CONTENT_ITEM_TYPE + "'", null, null);
+
+ assertTrue(c.moveToFirst());
+ assertEquals(prefix, c.getString(0));
+ assertEquals(givenName, c.getString(1));
+ assertEquals(middleName, c.getString(2));
+ assertEquals(familyName, c.getString(3));
+ assertEquals(suffix, c.getString(4));
+ c.close();
+ }
}
diff --git a/tests/src/com/android/providers/contacts2/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts2/ContactsProvider2Test.java
index f72aa3e..dcf9caa 100644
--- a/tests/src/com/android/providers/contacts2/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts2/ContactsProvider2Test.java
@@ -23,6 +23,9 @@
import android.net.Uri;
import android.provider.ContactsContract.Aggregates;
import android.provider.ContactsContract.AggregationExceptions;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Data;
import android.test.suitebuilder.annotation.LargeTest;
/**
@@ -37,6 +40,25 @@
@LargeTest
public class ContactsProvider2Test extends BaseContactsProvider2Test {
+ public void testDisplayNameParsingWhenPartsUnspecified() {
+ long contactId = createContact();
+ ContentValues values = new ContentValues();
+ values.put(StructuredName.DISPLAY_NAME, "Mr.John Kevin von Smith, Jr.");
+ insertStructuredName(contactId, values);
+
+ assertStructuredName(contactId, "Mr", "John", "Kevin", "von Smith", "Jr");
+ }
+
+ public void testDisplayNameParsingWhenPartsSpecified() {
+ long contactId = createContact();
+ ContentValues values = new ContentValues();
+ values.put(StructuredName.DISPLAY_NAME, "Mr.John Kevin von Smith, Jr.");
+ values.put(StructuredName.FAMILY_NAME, "Johnson");
+ insertStructuredName(contactId, values);
+
+ assertStructuredName(contactId, null, null, null, "Johnson", null);
+ }
+
public void testSendToVoicemailDefault() {
long contactId = createContact();
long aggregateId = queryAggregateId(contactId);
diff --git a/tests/src/com/android/providers/contacts2/NameSplitterTest.java b/tests/src/com/android/providers/contacts2/NameSplitterTest.java
new file mode 100644
index 0000000..22b3f7f
--- /dev/null
+++ b/tests/src/com/android/providers/contacts2/NameSplitterTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.contacts2;
+
+import junit.framework.TestCase;
+
+import com.android.providers.contacts2.NameSplitter.Name;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Tests for {@link NameSplitter}.
+ */
+@SmallTest
+public class NameSplitterTest extends TestCase {
+ private NameSplitter mNameSplitter;
+ private Name mName;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mNameSplitter = new NameSplitter("Mr, Ms, Mrs", "d', st, st., von", "Jr, M.D., MD, D.D.S.",
+ "&, AND");
+ mName = new Name();
+ }
+
+ public void testNull() {
+ assertSplitName(null, null, null, null, null, null);
+ }
+
+ public void testEmpty() {
+ assertSplitName("", null, null, null, null, null);
+ }
+
+ public void testSpaces() {
+ assertSplitName(" ", null, null, null, null, null);
+ }
+
+ public void testLastName() {
+ assertSplitName("Smith", null, null, null, "Smith", null);
+ }
+
+ public void testFirstLastName() {
+ assertSplitName("John Smith", null, "John", null, "Smith", null);
+ }
+
+ public void testFirstMiddleLastName() {
+ assertSplitName("John Edward Smith", null, "John", "Edward", "Smith", null);
+ }
+
+ public void testThreeNamesAndLastName() {
+ assertSplitName("John Edward Kevin Smith", null, "John Edward", "Kevin", "Smith", null);
+ }
+
+ public void testPrefixFirstLastName() {
+ assertSplitName("Mr. John Smith", "Mr", "John", null, "Smith", null);
+ assertSplitName("Mr.John Smith", "Mr", "John", null, "Smith", null);
+ }
+
+ public void testFirstLastNameSuffix() {
+ assertSplitName("John Smith Jr.", null, "John", null, "Smith", "Jr");
+ }
+
+ public void testFirstLastNameSuffixWithDot() {
+ assertSplitName("John Smith M.D.", null, "John", null, "Smith", "M.D.");
+ assertSplitName("John Smith D D S", null, "John", null, "Smith", "D D S");
+ }
+
+ public void testFirstSuffixLastName() {
+ assertSplitName("John von Smith", null, "John", null, "von Smith", null);
+ }
+
+ public void testFirstSuffixLastNameWithDot() {
+ assertSplitName("John St.Smith", null, "John", null, "St. Smith", null);
+ }
+
+ public void testPrefixFirstMiddleLast() {
+ assertSplitName("Mr. John Kevin Smith", "Mr", "John", "Kevin", "Smith", null);
+ assertSplitName("Mr.John Kevin Smith", "Mr", "John", "Kevin", "Smith", null);
+ }
+
+ public void testPrefixFirstMiddleLastSuffix() {
+ assertSplitName("Mr. John Kevin Smith Jr.", "Mr", "John", "Kevin", "Smith", "Jr");
+ }
+
+ public void testPrefixFirstMiddlePrefixLastSuffixWrongCapitalization() {
+ assertSplitName("MR. john keVin VON SmiTh JR.", "MR", "john", "keVin", "VON SmiTh", "JR");
+ }
+
+ public void testPrefixLastSuffix() {
+ assertSplitName("von Smith Jr.", null, null, null, "von Smith", "Jr");
+ }
+
+ public void testTwoNamesAndLastNameWithAmpersand() {
+ assertSplitName("John & Edward Smith", null, "John & Edward", null, "Smith", null);
+ assertSplitName("John and Edward Smith", null, "John and Edward", null, "Smith", null);
+ }
+
+ public void testWithMiddleInitialAndNoDot() {
+ assertSplitName("John E. Smith", null, "John", "E", "Smith", null);
+ }
+
+ public void testWithLongFirstNameAndDot() {
+ assertSplitName("John Ed. K. Smith", null, "John Ed.", "K", "Smith", null);
+ }
+
+ private void assertSplitName(String fullName, String prefix, String givenNames,
+ String middleName, String lastName, String suffix) {
+ mNameSplitter.split(mName, fullName);
+ assertEquals(prefix, mName.getPrefix());
+ assertEquals(givenNames, mName.getGivenNames());
+ assertEquals(middleName, mName.getMiddleName());
+ assertEquals(lastName, mName.getFamilyName());
+ assertEquals(suffix, mName.getSuffix());
+ }
+}