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
17package com.android.inputmethod.latin;
18
19import android.text.TextUtils;
20
21import java.util.ArrayList;
22import java.util.Locale;
23
24public 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