Project: Jota-Text-Editor
/*
 * Copyright (C) 2006 The Android Open Source Project 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
 
package jp.sblo.pandora.jota.text; 
 
import java.lang.reflect.Array; 
 
import android.graphics.Canvas; 
import android.graphics.Paint; 
import android.text.Editable; 
import android.text.GetChars; 
import android.text.GraphicsOperations; 
import android.text.InputFilter; 
import android.text.NoCopySpan; 
import android.text.SpanWatcher; 
import android.text.Spannable; 
import android.text.Spanned; 
import android.text.TextWatcher; 
import android.util.Log; 
 
import com.android.internal.util.ArrayUtils; 
 
/**
 * This is the class for text whose content and markup can both be changed. 
 */
 
public class SpannableStringBuilder 
implements CharSequence, GetChars, Spannable, Editable, Appendable, 
           GraphicsOperations 
    /**
     * Create a new SpannableStringBuilder with empty contents 
     */
 
    public SpannableStringBuilder() { 
        this(""); 
    } 
 
    /**
     * Create a new SpannableStringBuilder containing a copy of the 
     * specified text, including its spans if any. 
     */
 
    public SpannableStringBuilder(CharSequence text) { 
        this(text, 0, text.length()); 
    } 
 
    /**
     * Create a new SpannableStringBuilder containing a copy of the 
     * specified slice of the specified text, including its spans if any. 
     */
 
    public SpannableStringBuilder(CharSequence text, int start, int end) { 
        int srclen = end - start; 
 
        int len = ArrayUtils.idealCharArraySize(srclen + 1); 
        mText = new char[len]; 
        mGapStart = srclen; 
        mGapLength = len - srclen; 
 
        TextUtils.getChars(text, start, end, mText, 0); 
 
        mSpanCount = 0
        int alloc = ArrayUtils.idealIntArraySize(0); 
        mSpans = new Object[alloc]; 
        mSpanStarts = new int[alloc]; 
        mSpanEnds = new int[alloc]; 
        mSpanFlags = new int[alloc]; 
 
        if (text instanceof Spanned) { 
            Spanned sp = (Spanned) text; 
            Object[] spans = sp.getSpans(start, end, Object.class); 
 
            for (int i = 0; i < spans.length; i++) { 
                if (spans[i] instanceof NoCopySpan) { 
                    continue
                } 
 
                int st = sp.getSpanStart(spans[i]) - start; 
                int en = sp.getSpanEnd(spans[i]) - start; 
                int fl = sp.getSpanFlags(spans[i]); 
 
                if (st < 0
                    st = 0
                if (st > end - start) 
                    st = end - start; 
 
                if (en < 0
                    en = 0
                if (en > end - start) 
                    en = end - start; 
 
                setSpan(spans[i], st, en, fl); 
            } 
        } 
    } 
 
    public static SpannableStringBuilder valueOf(CharSequence source) { 
        if (source instanceof SpannableStringBuilder) { 
            return (SpannableStringBuilder) source; 
        } else { 
            return new SpannableStringBuilder(source); 
        } 
    } 
 
    /**
     * Return the char at the specified offset within the buffer. 
     */
 
    public char charAt(int where) { 
        int len = length(); 
        if (where < 0) { 
            throw new IndexOutOfBoundsException("charAt: " + where + " < 0"); 
        } else if (where >= len) { 
            throw new IndexOutOfBoundsException("charAt: " + where + 
                                                " >= length " + len); 
        } 
 
        if (where >= mGapStart) 
            return mText[where + mGapLength]; 
        else 
            return mText[where]; 
    } 
 
    /**
     * Return the number of chars in the buffer. 
     */
 
    public int length() { 
        return mText.length - mGapLength; 
    } 
 
    private void resizeFor(int size) { 
        int newlen = ArrayUtils.idealCharArraySize(size + 1); 
 
        // Jota Text Editor 
        if ( size > 512*1024 ){ 
            newlen = size + 128 * 1024
        } 
 
        char[] newtext = new char[newlen]; 
 
        int after = mText.length - (mGapStart + mGapLength); 
 
        System.arraycopy(mText, 0, newtext, 0, mGapStart); 
        System.arraycopy(mText, mText.length - after, 
                         newtext, newlen - after, after); 
 
        for (int i = 0; i < mSpanCount; i++) { 
            if (mSpanStarts[i] > mGapStart) 
                mSpanStarts[i] += newlen - mText.length; 
            if (mSpanEnds[i] > mGapStart) 
                mSpanEnds[i] += newlen - mText.length; 
        } 
 
        int oldlen = mText.length; 
        mText = newtext; 
        mGapLength += mText.length - oldlen; 
 
        if (mGapLength < 1
            new Exception("mGapLength < 1").printStackTrace(); 
    } 
 
    private void moveGapTo(int where) { 
        if (where == mGapStart) 
            return
 
        boolean atend = (where == length()); 
 
        if (where < mGapStart) { 
            int overlap = mGapStart - where; 
 
            System.arraycopy(mText, where, 
                             mText, mGapStart + mGapLength - overlap, overlap); 
        } else /* where > mGapStart */ { 
            int overlap = where - mGapStart; 
 
            System.arraycopy(mText, where + mGapLength - overlap, 
                             mText, mGapStart, overlap); 
        } 
 
        // XXX be more clever 
        for (int i = 0; i < mSpanCount; i++) { 
            int start = mSpanStarts[i]; 
            int end = mSpanEnds[i]; 
 
            if (start > mGapStart) 
                start -= mGapLength; 
            if (start > where) 
                start += mGapLength; 
            else if (start == where) { 
                int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT; 
 
                if (flag == POINT || (atend && flag == PARAGRAPH)) 
                    start += mGapLength; 
            } 
 
            if (end > mGapStart) 
                end -= mGapLength; 
            if (end > where) 
                end += mGapLength; 
            else if (end == where) { 
                int flag = (mSpanFlags[i] & END_MASK); 
 
                if (flag == POINT || (atend && flag == PARAGRAPH)) 
                    end += mGapLength; 
            } 
 
            mSpanStarts[i] = start; 
            mSpanEnds[i] = end; 
        } 
 
        mGapStart = where; 
    } 
 
    // Documentation from interface 
    public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) { 
        return replace(where, where, tb, start, end); 
    } 
 
    // Documentation from interface 
    public SpannableStringBuilder insert(int where, CharSequence tb) { 
        return replace(where, where, tb, 0, tb.length()); 
    } 
 
    // Documentation from interface 
    public SpannableStringBuilder delete(int start, int end) { 
        SpannableStringBuilder ret = replace(start, end, ""00); 
 
        if (mGapLength > 2 * length()) 
            resizeFor(length()); 
 
        return ret; // == this 
    } 
 
    // Documentation from interface 
    public void clear() { 
        replace(0, length(), ""00); 
    } 
 
    // Documentation from interface 
    public void clearSpans() { 
        for (int i = mSpanCount - 1; i >= 0; i--) { 
            Object what = mSpans[i]; 
            int ostart = mSpanStarts[i]; 
            int oend = mSpanEnds[i]; 
 
            if (ostart > mGapStart) 
                ostart -= mGapLength; 
            if (oend > mGapStart) 
                oend -= mGapLength; 
 
            mSpanCount = i; 
            mSpans[i] = null
 
            sendSpanRemoved(what, ostart, oend); 
        } 
    } 
 
    // Documentation from interface 
    public SpannableStringBuilder append(CharSequence text) { 
        int length = length(); 
        return replace(length, length, text, 0, text.length()); 
    } 
 
    // Documentation from interface 
    public SpannableStringBuilder append(CharSequence text, int start, int end) { 
        int length = length(); 
        return replace(length, length, text, start, end); 
    } 
 
    // Documentation from interface 
    public SpannableStringBuilder append(char text) { 
        return append(String.valueOf(text)); 
    } 
 
    private int change(int start, int end, 
                       CharSequence tb, int tbstart, int tbend) { 
        return change(true, start, end, tb, tbstart, tbend); 
    } 
 
    private int change(boolean notify, int start, int end, 
                       CharSequence tb, int tbstart, int tbend) { 
        checkRange("replace", start, end); 
        int ret = tbend - tbstart; 
        TextWatcher[] recipients = null
 
        if (notify) 
            recipients = sendTextWillChange(start, end - start, 
                                            tbend - tbstart); 
 
        for (int i = mSpanCount - 1; i >= 0; i--) { 
            if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) { 
                int st = mSpanStarts[i]; 
                if (st > mGapStart) 
                    st -= mGapLength; 
 
                int en = mSpanEnds[i]; 
                if (en > mGapStart) 
                    en -= mGapLength; 
 
                int ost = st; 
                int oen = en; 
                int clen = length(); 
 
                if (st > start && st <= end) { 
                    for (st = end; st < clen; st++) 
                        if (st > end && charAt(st - 1) == '\n') 
                            break
                } 
 
                if (en > start && en <= end) { 
                    for (en = end; en < clen; en++) 
                        if (en > end && charAt(en - 1) == '\n') 
                            break
                } 
 
                if (st != ost || en != oen) 
                    setSpan(mSpans[i], st, en, mSpanFlags[i]); 
            } 
        } 
 
        moveGapTo(end); 
 
        if (tbend - tbstart >= mGapLength + (end - start)) 
            resizeFor(mText.length - mGapLength + 
                      tbend - tbstart - (end - start)); 
 
        mGapStart += tbend - tbstart - (end - start); 
        mGapLength -= tbend - tbstart - (end - start); 
 
        if (mGapLength < 1
            new Exception("mGapLength < 1").printStackTrace(); 
 
        TextUtils.getChars(tb, tbstart, tbend, mText, start); 
 
        if (tb instanceof Spanned) { 
            Spanned sp = (Spanned) tb; 
            Object[] spans = sp.getSpans(tbstart, tbend, Object.class); 
 
            for (int i = 0; i < spans.length; i++) { 
                int st = sp.getSpanStart(spans[i]); 
                int en = sp.getSpanEnd(spans[i]); 
 
                if (st < tbstart) 
                    st = tbstart; 
                if (en > tbend) 
                    en = tbend; 
 
                if (getSpanStart(spans[i]) < 0) { 
                    setSpan(false, spans[i], 
                            st - tbstart + start, 
                            en - tbstart + start, 
                            sp.getSpanFlags(spans[i])); 
                } 
            } 
        } 
 
        // no need for span fixup on pure insertion 
        if (tbend > tbstart && end - start == 0) { 
            if (notify) { 
                sendTextChange(recipients, start, end - start, tbend - tbstart); 
                sendTextHasChanged(recipients); 
            } 
 
            return ret; 
        } 
 
        boolean atend = (mGapStart + mGapLength == mText.length); 
 
        for (int i = mSpanCount - 1; i >= 0; i--) { 
            if (mSpanStarts[i] >= start && 
                mSpanStarts[i] < mGapStart + mGapLength) { 
                int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT; 
 
                if (flag == POINT || (flag == PARAGRAPH && atend)) 
                    mSpanStarts[i] = mGapStart + mGapLength; 
                else 
                    mSpanStarts[i] = start; 
            } 
 
            if (mSpanEnds[i] >= start && 
                mSpanEnds[i] < mGapStart + mGapLength) { 
                int flag = (mSpanFlags[i] & END_MASK); 
 
                if (flag == POINT || (flag == PARAGRAPH && atend)) 
                    mSpanEnds[i] = mGapStart + mGapLength; 
                else 
                    mSpanEnds[i] = start; 
            } 
 
            // remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE 
            // XXX send notification on removal 
 
            if (mSpanEnds[i] < mSpanStarts[i]) { 
                System.arraycopy(mSpans, i + 1
                                 mSpans, i, mSpanCount - (i + 1)); 
                System.arraycopy(mSpanStarts, i + 1
                                 mSpanStarts, i, mSpanCount - (i + 1)); 
                System.arraycopy(mSpanEnds, i + 1
                                 mSpanEnds, i, mSpanCount - (i + 1)); 
                System.arraycopy(mSpanFlags, i + 1
                                 mSpanFlags, i, mSpanCount - (i + 1)); 
 
                mSpanCount--; 
            } 
        } 
 
        if (notify) { 
            sendTextChange(recipients, start, end - start, tbend - tbstart); 
            sendTextHasChanged(recipients); 
        } 
 
        return ret; 
    } 
 
    // Documentation from interface 
    public SpannableStringBuilder replace(int start, int end, CharSequence tb) { 
        return replace(start, end, tb, 0, tb.length()); 
    } 
 
    // Documentation from interface 
    public SpannableStringBuilder replace(final int start, final int end, 
                        CharSequence tb, int tbstart, int tbend) { 
        int filtercount = mFilters.length; 
        for (int i = 0; i < filtercount; i++) { 
            CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, 
                                                   this, start, end); 
 
            if (repl != null) { 
                tb = repl; 
                tbstart = 0
                tbend = repl.length(); 
            } 
        } 
 
        if (end == start && tbstart == tbend) { 
            return this
        } 
 
        if (end == start || tbstart == tbend) { 
            change(start, end, tb, tbstart, tbend); 
        } else { 
            int selstart = Selection.getSelectionStart(this); 
            int selend = Selection.getSelectionEnd(this); 
 
            // XXX just make the span fixups in change() do the right thing 
            // instead of this madness! 
 
            checkRange("replace", start, end); 
            moveGapTo(end); 
            TextWatcher[] recipients; 
 
            recipients = sendTextWillChange(start, end - start, 
                                            tbend - tbstart); 
 
            int origlen = end - start; 
 
            if (mGapLength < 2
                resizeFor(length() + 1); 
 
            for (int i = mSpanCount - 1; i >= 0; i--) { 
                if (mSpanStarts[i] == mGapStart) 
                    mSpanStarts[i]++; 
 
                if (mSpanEnds[i] == mGapStart) 
                    mSpanEnds[i]++; 
            } 
 
            mText[mGapStart] = ' '; 
            mGapStart++; 
            mGapLength--; 
 
            if (mGapLength < 1
                new Exception("mGapLength < 1").printStackTrace(); 
 
            int oldlen = (end + 1) - start; 
 
            int inserted = change(false, start + 1, start + 1
                                  tb, tbstart, tbend); 
            change(false, start, start + 1""00); 
            change(false, start + inserted, start + inserted + oldlen - 1
                   ""00); 
 
            /*
             * Special case to keep the cursor in the same position 
             * if it was somewhere in the middle of the replaced region. 
             * If it was at the start or the end or crossing the whole 
             * replacement, it should already be where it belongs. 
             * TODO: Is there some more general mechanism that could 
             * accomplish this? 
             */
 
            if (selstart > start && selstart < end) { 
                long off = selstart - start; 
 
                off = off * inserted / (end - start); 
                selstart = (int) off + start; 
 
                setSpan(false, Selection.SELECTION_START, selstart, selstart, 
                        Spanned.SPAN_POINT_POINT); 
            } 
            if (selend > start && selend < end) { 
                long off = selend - start; 
 
                off = off * inserted / (end - start); 
                selend = (int) off + start; 
 
                setSpan(false, Selection.SELECTION_END, selend, selend, 
                        Spanned.SPAN_POINT_POINT); 
            } 
 
            sendTextChange(recipients, start, origlen, inserted); 
            sendTextHasChanged(recipients); 
        } 
        return this
    } 
 
    /**
     * Mark the specified range of text with the specified object. 
     * The flags determine how the span will behave when text is 
     * inserted at the start or end of the span's range. 
     */
 
    public void setSpan(Object what, int start, int end, int flags) { 
        setSpan(true, what, start, end, flags); 
    } 
 
    private void setSpan(boolean send, 
                         Object what, int start, int end, int flags) { 
        int nstart = start; 
        int nend = end; 
 
        checkRange("setSpan", start, end); 
 
        if ((flags & START_MASK) == (PARAGRAPH << START_SHIFT)) { 
            if (start != 0 && start != length()) { 
                char c = charAt(start - 1); 
 
                if (c != '\n') 
                    throw new RuntimeException( 
                            "PARAGRAPH span must start at paragraph boundary"); 
            } 
        } 
 
        if ((flags & END_MASK) == PARAGRAPH) { 
            if (end != 0 && end != length()) { 
                char c = charAt(end - 1); 
 
                if (c != '\n') 
                    throw new RuntimeException( 
                            "PARAGRAPH span must end at paragraph boundary"); 
            } 
        } 
 
        if (start > mGapStart) 
            start += mGapLength; 
        else if (start == mGapStart) { 
            int flag = (flags & START_MASK) >> START_SHIFT; 
 
            if (flag == POINT || (flag == PARAGRAPH && start == length())) 
                start += mGapLength; 
        } 
 
        if (end > mGapStart) 
            end += mGapLength; 
        else if (end == mGapStart) { 
            int flag = (flags & END_MASK); 
 
            if (flag == POINT || (flag == PARAGRAPH && end == length())) 
                end += mGapLength; 
        } 
 
        int count = mSpanCount; 
        Object[] spans = mSpans; 
 
        for (int i = 0; i < count; i++) { 
            if (spans[i] == what) { 
                int ostart = mSpanStarts[i]; 
                int oend = mSpanEnds[i]; 
 
                if (ostart > mGapStart) 
                    ostart -= mGapLength; 
                if (oend > mGapStart) 
                    oend -= mGapLength; 
 
                mSpanStarts[i] = start; 
                mSpanEnds[i] = end; 
                mSpanFlags[i] = flags; 
 
                if (send) 
                    sendSpanChanged(what, ostart, oend, nstart, nend); 
 
                return
            } 
        } 
 
        if (mSpanCount + 1 >= mSpans.length) { 
            int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1); 
            Object[] newspans = new Object[newsize]; 
            int[] newspanstarts = new int[newsize]; 
            int[] newspanends = new int[newsize]; 
            int[] newspanflags = new int[newsize]; 
 
            System.arraycopy(mSpans, 0, newspans, 0, mSpanCount); 
            System.arraycopy(mSpanStarts, 0, newspanstarts, 0, mSpanCount); 
            System.arraycopy(mSpanEnds, 0, newspanends, 0, mSpanCount); 
            System.arraycopy(mSpanFlags, 0, newspanflags, 0, mSpanCount); 
 
            mSpans = newspans; 
            mSpanStarts = newspanstarts; 
            mSpanEnds = newspanends; 
            mSpanFlags = newspanflags; 
        } 
 
        mSpans[mSpanCount] = what; 
        mSpanStarts[mSpanCount] = start; 
        mSpanEnds[mSpanCount] = end; 
        mSpanFlags[mSpanCount] = flags; 
        mSpanCount++; 
 
        if (send) 
            sendSpanAdded(what, nstart, nend); 
    } 
 
    /**
     * Remove the specified markup object from the buffer. 
     */
 
    public void removeSpan(Object what) { 
        for (int i = mSpanCount - 1; i >= 0; i--) { 
            if (mSpans[i] == what) { 
                int ostart = mSpanStarts[i]; 
                int oend = mSpanEnds[i]; 
 
                if (ostart > mGapStart) 
                    ostart -= mGapLength; 
                if (oend > mGapStart) 
                    oend -= mGapLength; 
 
                int count = mSpanCount - (i + 1); 
 
                System.arraycopy(mSpans, i + 1, mSpans, i, count); 
                System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count); 
                System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count); 
                System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count); 
 
                mSpanCount--; 
                mSpans[mSpanCount] = null
 
                sendSpanRemoved(what, ostart, oend); 
                return
            } 
        } 
    } 
 
    /**
     * Return the buffer offset of the beginning of the specified 
     * markup object, or -1 if it is not attached to this buffer. 
     */
 
    public int getSpanStart(Object what) { 
        int count = mSpanCount; 
        Object[] spans = mSpans; 
 
        for (int i = count - 1; i >= 0; i--) { 
            if (spans[i] == what) { 
                int where = mSpanStarts[i]; 
 
                if (where > mGapStart) 
                    where -= mGapLength; 
 
                return where; 
            } 
        } 
 
        return -1
    } 
 
    /**
     * Return the buffer offset of the end of the specified 
     * markup object, or -1 if it is not attached to this buffer. 
     */
 
    public int getSpanEnd(Object what) { 
        int count = mSpanCount; 
        Object[] spans = mSpans; 
 
        for (int i = count - 1; i >= 0; i--) { 
            if (spans[i] == what) { 
                int where = mSpanEnds[i]; 
 
                if (where > mGapStart) 
                    where -= mGapLength; 
 
                return where; 
            } 
        } 
 
        return -1
    } 
 
    /**
     * Return the flags of the end of the specified 
     * markup object, or 0 if it is not attached to this buffer. 
     */
 
    public int getSpanFlags(Object what) { 
        int count = mSpanCount; 
        Object[] spans = mSpans; 
 
        for (int i = count - 1; i >= 0; i--) { 
            if (spans[i] == what) { 
                return mSpanFlags[i]; 
            } 
        } 
 
        return 0
    } 
 
    /**
     * Return an array of the spans of the specified type that overlap 
     * the specified range of the buffer.  The kind may be Object.class to get 
     * a list of all the spans regardless of type. 
     */
 
    public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) { 
        int spanCount = mSpanCount; 
        Object[] spans = mSpans; 
        int[] starts = mSpanStarts; 
        int[] ends = mSpanEnds; 
        int[] flags = mSpanFlags; 
        int gapstart = mGapStart; 
        int gaplen = mGapLength; 
 
        int count = 0
        Object[] ret = null
        Object ret1 = null
 
        for (int i = 0; i < spanCount; i++) { 
            int spanStart = starts[i]; 
            int spanEnd = ends[i]; 
 
            if (spanStart > gapstart) { 
                spanStart -= gaplen; 
            } 
            if (spanEnd > gapstart) { 
                spanEnd -= gaplen; 
            } 
 
            if (spanStart > queryEnd) { 
                continue
            } 
            if (spanEnd < queryStart) { 
                continue
            } 
 
            if (spanStart != spanEnd && queryStart != queryEnd) { 
                if (spanStart == queryEnd) 
                    continue
                if (spanEnd == queryStart) 
                    continue
            } 
 
            if (kind != null && !kind.isInstance(spans[i])) { 
                continue
            } 
 
            if (count == 0) { 
                ret1 = spans[i]; 
                count++; 
            } else { 
                if (count == 1) { 
                    ret = (Object[]) Array.newInstance(kind, spanCount - i + 1); 
                    ret[0] = ret1; 
                } 
 
                int prio = flags[i] & SPAN_PRIORITY; 
                if (prio != 0) { 
                    int j; 
 
                    for (j = 0; j < count; j++) { 
                        int p = getSpanFlags(ret[j]) & SPAN_PRIORITY; 
 
                        if (prio > p) { 
                            break
                        } 
                    } 
 
                    System.arraycopy(ret, j, ret, j + 1, count - j); 
                    ret[j] = spans[i]; 
                    count++; 
                } else { 
                    ret[count++] = spans[i]; 
                } 
            } 
        } 
 
        if (count == 0) { 
            return (T[]) ArrayUtils.emptyArray(kind); 
        } 
        if (count == 1) { 
            ret = (Object[]) Array.newInstance(kind, 1); 
            ret[0] = ret1; 
            return (T[]) ret; 
        } 
        if (count == ret.length) { 
            return (T[]) ret; 
        } 
 
        Object[] nret = (Object[]) Array.newInstance(kind, count); 
        System.arraycopy(ret, 0, nret, 0, count); 
        return (T[]) nret; 
    } 
 
    /**
     * Return the next offset after <code>start</code> but less than or 
     * equal to <code>limit</code> where a span of the specified type 
     * begins or ends. 
     */
 
    public int nextSpanTransition(int start, int limit, Class kind) { 
        int count = mSpanCount; 
        Object[] spans = mSpans; 
        int[] starts = mSpanStarts; 
        int[] ends = mSpanEnds; 
        int gapstart = mGapStart; 
        int gaplen = mGapLength; 
 
        if (kind == null) { 
            kind = Object.class
        } 
 
        for (int i = 0; i < count; i++) { 
            int st = starts[i]; 
            int en = ends[i]; 
 
            if (st > gapstart) 
                st -= gaplen; 
            if (en > gapstart) 
                en -= gaplen; 
 
            if (st > start && st < limit && kind.isInstance(spans[i])) 
                limit = st; 
            if (en > start && en < limit && kind.isInstance(spans[i])) 
                limit = en; 
        } 
 
        return limit; 
    } 
 
    /**
     * Return a new CharSequence containing a copy of the specified 
     * range of this buffer, including the overlapping spans. 
     */
 
    public CharSequence subSequence(int start, int end) { 
        return new SpannableStringBuilder(this, start, end); 
    } 
 
    /**
     * Copy the specified range of chars from this buffer into the 
     * specified array, beginning at the specified offset. 
     */
 
    public void getChars(int start, int end, char[] dest, int destoff) { 
        checkRange("getChars", start, end); 
 
        if (end <= mGapStart) { 
            System.arraycopy(mText, start, dest, destoff, end - start); 
        } else if (start >= mGapStart) { 
            System.arraycopy(mText, start + mGapLength, 
                             dest, destoff, end - start); 
        } else { 
            System.arraycopy(mText, start, dest, destoff, mGapStart - start); 
            System.arraycopy(mText, mGapStart + mGapLength, 
                             dest, destoff + (mGapStart - start), 
                             end - mGapStart); 
        } 
    } 
 
    /**
     * Return a String containing a copy of the chars in this buffer. 
     */
 
    public String toString() { 
        int len = length(); 
        char[] buf = new char[len]; 
 
        getChars(0, len, buf, 0); 
        return new String(buf); 
    } 
 
    private TextWatcher[] sendTextWillChange(int start, int before, int after) { 
        TextWatcher[] recip = getSpans(start, start + before, TextWatcher.class); 
        int n = recip.length; 
 
        for (int i = 0; i < n; i++) { 
            recip[i].beforeTextChanged(this, start, before, after); 
        } 
 
        return recip; 
    } 
 
    private void sendTextChange(TextWatcher[] recip, int start, int before, 
                                int after) { 
        int n = recip.length; 
 
        for (int i = 0; i < n; i++) { 
            recip[i].onTextChanged(this, start, before, after); 
        } 
    } 
 
    private void sendTextHasChanged(TextWatcher[] recip) { 
        int n = recip.length; 
 
        for (int i = 0; i < n; i++) { 
            recip[i].afterTextChanged(this); 
        } 
    } 
 
    private void sendSpanAdded(Object what, int start, int end) { 
        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 
        int n = recip.length; 
 
        for (int i = 0; i < n; i++) { 
            recip[i].onSpanAdded(this, what, start, end); 
        } 
    } 
 
    private void sendSpanRemoved(Object what, int start, int end) { 
        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 
        int n = recip.length; 
 
        for (int i = 0; i < n; i++) { 
            recip[i].onSpanRemoved(this, what, start, end); 
        } 
    } 
 
    private void sendSpanChanged(Object what, int s, int e, int st, int en) { 
        SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en), 
                                  SpanWatcher.class); 
        int n = recip.length; 
 
        for (int i = 0; i < n; i++) { 
            recip[i].onSpanChanged(this, what, s, e, st, en); 
        } 
    } 
 
    private static String region(int start, int end) { 
        return "(" + start + " ... " + end + ")"
    } 
 
    private void checkRange(final String operation, int start, int end) { 
        if (end < start) { 
            throw new IndexOutOfBoundsException(operation + " " + 
                                                region(start, end) + 
                                                " has end before start"); 
        } 
 
        int len = length(); 
 
        if (start > len || end > len) { 
            throw new IndexOutOfBoundsException(operation + " " + 
                                                region(start, end) + 
                                                " ends beyond length " + len); 
        } 
 
        if (start < 0 || end < 0) { 
            throw new IndexOutOfBoundsException(operation + " " + 
                                                region(start, end) + 
                                                " starts before 0"); 
        } 
    } 
 
    private boolean isprint(char c) { // XXX 
        if (c >= ' ' && c <= '~') 
            return true
        else 
            return false
    } 
 
