• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 /*
3  * Copyright (C) 2011 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package android.text.method;
19 
20 import android.text.Selection;
21 
22 import java.text.BreakIterator;
23 import java.util.Locale;
24 
25 /**
26  * Walks through cursor positions at word boundaries. Internally uses
27  * {@link BreakIterator#getWordInstance()}, and caches {@link CharSequence}
28  * for performance reasons.
29  *
30  * Also provides methods to determine word boundaries.
31  * {@hide}
32  */
33 public class WordIterator implements Selection.PositionIterator {
34     // Size of the window for the word iterator, should be greater than the longest word's length
35     private static final int WINDOW_WIDTH = 50;
36 
37     private String mString;
38     private int mOffsetShift;
39 
40     private BreakIterator mIterator;
41 
42     /**
43      * Constructs a WordIterator using the default locale.
44      */
WordIterator()45     public WordIterator() {
46         this(Locale.getDefault());
47     }
48 
49     /**
50      * Constructs a new WordIterator for the specified locale.
51      * @param locale The locale to be used when analysing the text.
52      */
WordIterator(Locale locale)53     public WordIterator(Locale locale) {
54         mIterator = BreakIterator.getWordInstance(locale);
55     }
56 
setCharSequence(CharSequence charSequence, int start, int end)57     public void setCharSequence(CharSequence charSequence, int start, int end) {
58         mOffsetShift = Math.max(0, start - WINDOW_WIDTH);
59         final int windowEnd = Math.min(charSequence.length(), end + WINDOW_WIDTH);
60 
61         mString = charSequence.toString().substring(mOffsetShift, windowEnd);
62         mIterator.setText(mString);
63     }
64 
65     /** {@inheritDoc} */
preceding(int offset)66     public int preceding(int offset) {
67         int shiftedOffset = offset - mOffsetShift;
68         do {
69             shiftedOffset = mIterator.preceding(shiftedOffset);
70             if (shiftedOffset == BreakIterator.DONE) {
71                 return BreakIterator.DONE;
72             }
73             if (isOnLetterOrDigit(shiftedOffset)) {
74                 return shiftedOffset + mOffsetShift;
75             }
76         } while (true);
77     }
78 
79     /** {@inheritDoc} */
following(int offset)80     public int following(int offset) {
81         int shiftedOffset = offset - mOffsetShift;
82         do {
83             shiftedOffset = mIterator.following(shiftedOffset);
84             if (shiftedOffset == BreakIterator.DONE) {
85                 return BreakIterator.DONE;
86             }
87             if (isAfterLetterOrDigit(shiftedOffset)) {
88                 return shiftedOffset + mOffsetShift;
89             }
90         } while (true);
91     }
92 
93     /** If <code>offset</code> is within a word, returns the index of the first character of that
94      * word, otherwise returns BreakIterator.DONE.
95      *
96      * The offsets that are considered to be part of a word are the indexes of its characters,
97      * <i>as well as</i> the index of its last character plus one.
98      * If offset is the index of a low surrogate character, BreakIterator.DONE will be returned.
99      *
100      * Valid range for offset is [0..textLength] (note the inclusive upper bound).
101      * The returned value is within [0..offset] or BreakIterator.DONE.
102      *
103      * @throws IllegalArgumentException is offset is not valid.
104      */
getBeginning(int offset)105     public int getBeginning(int offset) {
106         final int shiftedOffset = offset - mOffsetShift;
107         checkOffsetIsValid(shiftedOffset);
108 
109         if (isOnLetterOrDigit(shiftedOffset)) {
110             if (mIterator.isBoundary(shiftedOffset)) {
111                 return shiftedOffset + mOffsetShift;
112             } else {
113                 return mIterator.preceding(shiftedOffset) + mOffsetShift;
114             }
115         } else {
116             if (isAfterLetterOrDigit(shiftedOffset)) {
117                 return mIterator.preceding(shiftedOffset) + mOffsetShift;
118             }
119         }
120         return BreakIterator.DONE;
121     }
122 
123     /** If <code>offset</code> is within a word, returns the index of the last character of that
124      * word plus one, otherwise returns BreakIterator.DONE.
125      *
126      * The offsets that are considered to be part of a word are the indexes of its characters,
127      * <i>as well as</i> the index of its last character plus one.
128      * If offset is the index of a low surrogate character, BreakIterator.DONE will be returned.
129      *
130      * Valid range for offset is [0..textLength] (note the inclusive upper bound).
131      * The returned value is within [offset..textLength] or BreakIterator.DONE.
132      *
133      * @throws IllegalArgumentException is offset is not valid.
134      */
getEnd(int offset)135     public int getEnd(int offset) {
136         final int shiftedOffset = offset - mOffsetShift;
137         checkOffsetIsValid(shiftedOffset);
138 
139         if (isAfterLetterOrDigit(shiftedOffset)) {
140             if (mIterator.isBoundary(shiftedOffset)) {
141                 return shiftedOffset + mOffsetShift;
142             } else {
143                 return mIterator.following(shiftedOffset) + mOffsetShift;
144             }
145         } else {
146             if (isOnLetterOrDigit(shiftedOffset)) {
147                 return mIterator.following(shiftedOffset) + mOffsetShift;
148             }
149         }
150         return BreakIterator.DONE;
151     }
152 
isAfterLetterOrDigit(int shiftedOffset)153     private boolean isAfterLetterOrDigit(int shiftedOffset) {
154         if (shiftedOffset >= 1 && shiftedOffset <= mString.length()) {
155             final int codePoint = mString.codePointBefore(shiftedOffset);
156             if (Character.isLetterOrDigit(codePoint)) return true;
157         }
158         return false;
159     }
160 
isOnLetterOrDigit(int shiftedOffset)161     private boolean isOnLetterOrDigit(int shiftedOffset) {
162         if (shiftedOffset >= 0 && shiftedOffset < mString.length()) {
163             final int codePoint = mString.codePointAt(shiftedOffset);
164             if (Character.isLetterOrDigit(codePoint)) return true;
165         }
166         return false;
167     }
168 
checkOffsetIsValid(int shiftedOffset)169     private void checkOffsetIsValid(int shiftedOffset) {
170         if (shiftedOffset < 0 || shiftedOffset > mString.length()) {
171             throw new IllegalArgumentException("Invalid offset: " + (shiftedOffset + mOffsetShift) +
172                     ". Valid range is [" + mOffsetShift + ", " + (mString.length() + mOffsetShift) +
173                     "]");
174         }
175     }
176 }
177