1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.latin; 18 19 import android.text.TextUtils; 20 21 import java.util.ArrayList; 22 import java.util.Locale; 23 24 public class StringUtils { 25 private StringUtils() { 26 // This utility class is not publicly instantiable. 27 } 28 29 public static int codePointCount(String text) { 30 if (TextUtils.isEmpty(text)) return 0; 31 return text.codePointCount(0, text.length()); 32 } 33 34 public static boolean containsInArray(String key, String[] array) { 35 for (final String element : array) { 36 if (key.equals(element)) return true; 37 } 38 return false; 39 } 40 41 public static boolean containsInCsv(String key, String csv) { 42 if (TextUtils.isEmpty(csv)) return false; 43 return containsInArray(key, csv.split(",")); 44 } 45 46 public static String appendToCsvIfNotExists(String key, String csv) { 47 if (TextUtils.isEmpty(csv)) return key; 48 if (containsInCsv(key, csv)) return csv; 49 return csv + "," + key; 50 } 51 52 public static String removeFromCsvIfExists(String key, String csv) { 53 if (TextUtils.isEmpty(csv)) return ""; 54 final String[] elements = csv.split(","); 55 if (!containsInArray(key, elements)) return csv; 56 final ArrayList<String> result = new ArrayList<String>(elements.length - 1); 57 for (final String element : elements) { 58 if (!key.equals(element)) result.add(element); 59 } 60 return TextUtils.join(",", result); 61 } 62 63 /** 64 * Returns true if a and b are equal ignoring the case of the character. 65 * @param a first character to check 66 * @param b second character to check 67 * @return {@code true} if a and b are equal, {@code false} otherwise. 68 */ 69 public static boolean equalsIgnoreCase(char a, char b) { 70 // Some language, such as Turkish, need testing both cases. 71 return a == b 72 || Character.toLowerCase(a) == Character.toLowerCase(b) 73 || Character.toUpperCase(a) == Character.toUpperCase(b); 74 } 75 76 /** 77 * Returns true if a and b are equal ignoring the case of the characters, including if they are 78 * both null. 79 * @param a first CharSequence to check 80 * @param b second CharSequence to check 81 * @return {@code true} if a and b are equal, {@code false} otherwise. 82 */ 83 public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) { 84 if (a == b) 85 return true; // including both a and b are null. 86 if (a == null || b == null) 87 return false; 88 final int length = a.length(); 89 if (length != b.length()) 90 return false; 91 for (int i = 0; i < length; i++) { 92 if (!equalsIgnoreCase(a.charAt(i), b.charAt(i))) 93 return false; 94 } 95 return true; 96 } 97 98 /** 99 * Returns true if a and b are equal ignoring the case of the characters, including if a is null 100 * and b is zero length. 101 * @param a CharSequence to check 102 * @param b character array to check 103 * @param offset start offset of array b 104 * @param length length of characters in array b 105 * @return {@code true} if a and b are equal, {@code false} otherwise. 106 * @throws IndexOutOfBoundsException 107 * if {@code offset < 0 || length < 0 || offset + length > data.length}. 108 * @throws NullPointerException if {@code b == null}. 109 */ 110 public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) { 111 if (offset < 0 || length < 0 || length > b.length - offset) 112 throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset 113 + " length=" + length); 114 if (a == null) 115 return length == 0; // including a is null and b is zero length. 116 if (a.length() != length) 117 return false; 118 for (int i = 0; i < length; i++) { 119 if (!equalsIgnoreCase(a.charAt(i), b[offset + i])) 120 return false; 121 } 122 return true; 123 } 124 125 /** 126 * Returns true if cs contains any upper case characters. 127 * 128 * @param cs the CharSequence to check 129 * @return {@code true} if cs contains any upper case characters, {@code false} otherwise. 130 */ 131 public static boolean hasUpperCase(final CharSequence cs) { 132 final int length = cs.length(); 133 for (int i = 0, cp = 0; i < length; i += Character.charCount(cp)) { 134 cp = Character.codePointAt(cs, i); 135 if (Character.isUpperCase(cp)) { 136 return true; 137 } 138 } 139 return false; 140 } 141 142 /** 143 * Remove duplicates from an array of strings. 144 * 145 * This method will always keep the first occurrence of all strings at their position 146 * in the array, removing the subsequent ones. 147 */ 148 public static void removeDupes(final ArrayList<CharSequence> suggestions) { 149 if (suggestions.size() < 2) return; 150 int i = 1; 151 // Don't cache suggestions.size(), since we may be removing items 152 while (i < suggestions.size()) { 153 final CharSequence cur = suggestions.get(i); 154 // Compare each suggestion with each previous suggestion 155 for (int j = 0; j < i; j++) { 156 CharSequence previous = suggestions.get(j); 157 if (TextUtils.equals(cur, previous)) { 158 suggestions.remove(i); 159 i--; 160 break; 161 } 162 } 163 i++; 164 } 165 } 166 167 public static String toTitleCase(String s, Locale locale) { 168 if (s.length() <= 1) { 169 // TODO: is this really correct? Shouldn't this be s.toUpperCase()? 170 return s; 171 } 172 // TODO: fix the bugs below 173 // - This does not work for Greek, because it returns upper case instead of title case. 174 // - It does not work for Serbian, because it fails to account for the "lj" character, 175 // which should be "Lj" in title case and "LJ" in upper case. 176 // - It does not work for Dutch, because it fails to account for the "ij" digraph, which 177 // are two different characters but both should be capitalized as "IJ" as if they were 178 // a single letter. 179 // - It also does not work with unicode surrogate code points. 180 return s.toUpperCase(locale).charAt(0) + s.substring(1); 181 } 182 183 public static int[] toCodePointArray(final String string) { 184 final char[] characters = string.toCharArray(); 185 final int length = characters.length; 186 final int[] codePoints = new int[Character.codePointCount(characters, 0, length)]; 187 int codePoint = Character.codePointAt(characters, 0); 188 int dsti = 0; 189 for (int srci = Character.charCount(codePoint); 190 srci < length; srci += Character.charCount(codePoint), ++dsti) { 191 codePoints[dsti] = codePoint; 192 codePoint = Character.codePointAt(characters, srci); 193 } 194 codePoints[dsti] = codePoint; 195 return codePoints; 196 } 197 } 198