ExpectedKey.java revision c9aa1beb6de6bbea71af8eba94354bff3001e0ac
1/*
2 * Copyright (C) 2014 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.keyboard.layout.expected;
18
19import com.android.inputmethod.keyboard.Key;
20import com.android.inputmethod.keyboard.internal.MoreKeySpec;
21
22import java.util.ArrayList;
23import java.util.Arrays;
24import java.util.Locale;
25
26/**
27 * This class represents an expected key.
28 */
29public class ExpectedKey {
30    static ExpectedKey EMPTY_KEY = newInstance("");
31
32    // A key that has a string label and may have "more keys".
33    static ExpectedKey newInstance(final String label, final ExpectedKey... moreKeys) {
34        return newInstance(label, label, moreKeys);
35    }
36
37    // A key that has a string label and a different output text and may have "more keys".
38    static ExpectedKey newInstance(final String label, final String outputText,
39            final ExpectedKey... moreKeys) {
40        return newInstance(ExpectedKeyVisual.newInstance(label),
41                ExpectedKeyOutput.newInstance(outputText), moreKeys);
42    }
43
44    // A key that has a string label and a code point output and may have "more keys".
45    static ExpectedKey newInstance(final String label, final int code,
46            final ExpectedKey... moreKeys) {
47        return newInstance(ExpectedKeyVisual.newInstance(label),
48                ExpectedKeyOutput.newInstance(code), moreKeys);
49    }
50
51    // A key that has an icon and an output text and may have "more keys".
52    static ExpectedKey newInstance(final int iconId, final String outputText,
53            final ExpectedKey... moreKeys) {
54        return newInstance(ExpectedKeyVisual.newInstance(iconId),
55                ExpectedKeyOutput.newInstance(outputText), moreKeys);
56    }
57
58    // A key that has an icon and a code point output and may have "more keys".
59    static ExpectedKey newInstance(final int iconId, final int code,
60            final ExpectedKey... moreKeys) {
61        return newInstance(ExpectedKeyVisual.newInstance(iconId),
62                ExpectedKeyOutput.newInstance(code), moreKeys);
63    }
64
65    static ExpectedKey newInstance(final ExpectedKeyVisual visual, final ExpectedKeyOutput output,
66            final ExpectedKey... moreKeys) {
67        if (moreKeys.length == 0) {
68            return new ExpectedKey(visual, output);
69        }
70        // The more keys are the extra keys that the main keyboard key may have in its long press
71        // popup keyboard.
72        // The additional more keys can be defined independently from other more keys.
73        // The position of the additional more keys in the long press popup keyboard can be
74        // controlled by specifying special marker "%" in the usual more keys definitions.
75        final ArrayList<ExpectedKey> moreKeysList = new ArrayList<>();
76        final ArrayList<ExpectedAdditionalMoreKey> additionalMoreKeys = new ArrayList<>();
77        int firstAdditionalMoreKeyIndex = -1;
78        for (int index = 0; index < moreKeys.length; index++) {
79            final ExpectedKey moreKey = moreKeys[index];
80            if (moreKey instanceof ExpectedAdditionalMoreKey) {
81                additionalMoreKeys.add((ExpectedAdditionalMoreKey) moreKey);
82                if (firstAdditionalMoreKeyIndex < 0) {
83                    firstAdditionalMoreKeyIndex = index;
84                }
85            } else {
86                moreKeysList.add(moreKey);
87            }
88        }
89        if (additionalMoreKeys.isEmpty()) {
90            return new ExpectedKeyWithMoreKeys(visual, output, moreKeys);
91        }
92        final ExpectedKey[] moreKeysArray = moreKeysList.toArray(
93                new ExpectedKey[moreKeysList.size()]);
94        final ExpectedAdditionalMoreKey[] additionalMoreKeysArray = additionalMoreKeys.toArray(
95                new ExpectedAdditionalMoreKey[additionalMoreKeys.size()]);
96        return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(
97                visual, output, moreKeysArray, firstAdditionalMoreKeyIndex,
98                additionalMoreKeysArray);
99    }
100
101    private static final ExpectedKey[] EMPTY_KEYS = new ExpectedKey[0];
102
103    // The expected visual outlook of this key.
104    private final ExpectedKeyVisual mVisual;
105    // The expected output of this key.
106    private final ExpectedKeyOutput mOutput;
107
108    public final ExpectedKeyVisual getVisual() {
109        return mVisual;
110    }
111
112    public final ExpectedKeyOutput getOutput() {
113        return mOutput;
114    }
115
116    public ExpectedKey[] getMoreKeys() {
117        // This key has no "more keys".
118        return EMPTY_KEYS;
119    }
120
121    public ExpectedKey setMoreKeys(final ExpectedKey... moreKeys) {
122        return newInstance(mVisual, mOutput, moreKeys);
123    }
124
125    public ExpectedKey setAdditionalMoreKeys(
126            final ExpectedAdditionalMoreKey... additionalMoreKeys) {
127        if (additionalMoreKeys.length == 0) {
128            return this;
129        }
130        return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(
131                mVisual, mOutput, EMPTY_KEYS, 0 /* additionalMoreKeysIndex */, additionalMoreKeys);
132    }
133
134    public ExpectedKey setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex) {
135        if (additionalMoreKeysIndex == 0) {
136            return this;
137        }
138        return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(
139                mVisual, mOutput, EMPTY_KEYS, additionalMoreKeysIndex);
140    }
141
142    protected ExpectedKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output) {
143        mVisual = visual;
144        mOutput = output;
145    }
146
147    public ExpectedKey toUpperCase(Locale locale) {
148        return newInstance(mVisual.toUpperCase(locale), mOutput.toUpperCase(locale));
149    }
150
151    public ExpectedKey preserveCase() {
152        final ExpectedKey[] moreKeys = getMoreKeys();
153        final ExpectedKey[] casePreservedMoreKeys = new ExpectedKey[moreKeys.length];
154        for (int index = 0; index < moreKeys.length; index++) {
155            final ExpectedKey moreKey = moreKeys[index];
156            casePreservedMoreKeys[index] = newInstance(
157                    moreKey.getVisual().preserveCase(), moreKey.getOutput().preserveCase());
158        }
159        return newInstance(
160                getVisual().preserveCase(), getOutput().preserveCase(), casePreservedMoreKeys);
161    }
162
163    public boolean equalsTo(final Key key) {
164        // This key has no "more keys".
165        return mVisual.equalsTo(key) && mOutput.equalsTo(key) && key.getMoreKeys() == null;
166    }
167
168    public boolean equalsTo(final MoreKeySpec moreKeySpec) {
169        return mVisual.equalsTo(moreKeySpec) && mOutput.equalsTo(moreKeySpec);
170    }
171
172    @Override
173    public boolean equals(final Object object) {
174        if (object instanceof ExpectedKey) {
175            final ExpectedKey key = (ExpectedKey) object;
176            return mVisual.equalsTo(key.mVisual) && mOutput.equalsTo(key.mOutput)
177                    && Arrays.equals(getMoreKeys(), key.getMoreKeys());
178        }
179        return false;
180    }
181
182    private static int hashCode(final Object... objects) {
183        return Arrays.hashCode(objects);
184    }
185
186    @Override
187    public int hashCode() {
188        return hashCode(mVisual, mOutput, getMoreKeys());
189    }
190
191    @Override
192    public String toString() {
193        if (mVisual.equalsTo(mOutput)) {
194            return mVisual.toString();
195        }
196        return mVisual + "|" + mOutput;
197    }
198
199    /**
200     * This class represents an expected "additional more key".
201     *
202     * The additional more keys can be defined independently from other more keys. The position of
203     * the additional more keys in the long press popup keyboard can be controlled by specifying
204     * special marker "%" in the usual more keys definitions.
205     */
206    public static class ExpectedAdditionalMoreKey extends ExpectedKey {
207        public static ExpectedAdditionalMoreKey newInstance(final String label) {
208            return new ExpectedAdditionalMoreKey(ExpectedKeyVisual.newInstance(label),
209                    ExpectedKeyOutput.newInstance(label));
210        }
211
212        public static ExpectedAdditionalMoreKey newInstance(final ExpectedKey key) {
213            return new ExpectedAdditionalMoreKey(key.getVisual(), key.getOutput());
214        }
215
216        ExpectedAdditionalMoreKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output) {
217            super(visual, output);
218        }
219
220        @Override
221        public ExpectedAdditionalMoreKey toUpperCase(final Locale locale) {
222            final ExpectedKey upperCaseKey = super.toUpperCase(locale);
223            return new ExpectedAdditionalMoreKey(
224                    upperCaseKey.getVisual(), upperCaseKey.getOutput());
225        }
226    }
227
228    /**
229     * This class represents an expected key that has "more keys".
230     */
231    private static class ExpectedKeyWithMoreKeys extends ExpectedKey {
232        private final ExpectedKey[] mMoreKeys;
233
234        ExpectedKeyWithMoreKeys(final ExpectedKeyVisual visual, final ExpectedKeyOutput output,
235                final ExpectedKey... moreKeys) {
236            super(visual, output);
237            mMoreKeys = moreKeys;
238        }
239
240        @Override
241        public ExpectedKey toUpperCase(final Locale locale) {
242            final ExpectedKey[] upperCaseMoreKeys = new ExpectedKey[mMoreKeys.length];
243            for (int i = 0; i < mMoreKeys.length; i++) {
244                upperCaseMoreKeys[i] = mMoreKeys[i].toUpperCase(locale);
245            }
246            return newInstance(getVisual().toUpperCase(locale), getOutput().toUpperCase(locale),
247                    upperCaseMoreKeys);
248        }
249
250        @Override
251        public ExpectedKey[] getMoreKeys() {
252            return mMoreKeys;
253        }
254
255        @Override
256        public ExpectedKey setAdditionalMoreKeys(
257                final ExpectedAdditionalMoreKey... additionalMoreKeys) {
258            if (additionalMoreKeys.length == 0) {
259                return this;
260            }
261            return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(
262                    getVisual(), getOutput(), mMoreKeys, 0 /* additionalMoreKeysIndex */,
263                    additionalMoreKeys);
264        }
265
266        @Override
267        public ExpectedKey setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex) {
268            if (additionalMoreKeysIndex == 0) {
269                return this;
270            }
271            return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(
272                    getVisual(), getOutput(), mMoreKeys, additionalMoreKeysIndex);
273        }
274
275        @Override
276        public boolean equalsTo(final Key key) {
277            if (getVisual().equalsTo(key) && getOutput().equalsTo(key)) {
278                final MoreKeySpec[] moreKeySpecs = key.getMoreKeys();
279                final ExpectedKey[] moreKeys = getMoreKeys();
280                // This key should have at least one "more key".
281                if (moreKeySpecs == null || moreKeySpecs.length != moreKeys.length) {
282                    return false;
283                }
284                for (int index = 0; index < moreKeySpecs.length; index++) {
285                    if (!moreKeys[index].equalsTo(moreKeySpecs[index])) {
286                        return false;
287                    }
288                }
289                return true;
290            }
291            return false;
292        }
293
294        @Override
295        public boolean equalsTo(final MoreKeySpec moreKeySpec) {
296            // MoreKeySpec has no "more keys".
297            return false;
298        }
299
300        @Override
301        public String toString() {
302            return super.toString() + "^" + Arrays.toString(getMoreKeys());
303        }
304    }
305
306    /**
307     * This class represents an expected key that has "more keys" and "additional more keys".
308     */
309    private static final class ExpectedKeyWithMoreKeysAndAdditionalMoreKeys
310            extends ExpectedKeyWithMoreKeys {
311        private final ExpectedAdditionalMoreKey[] mAdditionalMoreKeys;
312        private final int mAdditionalMoreKeysIndex;
313
314        ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(final ExpectedKeyVisual visual,
315                final ExpectedKeyOutput output, final ExpectedKey[] moreKeys,
316                final int additionalMoreKeysIndex,
317                final ExpectedAdditionalMoreKey... additionalMoreKeys) {
318            super(visual, output, moreKeys);
319            mAdditionalMoreKeysIndex = additionalMoreKeysIndex;
320            mAdditionalMoreKeys = additionalMoreKeys;
321        }
322
323        @Override
324        public ExpectedKey setMoreKeys(final ExpectedKey... moreKeys) {
325            return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(
326                    getVisual(), getOutput(), moreKeys, mAdditionalMoreKeysIndex,
327                    mAdditionalMoreKeys);
328        }
329
330        @Override
331        public ExpectedKey setAdditionalMoreKeys(
332                final ExpectedAdditionalMoreKey... additionalMoreKeys) {
333            return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(
334                    getVisual(), getOutput(), super.getMoreKeys(), mAdditionalMoreKeysIndex,
335                    additionalMoreKeys);
336        }
337
338        @Override
339        public ExpectedKey setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex) {
340            return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(
341                    getVisual(), getOutput(), super.getMoreKeys(), additionalMoreKeysIndex,
342                    mAdditionalMoreKeys);
343        }
344
345        @Override
346        public ExpectedKey toUpperCase(final Locale locale) {
347            final ExpectedKey[] moreKeys = super.getMoreKeys();
348            final ExpectedKey[] upperCaseMoreKeys = new ExpectedKey[moreKeys.length];
349            for (int i = 0; i < moreKeys.length; i++) {
350                upperCaseMoreKeys[i] = moreKeys[i].toUpperCase(locale);
351            }
352            final ExpectedAdditionalMoreKey[] upperCaseAdditionalMoreKeys =
353                    new ExpectedAdditionalMoreKey[mAdditionalMoreKeys.length];
354            for (int i = 0; i < mAdditionalMoreKeys.length; i++) {
355                upperCaseAdditionalMoreKeys[i] = mAdditionalMoreKeys[i].toUpperCase(locale);
356            }
357            return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(
358                    getVisual().toUpperCase(locale), getOutput().toUpperCase(locale),
359                    upperCaseMoreKeys, mAdditionalMoreKeysIndex, upperCaseAdditionalMoreKeys);
360        }
361
362        @Override
363        public ExpectedKey[] getMoreKeys() {
364            final ExpectedKey[] moreKeys = super.getMoreKeys();
365            final ExpectedKey[] edittedMoreKeys = Arrays.copyOf(
366                    moreKeys, moreKeys.length + mAdditionalMoreKeys.length);
367            System.arraycopy(edittedMoreKeys, mAdditionalMoreKeysIndex,
368                    edittedMoreKeys, mAdditionalMoreKeysIndex + mAdditionalMoreKeys.length,
369                    moreKeys.length - mAdditionalMoreKeysIndex);
370            System.arraycopy(mAdditionalMoreKeys, 0, edittedMoreKeys, mAdditionalMoreKeysIndex,
371                    mAdditionalMoreKeys.length);
372            return edittedMoreKeys;
373        }
374    }
375}
376