1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.inputmethod.latin.utils;
18
19import java.util.Locale;
20
21/**
22 * The status of the current recapitalize process.
23 */
24public class RecapitalizeStatus {
25    public static final int NOT_A_RECAPITALIZE_MODE = -1;
26    public static final int CAPS_MODE_ORIGINAL_MIXED_CASE = 0;
27    public static final int CAPS_MODE_ALL_LOWER = 1;
28    public static final int CAPS_MODE_FIRST_WORD_UPPER = 2;
29    public static final int CAPS_MODE_ALL_UPPER = 3;
30    // When adding a new mode, don't forget to update the CAPS_MODE_LAST constant.
31    public static final int CAPS_MODE_LAST = CAPS_MODE_ALL_UPPER;
32
33    private static final int[] ROTATION_STYLE = {
34        CAPS_MODE_ORIGINAL_MIXED_CASE,
35        CAPS_MODE_ALL_LOWER,
36        CAPS_MODE_FIRST_WORD_UPPER,
37        CAPS_MODE_ALL_UPPER
38    };
39
40    private static final int getStringMode(final String string, final int[] sortedSeparators) {
41        if (StringUtils.isIdenticalAfterUpcase(string)) {
42            return CAPS_MODE_ALL_UPPER;
43        } else if (StringUtils.isIdenticalAfterDowncase(string)) {
44            return CAPS_MODE_ALL_LOWER;
45        } else if (StringUtils.isIdenticalAfterCapitalizeEachWord(string, sortedSeparators)) {
46            return CAPS_MODE_FIRST_WORD_UPPER;
47        } else {
48            return CAPS_MODE_ORIGINAL_MIXED_CASE;
49        }
50    }
51
52    /**
53     * We store the location of the cursor and the string that was there before the recapitalize
54     * action was done, and the location of the cursor and the string that was there after.
55     */
56    private int mCursorStartBefore;
57    private String mStringBefore;
58    private int mCursorStartAfter;
59    private int mCursorEndAfter;
60    private int mRotationStyleCurrentIndex;
61    private boolean mSkipOriginalMixedCaseMode;
62    private Locale mLocale;
63    private int[] mSortedSeparators;
64    private String mStringAfter;
65    private boolean mIsStarted;
66    private boolean mIsEnabled = true;
67
68    private static final int[] EMPTY_STORTED_SEPARATORS = {};
69
70    public RecapitalizeStatus() {
71        // By default, initialize with dummy values that won't match any real recapitalize.
72        start(-1, -1, "", Locale.getDefault(), EMPTY_STORTED_SEPARATORS);
73        stop();
74    }
75
76    public void start(final int cursorStart, final int cursorEnd, final String string,
77            final Locale locale, final int[] sortedSeparators) {
78        if (!mIsEnabled) {
79            return;
80        }
81        mCursorStartBefore = cursorStart;
82        mStringBefore = string;
83        mCursorStartAfter = cursorStart;
84        mCursorEndAfter = cursorEnd;
85        mStringAfter = string;
86        final int initialMode = getStringMode(mStringBefore, sortedSeparators);
87        mLocale = locale;
88        mSortedSeparators = sortedSeparators;
89        if (CAPS_MODE_ORIGINAL_MIXED_CASE == initialMode) {
90            mRotationStyleCurrentIndex = 0;
91            mSkipOriginalMixedCaseMode = false;
92        } else {
93            // Find the current mode in the array.
94            int currentMode;
95            for (currentMode = ROTATION_STYLE.length - 1; currentMode > 0; --currentMode) {
96                if (ROTATION_STYLE[currentMode] == initialMode) {
97                    break;
98                }
99            }
100            mRotationStyleCurrentIndex = currentMode;
101            mSkipOriginalMixedCaseMode = true;
102        }
103        mIsStarted = true;
104    }
105
106    public void stop() {
107        mIsStarted = false;
108    }
109
110    public boolean isStarted() {
111        return mIsStarted;
112    }
113
114    public void enable() {
115        mIsEnabled = true;
116    }
117
118    public void disable() {
119        mIsEnabled = false;
120    }
121
122    public boolean mIsEnabled() {
123        return mIsEnabled;
124    }
125
126    public boolean isSetAt(final int cursorStart, final int cursorEnd) {
127        return cursorStart == mCursorStartAfter && cursorEnd == mCursorEndAfter;
128    }
129
130    /**
131     * Rotate through the different possible capitalization modes.
132     */
133    public void rotate() {
134        final String oldResult = mStringAfter;
135        int count = 0; // Protection against infinite loop.
136        do {
137            mRotationStyleCurrentIndex = (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length;
138            if (CAPS_MODE_ORIGINAL_MIXED_CASE == ROTATION_STYLE[mRotationStyleCurrentIndex]
139                    && mSkipOriginalMixedCaseMode) {
140                mRotationStyleCurrentIndex =
141                        (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length;
142            }
143            ++count;
144            switch (ROTATION_STYLE[mRotationStyleCurrentIndex]) {
145            case CAPS_MODE_ORIGINAL_MIXED_CASE:
146                mStringAfter = mStringBefore;
147                break;
148            case CAPS_MODE_ALL_LOWER:
149                mStringAfter = mStringBefore.toLowerCase(mLocale);
150                break;
151            case CAPS_MODE_FIRST_WORD_UPPER:
152                mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSortedSeparators,
153                        mLocale);
154                break;
155            case CAPS_MODE_ALL_UPPER:
156                mStringAfter = mStringBefore.toUpperCase(mLocale);
157                break;
158            default:
159                mStringAfter = mStringBefore;
160            }
161        } while (mStringAfter.equals(oldResult) && count < ROTATION_STYLE.length + 1);
162        mCursorEndAfter = mCursorStartAfter + mStringAfter.length();
163    }
164
165    /**
166     * Remove leading/trailing whitespace from the considered string.
167     */
168    public void trim() {
169        final int len = mStringBefore.length();
170        int nonWhitespaceStart = 0;
171        for (; nonWhitespaceStart < len;
172                nonWhitespaceStart = mStringBefore.offsetByCodePoints(nonWhitespaceStart, 1)) {
173            final int codePoint = mStringBefore.codePointAt(nonWhitespaceStart);
174            if (!Character.isWhitespace(codePoint)) break;
175        }
176        int nonWhitespaceEnd = len;
177        for (; nonWhitespaceEnd > 0;
178                nonWhitespaceEnd = mStringBefore.offsetByCodePoints(nonWhitespaceEnd, -1)) {
179            final int codePoint = mStringBefore.codePointBefore(nonWhitespaceEnd);
180            if (!Character.isWhitespace(codePoint)) break;
181        }
182        // If nonWhitespaceStart >= nonWhitespaceEnd, that means the selection contained only
183        // whitespace, so we leave it as is.
184        if ((0 != nonWhitespaceStart || len != nonWhitespaceEnd)
185                && nonWhitespaceStart < nonWhitespaceEnd) {
186            mCursorEndAfter = mCursorStartBefore + nonWhitespaceEnd;
187            mCursorStartBefore = mCursorStartAfter = mCursorStartBefore + nonWhitespaceStart;
188            mStringAfter = mStringBefore =
189                    mStringBefore.substring(nonWhitespaceStart, nonWhitespaceEnd);
190        }
191    }
192
193    public String getRecapitalizedString() {
194        return mStringAfter;
195    }
196
197    public int getNewCursorStart() {
198        return mCursorStartAfter;
199    }
200
201    public int getNewCursorEnd() {
202        return mCursorEndAfter;
203    }
204
205    public int getCurrentMode() {
206        return ROTATION_STYLE[mRotationStyleCurrentIndex];
207    }
208}
209