1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.codec.language;
19
20 import java.util.Locale;
21
22 import org.apache.commons.codec.EncoderException;
23 import org.apache.commons.codec.StringEncoder;
24
25 /**
26 * Utility methods for {@link Soundex} and {@link RefinedSoundex} classes.
27 * <p>
28 * This class is immutable and thread-safe.
29 * </p>
30 *
31 * @since 1.3
32 */
33 final class SoundexUtils {
34
35 /**
36 * Cleans up the input string before Soundex processing by only returning upper case letters.
37 *
38 * @param str The String to clean.
39 * @return A clean String.
40 */
41 static String clean(final String str) {
42 if (isEmpty(str)) {
43 return str;
44 }
45 final int len = str.length();
46 final char[] chars = new char[len];
47 int count = 0;
48 for (int i = 0; i < len; i++) {
49 if (Character.isLetter(str.charAt(i))) {
50 chars[count++] = str.charAt(i);
51 }
52 }
53 if (count == len) {
54 return str.toUpperCase(Locale.ENGLISH);
55 }
56 return new String(chars, 0, count).toUpperCase(Locale.ENGLISH);
57 }
58
59 /**
60 * Encodes the Strings and returns the number of characters in the two encoded Strings that are the same.
61 * <ul>
62 * <li>For Soundex, this return value ranges from 0 through 4: 0 indicates little or no similarity, and 4 indicates strong similarity or identical
63 * values.</li>
64 * <li>For refined Soundex, the return value can be greater than 4.</li>
65 * </ul>
66 *
67 * @param encoder The encoder to use to encode the Strings.
68 * @param s1 A String that will be encoded and compared.
69 * @param s2 A String that will be encoded and compared.
70 * @return The number of characters in the two Soundex encoded Strings that are the same.
71 * @see #differenceEncoded(String,String)
72 * @see <a href="https://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_de-dz_8co5.asp"> MS T-SQL DIFFERENCE</a>
73 * @throws EncoderException Thrown if an error occurs encoding one of the strings.
74 */
75 static int difference(final StringEncoder encoder, final String s1, final String s2) throws EncoderException {
76 return differenceEncoded(encoder.encode(s1), encoder.encode(s2));
77 }
78
79 /**
80 * Returns the number of characters in the two Soundex encoded Strings that are the same.
81 * <ul>
82 * <li>For Soundex, this return value ranges from 0 through 4: 0 indicates little or no similarity, and 4 indicates strong similarity or identical
83 * values.</li>
84 * <li>For refined Soundex, the return value can be greater than 4.</li>
85 * </ul>
86 *
87 * @param es1 An encoded String.
88 * @param es2 An encoded String.
89 * @return The number of characters in the two Soundex encoded Strings that are the same.
90 * @see <a href="https://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_de-dz_8co5.asp"> MS T-SQL DIFFERENCE</a>
91 */
92 static int differenceEncoded(final String es1, final String es2) {
93 if (es1 == null || es2 == null) {
94 return 0;
95 }
96 final int lengthToMatch = Math.min(es1.length(), es2.length());
97 int diff = 0;
98 for (int i = 0; i < lengthToMatch; i++) {
99 if (es1.charAt(i) == es2.charAt(i)) {
100 diff++;
101 }
102 }
103 return diff;
104 }
105
106 /**
107 * Tests if a CharSequence is empty ("") or null.
108 *
109 * <pre>
110 * StringUtils.isEmpty(null) = true
111 * StringUtils.isEmpty("") = true
112 * StringUtils.isEmpty(" ") = false
113 * StringUtils.isEmpty("bob") = false
114 * StringUtils.isEmpty(" bob ") = false
115 * </pre>
116 *
117 * @param cs The CharSequence to check, may be null.
118 * @return {@code true} if the CharSequence is empty or null.
119 */
120 static boolean isEmpty(final CharSequence cs) {
121 return cs == null || cs.length() == 0;
122 }
123 }