/*
    private static final int startFlag(int flag) { 
        return (flag >> 4) & 0x0F; 
    } 
 
    private static final int endFlag(int flag) { 
        return flag & 0x0F; 
    } 
 
    public void dump() { // XXX 
        for (int i = 0; i < mGapStart; i++) { 
            System.out.print('|'); 
            System.out.print(' '); 
            System.out.print(isprint(mText[i]) ? mText[i] : '.'); 
            System.out.print(' '); 
        } 
 
        for (int i = mGapStart; i < mGapStart + mGapLength; i++) { 
            System.out.print('|'); 
            System.out.print('('); 
            System.out.print(isprint(mText[i]) ? mText[i] : '.'); 
            System.out.print(')'); 
        } 
 
        for (int i = mGapStart + mGapLength; i < mText.length; i++) { 
            System.out.print('|'); 
            System.out.print(' '); 
            System.out.print(isprint(mText[i]) ? mText[i] : '.'); 
            System.out.print(' '); 
        } 
 
        System.out.print('\n'); 
 
        for (int i = 0; i < mText.length + 1; i++) { 
            int found = 0; 
            int wfound = 0; 
 
            for (int j = 0; j < mSpanCount; j++) { 
                if (mSpanStarts[j] == i) { 
                    found = 1; 
                    wfound = j; 
                    break; 
                } 
 
                if (mSpanEnds[j] == i) { 
                    found = 2; 
                    wfound = j; 
                    break; 
                } 
            } 
 
            if (found == 1) { 
                if (startFlag(mSpanFlags[wfound]) == MARK) 
                    System.out.print("(   "); 
                if (startFlag(mSpanFlags[wfound]) == PARAGRAPH) 
                    System.out.print("<   "); 
                else 
                    System.out.print("[   "); 
            } else if (found == 2) { 
                if (endFlag(mSpanFlags[wfound]) == POINT) 
                    System.out.print(")   "); 
                if (endFlag(mSpanFlags[wfound]) == PARAGRAPH) 
                    System.out.print(">   "); 
                else 
                    System.out.print("]   "); 
            } else { 
                System.out.print("    "); 
            } 
        } 
 
        System.out.print("\n"); 
    } 
*/
 
 
    /**
     * Don't call this yourself -- exists for Canvas to use internally. 
     * {@hide} 
     */
 
    public void drawText(Canvas c, int start, int end, 
                         float x, float y, Paint p) { 
        checkRange("drawText", start, end); 
 
        if (end <= mGapStart) { 
            c.drawText(mText, start, end - start, x, y, p); 
        } else if (start >= mGapStart) { 
            c.drawText(mText, start + mGapLength, end - start, x, y, p); 
        } else { 
            char[] buf = TextUtils.obtain(end - start); 
 
            getChars(start, end, buf, 0); 
            c.drawText(buf, 0, end - start, x, y, p); 
            TextUtils.recycle(buf); 
        } 
    } 
 
    /**
     * Don't call this yourself -- exists for Paint to use internally. 
     * {@hide} 
     */
 
    public float measureText(int start, int end, Paint p) { 
        checkRange("measureText", start, end); 
 
        float ret; 
 
        if (end <= mGapStart) { 
            ret = p.measureText(mText, start, end - start); 
        } else if (start >= mGapStart) { 
            ret = p.measureText(mText, start + mGapLength, end - start); 
        } else { 
            char[] buf = TextUtils.obtain(end - start); 
 
            getChars(start, end, buf, 0); 
            ret = p.measureText(buf, 0, end - start); 
            TextUtils.recycle(buf); 
        } 
 
        return ret; 
    } 
 
    /**
     * Don't call this yourself -- exists for Paint to use internally. 
     * {@hide} 
     */
 
    public int getTextWidths(int start, int end, float[] widths, Paint p) { 
        checkRange("getTextWidths", start, end); 
 
        int ret; 
 
        if (end <= mGapStart) { 
            ret = p.getTextWidths(mText, start, end - start, widths); 
        } else if (start >= mGapStart) { 
            ret = p.getTextWidths(mText, start + mGapLength, end - start, 
                                  widths); 
        } else { 
            char[] buf = TextUtils.obtain(end - start); 
 
            getChars(start, end, buf, 0); 
            ret = p.getTextWidths(buf, 0, end - start, widths); 
            TextUtils.recycle(buf); 
        } 
 
        return ret; 
    } 
 
    // Documentation from interface 
    public void setFilters(InputFilter[] filters) { 
        if (filters == null) { 
            throw new IllegalArgumentException(); 
        } 
 
        mFilters = filters; 
    } 
 
    // Documentation from interface 
    public InputFilter[] getFilters() { 
        return mFilters; 
    } 
 
    private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 
    private InputFilter[] mFilters = NO_FILTERS; 
 
    private char[] mText; 
    private int mGapStart; 
    private int mGapLength; 
 
    private Object[] mSpans; 
    private int[] mSpanStarts; 
    private int[] mSpanEnds; 
    private int[] mSpanFlags; 
    private int mSpanCount; 
 
    private static final int MARK = 1
    private static final int POINT = 2
    private static final int PARAGRAPH = 3
 
    private static final int START_MASK = 0xF0
    private static final int END_MASK = 0x0F
    private static final int START_SHIFT = 4
}