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