1/*
2 * Copyright (C) 2006 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 android.text.method;
18
19import android.graphics.Rect;
20import android.text.Editable;
21import android.text.GetChars;
22import android.text.Spannable;
23import android.text.Spanned;
24import android.text.SpannedString;
25import android.text.TextUtils;
26import android.view.View;
27
28/**
29 * This transformation method causes the characters in the {@link #getOriginal}
30 * array to be replaced by the corresponding characters in the
31 * {@link #getReplacement} array.
32 */
33public abstract class ReplacementTransformationMethod
34implements TransformationMethod
35{
36    /**
37     * Returns the list of characters that are to be replaced by other
38     * characters when displayed.
39     */
40    protected abstract char[] getOriginal();
41    /**
42     * Returns a parallel array of replacement characters for the ones
43     * that are to be replaced.
44     */
45    protected abstract char[] getReplacement();
46
47    /**
48     * Returns a CharSequence that will mirror the contents of the
49     * source CharSequence but with the characters in {@link #getOriginal}
50     * replaced by ones from {@link #getReplacement}.
51     */
52    public CharSequence getTransformation(CharSequence source, View v) {
53        char[] original = getOriginal();
54        char[] replacement = getReplacement();
55
56        /*
57         * Short circuit for faster display if the text will never change.
58         */
59        if (!(source instanceof Editable)) {
60            /*
61             * Check whether the text does not contain any of the
62             * source characters so can be used unchanged.
63             */
64            boolean doNothing = true;
65            int n = original.length;
66            for (int i = 0; i < n; i++) {
67                if (TextUtils.indexOf(source, original[i]) >= 0) {
68                    doNothing = false;
69                    break;
70                }
71            }
72            if (doNothing) {
73                return source;
74            }
75
76            if (!(source instanceof Spannable)) {
77                /*
78                 * The text contains some of the source characters,
79                 * but they can be flattened out now instead of
80                 * at display time.
81                 */
82                if (source instanceof Spanned) {
83                    return new SpannedString(new SpannedReplacementCharSequence(
84                                                        (Spanned) source,
85                                                        original, replacement));
86                } else {
87                    return new ReplacementCharSequence(source,
88                                                       original,
89                                                       replacement).toString();
90                }
91            }
92        }
93
94        if (source instanceof Spanned) {
95            return new SpannedReplacementCharSequence((Spanned) source,
96                                                      original, replacement);
97        } else {
98            return new ReplacementCharSequence(source, original, replacement);
99        }
100    }
101
102    public void onFocusChanged(View view, CharSequence sourceText,
103                               boolean focused, int direction,
104                               Rect previouslyFocusedRect) {
105        // This callback isn't used.
106    }
107
108    private static class ReplacementCharSequence
109    implements CharSequence, GetChars {
110        private char[] mOriginal, mReplacement;
111
112        public ReplacementCharSequence(CharSequence source, char[] original,
113                                       char[] replacement) {
114            mSource = source;
115            mOriginal = original;
116            mReplacement = replacement;
117        }
118
119        public int length() {
120            return mSource.length();
121        }
122
123        public char charAt(int i) {
124            char c = mSource.charAt(i);
125
126            int n = mOriginal.length;
127            for (int j = 0; j < n; j++) {
128                if (c == mOriginal[j]) {
129                    c = mReplacement[j];
130                }
131            }
132
133            return c;
134        }
135
136        public CharSequence subSequence(int start, int end) {
137            char[] c = new char[end - start];
138
139            getChars(start, end, c, 0);
140            return new String(c);
141        }
142
143        public String toString() {
144            char[] c = new char[length()];
145
146            getChars(0, length(), c, 0);
147            return new String(c);
148        }
149
150        public void getChars(int start, int end, char[] dest, int off) {
151            TextUtils.getChars(mSource, start, end, dest, off);
152            int offend = end - start + off;
153            int n = mOriginal.length;
154
155            for (int i = off; i < offend; i++) {
156                char c = dest[i];
157
158                for (int j = 0; j < n; j++) {
159                    if (c == mOriginal[j]) {
160                        dest[i] = mReplacement[j];
161                    }
162                }
163            }
164        }
165
166        private CharSequence mSource;
167    }
168
169    private static class SpannedReplacementCharSequence
170    extends ReplacementCharSequence
171    implements Spanned
172    {
173        public SpannedReplacementCharSequence(Spanned source, char[] original,
174                                              char[] replacement) {
175            super(source, original, replacement);
176            mSpanned = source;
177        }
178
179        public CharSequence subSequence(int start, int end) {
180            return new SpannedString(this).subSequence(start, end);
181        }
182
183        public <T> T[] getSpans(int start, int end, Class<T> type) {
184            return mSpanned.getSpans(start, end, type);
185        }
186
187        public int getSpanStart(Object tag) {
188            return mSpanned.getSpanStart(tag);
189        }
190
191        public int getSpanEnd(Object tag) {
192            return mSpanned.getSpanEnd(tag);
193        }
194
195        public int getSpanFlags(Object tag) {
196            return mSpanned.getSpanFlags(tag);
197        }
198
199        public int nextSpanTransition(int start, int end, Class type) {
200            return mSpanned.nextSpanTransition(start, end, type);
201        }
202
203        private Spanned mSpanned;
204    }
205}
206