1/*
2 * Copyright (C) 2011 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 */
16package com.android.contacts.format;
17
18import android.database.CharArrayBuffer;
19import android.graphics.Typeface;
20import android.text.SpannableString;
21import android.text.style.StyleSpan;
22
23import com.google.common.annotations.VisibleForTesting;
24
25import java.util.Arrays;
26
27/**
28 * Assorted utility methods related to text formatting in Contacts.
29 */
30public class FormatUtils {
31
32    /**
33     * Finds the earliest point in buffer1 at which the first part of buffer2 matches.  For example,
34     * overlapPoint("abcd", "cdef") == 2.
35     */
36    public static int overlapPoint(CharArrayBuffer buffer1, CharArrayBuffer buffer2) {
37        if (buffer1 == null || buffer2 == null) {
38            return -1;
39        }
40        return overlapPoint(Arrays.copyOfRange(buffer1.data, 0, buffer1.sizeCopied),
41                Arrays.copyOfRange(buffer2.data, 0, buffer2.sizeCopied));
42    }
43
44    /**
45     * Finds the earliest point in string1 at which the first part of string2 matches.  For example,
46     * overlapPoint("abcd", "cdef") == 2.
47     */
48    @VisibleForTesting
49    public static int overlapPoint(String string1, String string2) {
50        if (string1 == null || string2 == null) {
51            return -1;
52        }
53        return overlapPoint(string1.toCharArray(), string2.toCharArray());
54    }
55
56    /**
57     * Finds the earliest point in array1 at which the first part of array2 matches.  For example,
58     * overlapPoint("abcd", "cdef") == 2.
59     */
60    public static int overlapPoint(char[] array1, char[] array2) {
61        if (array1 == null || array2 == null) {
62            return -1;
63        }
64        int count1 = array1.length;
65        int count2 = array2.length;
66
67        // Ignore matching tails of the two arrays.
68        while (count1 > 0 && count2 > 0 && array1[count1 - 1] == array2[count2 - 1]) {
69            count1--;
70            count2--;
71        }
72
73        int size = count2;
74        for (int i = 0; i < count1; i++) {
75            if (i + size > count1) {
76                size = count1 - i;
77            }
78            int j;
79            for (j = 0; j < size; j++) {
80                if (array1[i+j] != array2[j]) {
81                    break;
82                }
83            }
84            if (j == size) {
85                return i;
86            }
87        }
88
89        return -1;
90    }
91
92    /**
93     * Applies the given style to a range of the input CharSequence.
94     * @param style The style to apply (see the style constants in {@link Typeface}).
95     * @param input The CharSequence to style.
96     * @param start Starting index of the range to style (will be clamped to be a minimum of 0).
97     * @param end Ending index of the range to style (will be clamped to a maximum of the input
98     *     length).
99     * @param flags Bitmask for configuring behavior of the span.  See {@link android.text.Spanned}.
100     * @return The styled CharSequence.
101     */
102    public static CharSequence applyStyleToSpan(int style, CharSequence input, int start, int end,
103            int flags) {
104        // Enforce bounds of the char sequence.
105        start = Math.max(0, start);
106        end = Math.min(input.length(), end);
107        SpannableString text = new SpannableString(input);
108        text.setSpan(new StyleSpan(style), start, end, flags);
109        return text;
110    }
111
112    @VisibleForTesting
113    public static void copyToCharArrayBuffer(String text, CharArrayBuffer buffer) {
114        if (text != null) {
115            char[] data = buffer.data;
116            if (data == null || data.length < text.length()) {
117                buffer.data = text.toCharArray();
118            } else {
119                text.getChars(0, text.length(), data, 0);
120            }
121            buffer.sizeCopied = text.length();
122        } else {
123            buffer.sizeCopied = 0;
124        }
125    }
126
127    /** Returns a String that represents the content of the given {@link CharArrayBuffer}. */
128    @VisibleForTesting
129    public static String charArrayBufferToString(CharArrayBuffer buffer) {
130        return new String(buffer.data, 0, buffer.sizeCopied);
131    }
132
133    /**
134     * Finds the index of the first word that starts with the given prefix.
135     * <p>
136     * If not found, returns -1.
137     *
138     * @param text the text in which to search for the prefix
139     * @param prefix the text to find, in upper case letters
140     */
141    public static int indexOfWordPrefix(CharSequence text, String prefix) {
142        if (prefix == null || text == null) {
143            return -1;
144        }
145
146        int textLength = text.length();
147        int prefixLength = prefix.length();
148
149        if (prefixLength == 0 || textLength < prefixLength) {
150            return -1;
151        }
152
153        int i = 0;
154        while (i < textLength) {
155            // Skip non-word characters
156            while (i < textLength && !Character.isLetterOrDigit(text.charAt(i))) {
157                i++;
158            }
159
160            if (i + prefixLength > textLength) {
161                return -1;
162            }
163
164            // Compare the prefixes
165            int j;
166            for (j = 0; j < prefixLength; j++) {
167                if (Character.toUpperCase(text.charAt(i + j)) != prefix.charAt(j)) {
168                    break;
169                }
170            }
171            if (j == prefixLength) {
172                return i;
173            }
174
175            // Skip this word
176            while (i < textLength && Character.isLetterOrDigit(text.charAt(i))) {
177                i++;
178            }
179        }
180
181        return -1;
182    }
183
184}
185