void handleMotion(int edge, float dx, float dy) {
        Rect r = computeLayout();
        if (edge == GROW_NONE) {
            return;
        } else if (edge == MOVE) {
            // Convert to image space before sending to moveBy().
            moveBy(dx * (mCropRect.width() / r.width()),
                    dy * (mCropRect.height() / r.height()));
        } else {
            if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0) {
                dx = 0;
            }

            if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & edge) == 0) {
                dy = 0;
            }

            // Convert to image space before sending to growBy().
            float xDelta = dx * (mCropRect.width() / r.width());
            float yDelta = dy * (mCropRect.height() / r.height());
            growBy((((edge & GROW_LEFT_EDGE) != 0) ? -1 : 1) * xDelta,
                    (((edge & GROW_TOP_EDGE) != 0) ? -1 : 1) * yDelta);
        }
    }

The "edge" parameter specifies which edges the user is dragging.

The edge parameter specifies which edges the user is dragging .


public Rect getCropRect() {
        return new Rect((int) mCropRect.left, (int) mCropRect.top, (int) mCropRect.right, (int) mCropRect.bottom);
    }

public Rect getCropRect ( ) { return new Rect ( ( int ) mCropRect . left , ( int ) mCropRect . top , ( int ) mCropRect . right , ( int ) mCropRect . bottom ) ; }

Returns the cropping rectangle in image space.

Returns the cropping rectangle in image space .


private Rect computeLayout() {
        RectF r = new RectF(mCropRect.left, mCropRect.top, mCropRect.right, mCropRect.bottom);
        mMatrix.mapRect(r);
        return new Rect(Math.round(r.left), Math.round(r.top), Math.round(r.right), Math.round(r.bottom));
    }

private Rect computeLayout ( ) { RectF r = new RectF ( mCropRect . left , mCropRect . top , mCropRect . right , mCropRect . bottom ) ; mMatrix . mapRect ( r ) ; return new Rect ( Math . round ( r . left ) , Math . round ( r . top ) , Math . round ( r . right ) , Math . round ( r . bottom ) ) ; }

Maps the cropping rectangle from image space to screen space.

Maps the cropping rectangle from image space to screen space .


void register(RTEditTextListener listener, RTMediaFactory<RTImage, RTAudio, RTVideo> mediaFactory) {
        mListener = listener;
        mMediaFactory = mediaFactory;
    }

void register ( RTEditTextListener listener , RTMediaFactory < RTImage , RTAudio , RTVideo > mediaFactory ) { mListener = listener ; mMediaFactory = mediaFactory ; }

This needs to be called before anything else because we need the media
factory.

@param listener     The RTEditTextListener (the RTManager)
@param mediaFactory The RTMediaFactory

This needs to be called before anything else because we need the media factory .


private void addSpanWatcher() {
        Spannable spannable = getText();
        if (spannable.getSpans(0, spannable.length(), getClass()) != null) {
            spannable.setSpan(this, 0, spannable.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        }
    }

private void addSpanWatcher ( ) { Spannable spannable = getText ( ) ; if ( spannable . getSpans ( 0 , spannable . length ( ) , getClass ( ) ) != null ) { spannable . setSpan ( this , 0 , spannable . length ( ) , Spanned . SPAN_INCLUSIVE_INCLUSIVE ) ; } }

Add a SpanWatcher for the Changeable implementation


@SuppressLint("ViewHolder")
    @Override
    public final View getView(int position, View convertView, ViewGroup parent) {
        View spinnerView = mInflater.inflate(mSpinnerId, parent, false);

        mParent = parent;
        TextView spinnerTitleView = (TextView) spinnerView.findViewById(R.id.title);
        updateSpinnerTitle(spinnerTitleView);
        return spinnerView;
    }

@ SuppressLint ( "ViewHolder" ) @ Override public final View getView ( int position , View convertView , ViewGroup parent ) { View spinnerView = mInflater . inflate ( mSpinnerId , parent , false ) ; mParent = parent ; TextView spinnerTitleView = ( TextView ) spinnerView . findViewById ( R . id . title ) ; updateSpinnerTitle ( spinnerTitleView ) ; return spinnerView ; }


@SuppressLint("InlinedApi")
    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        SpinnerItem spinnerItem = mItems.get(position);
        spinnerItem.setOnChangedListener(this, position);

        // we can't use the convertView because it keeps handing us spinner layouts (mSpinnerId)
        View spinnerItemView = mInflater.inflate(mSpinnerItemId, parent, false);
        int key = (position << 16) + getItemViewType(position);
        mViewCache.put(key, spinnerItemView);

        bindView(position, spinnerItemView, spinnerItem);

        return spinnerItemView;
    }


public RTHtml<RTImage, RTAudio, RTVideo> convert(final Spanned text, RTFormat.Html rtFormat) {
        mText = text;
        mRTFormat = rtFormat;

        mOut = new StringBuilder();
        mImages = new ArrayList<>();
        mParagraphStyles.clear();

        // convert paragraphs
        convertParagraphs();

        return new RTHtml<>(rtFormat, mOut.toString(), mImages);
    }

public
RTHtml
<
RTImage
,
RTAudio
,
RTVideo
>
convert
(
final
Spanned
text
,
RTFormat
.
Html
rtFormat
)
{
mText
=
text
;
mRTFormat
=
rtFormat
;
mOut
=
new
StringBuilder
(
)
;
mImages
=
new
ArrayList
<>
(
)
;
mParagraphStyles
.
clear
(
)
;
// convert paragraphs
convertParagraphs
(
)
;
return
new
RTHtml
<>
(
rtFormat
,
mOut
.
toString
(
)
,
mImages
)
;
}


private void withinParagraph(final Spanned text, int start, int end) {
        // create sorted set of CharacterStyles
        SortedSet<CharacterStyle> sortedSpans = new TreeSet<>((s1, s2) -> {
            int start1 = text.getSpanStart(s1);
            int start2 = text.getSpanStart(s2);
            if (start1 != start2)
                return start1 - start2;        // span which starts first comes first

            int end1 = text.getSpanEnd(s1);
            int end2 = text.getSpanEnd(s2);
            if (end1 != end2) return end2 - end1;                // longer span comes first

            // if the paragraphs have the same span [start, end] we compare their name
            // compare the name only because local + anonymous classes have no canonical name
            return s1.getClass().getName().compareTo(s2.getClass().getName());
        });
        List<CharacterStyle> spanList = Arrays.asList(text.getSpans(start, end, CharacterStyle.class));
        sortedSpans.addAll(spanList);

        // process paragraphs/divs
        convertText(text, start, end, sortedSpans);
    }

Convert a spanned text within a paragraph


public static File createUniqueFile(File targetFolder, String originalFile, boolean keepOriginal) {
        String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(originalFile);
        return createUniqueFile(targetFolder, originalFile, mimeType, keepOriginal);
    }

public static File createUniqueFile ( File targetFolder , String originalFile , boolean keepOriginal ) { String mimeType = MimeTypeMap . getSingleton ( ) . getMimeTypeFromExtension ( originalFile ) ; return createUniqueFile ( targetFolder , originalFile , mimeType , keepOriginal ) ; }

Creates a file with a non-conflicting file name in a specified folder based on an existing file name.

@param targetFolder The target folder (e.g. /sdcard/Android/data)
@param originalFile The source file including the path (e.g. /sdcard/image.jpg)
@param mimeType     If the originalFile has no extension (e.g. for files provided by picasa) we use the mime type
to determine the file extension
@return The non-conflicting file name in targetFolder (e.g. /sdcard/Android/data/158867324_201308071234568.jpg)

Creates a file with a non - conflicting file name in a specified folder based on an existing file name .


public static String determineOriginalFile(Context context, Uri uri) throws IllegalArgumentException {
        String originalFile = null;

        if (uri != null) {
            // Picasa on Android >= 3.0 or other files using content providers
            if (uri.getScheme().startsWith("content")) {
                originalFile = getPathFromUri(context, uri);
            }

            // Picasa on Android < 3.0
            if (uri.toString().matches("https?://\\w+\\.googleusercontent\\.com/.+")) {
                originalFile = uri.toString();
            }

            // local storage
            if (uri.getScheme().startsWith("file")) {
                originalFile = uri.toString().substring(7);
            }

            if (isNullOrEmpty(originalFile)) {
                throw new IllegalArgumentException("File path was null");
            }
        } else {
            throw new IllegalArgumentException("Image Uri was null!");
        }

        return originalFile;
    }

Retrieve local file path for an arbitrary Uri

@throws IllegalArgumentException If the uri is null or we can't resolve the uri to an absolute file path
(meaning the uri isn't valid)


public String namespace(String name, boolean attribute) {
        int colon = name.indexOf(':');
        if (colon == -1) {
            return attribute ? "" : theSchema.getURI();
        }
        String prefix = name.substring(0, colon);
        if (prefix.equals("xml")) {
            return "http://www.w3.org/XML/1998/namespace";
        } else {
            return ("urn:x-prefix:" + prefix).intern();
        }
    }

public String namespace ( String name , boolean attribute ) { int colon = name . indexOf ( ' ' ) ; if ( colon == - 1 ) { return attribute ? "" : theSchema . getURI ( ) ; } String prefix = name . substring ( 0 , colon ) ; if ( prefix . equals ( "xml" ) ) { return "http://www.w3.org/XML/1998/namespace" ; } else { return ( "urn:x-prefix:" + prefix ) . intern ( ) ; } }

Return a namespace name from a Qname. The attribute flag tells us whether
to return an empty namespace name if there is no prefix, or use the
schema default instead.

@param name      The Qname
@param attribute True if name is an attribute name
@return The namespace name

Return a namespace name from a Qname . The attribute flag tells us whether to return an empty namespace name if there is no prefix or use the schema default instead .


public String localName(String name) {
        int colon = name.indexOf(':');
        if (colon == -1) {
            return name;
        } else {
            return name.substring(colon + 1).intern();
        }
    }

public String localName ( String name ) { int colon = name . indexOf ( ' ' ) ; if ( colon == - 1 ) { return name ; } else { return name . substring ( colon + 1 ) . intern ( ) ; } }

Return a local name from a Qname.

@param name The Qname
@return The local name


public void setAttribute(AttributesImpl atts, String name, String type,
                             String value) {
        if (name.equals("xmlns") || name.startsWith("xmlns:")) {
            return;
        }
        ;
        String namespace = namespace(name, true);
        String localName = localName(name);
        int i = atts.getIndex(name);
        if (i == -1) {
            name = name.intern();
            if (type == null)
                type = "CDATA";
            if (!type.equals("CDATA"))
                value = normalize(value);
            atts.addAttribute(namespace, localName, name, type, value);
        } else {
            if (type == null)
                type = atts.getType(i);
            if (!type.equals("CDATA"))
                value = normalize(value);
            atts.setAttribute(i, namespace, localName, name, type, value);
        }
    }

public void setAttribute ( AttributesImpl atts , String name , String type , String value ) { if ( name . equals ( "xmlns" ) || name . startsWith ( "xmlns:" ) ) { return ; } ; String namespace = namespace ( name , true ) ; String localName = localName ( name ) ; int i = atts . getIndex ( name ) ; if ( i == - 1 ) { name = name . intern ( ) ; if ( type == null ) type = "CDATA" ; if ( ! type . equals ( "CDATA" ) ) value = normalize ( value ) ; atts . addAttribute ( namespace , localName , name , type , value ) ; } else { if ( type == null ) type = atts . getType ( i ) ; if ( ! type . equals ( "CDATA" ) ) value = normalize ( value ) ; atts . setAttribute ( i , namespace , localName , name , type , value ) ; } }

Sets an attribute and its value into an AttributesImpl object. Attempts
to set a namespace declaration are ignored.

@param atts  The AttributesImpl object
@param name  The name (Qname) of the attribute
@param type  The type of the attribute
@param value The value of the attribute

Sets an attribute and its value into an AttributesImpl object . Attempts to set a namespace declaration are ignored .


public void setAttribute(String name, String type, String value) {
        setAttribute(theAtts, name, type, value);
    }

public void setAttribute ( String name , String type , String value ) { setAttribute ( theAtts , name , type , value ) ; }

Sets an attribute and its value into this element type.

@param name  The name of the attribute
@param type  The type of the attribute
@param value The value of the attribute

Sets an attribute and its value into this element type .


static String getFontName(String filePath) {
        TTFRandomAccessFile in = null;
        try {
            RandomAccessFile file = new RandomAccessFile(filePath, "r");
            in = new TTFRandomAccessFile(file);
            return getTTFFontName(in, filePath);
        } catch (IOException e) {
            return null;    // Missing permissions or corrupted font file?
        }
        finally {
            IOUtils.closeQuietly(in);
        }
    }

Retrieve the file name for a system font.

@param filePath the full path for the font to retrieve.

@return The file name or null of none could be retrieved.

Retrieve the file name for a system font .


static String getFontName(AssetManager assets, String filePath) {
        TTFAssetInputStream in = null;
        try {
            InputStream file = assets.open(filePath, AssetManager.ACCESS_RANDOM);
            in = new TTFAssetInputStream(file);
            return getTTFFontName(in, filePath);
        } catch (FileNotFoundException e) {
            return null;    // Missing permissions?
        } catch (IOException e) {
            return null;    // Corrupted font file?
        }
        finally {
            IOUtils.closeQuietly(in);
        }
    }

static
String
getFontName
(
AssetManager
assets
,
String
filePath
)
{
TTFAssetInputStream
in
=
;
try
{
InputStream
file
=
assets
.
open
(
filePath
,
AssetManager
.
ACCESS_RANDOM
)
;
in
=
new
TTFAssetInputStream
(
file
)
;
return
getTTFFontName
(
in
,
filePath
)
;
}
catch
(
FileNotFoundException
e
)
{
return
;
// Missing permissions?
}
catch
(
IOException
e
)
{
return
;
// Corrupted font file?
}
finally
{
IOUtils
.
closeQuietly
(
in
)
;
}
}

Retrieve the file name for a font in the asset folder.

@param filePath the full path for the font in the asset folder to retrieve.

@return The file name or null of none could be retrieved.

Retrieve the file name for a font in the asset folder .


private void needNewBuffer(int newcount) {
        if (currentBufferIndex < buffers.size() - 1) {
            //Recycling old buffer
            filledBufferSum += currentBuffer.length;

            currentBufferIndex++;
            currentBuffer = buffers.get(currentBufferIndex);
        } else {
            //Creating new buffer
            int newBufferSize;
            if (currentBuffer == null) {
                newBufferSize = newcount;
                filledBufferSum = 0;
            } else {
                newBufferSize = Math.max(
                    currentBuffer.length << 1,
                    newcount - filledBufferSum);
                filledBufferSum += currentBuffer.length;
            }

            currentBufferIndex++;
            currentBuffer = new byte[newBufferSize];
            buffers.add(currentBuffer);
        }
    }

private
void
needNewBuffer
(
int
newcount
)
{
if
(
currentBufferIndex
<
buffers
.
size
(
)
1
)
{
//Recycling old buffer
filledBufferSum
+=
currentBuffer
.
length
;
currentBufferIndex
++
;
currentBuffer
=
buffers
.
get
(
currentBufferIndex
)
;
}
else
{
//Creating new buffer
int
newBufferSize
;
if
(
currentBuffer
==
)
{
newBufferSize
=
newcount
;
filledBufferSum
=
0
;
}
else
{
newBufferSize
=
Math
.
max
(
currentBuffer
.
length
<<
1
,
newcount
filledBufferSum
)
;
filledBufferSum
+=
currentBuffer
.
length
;
}
currentBufferIndex
++
;
currentBuffer
=
new
byte
[
newBufferSize
]
;
buffers
.
add
(
currentBuffer
)
;
}
}

Makes a new buffer available either by allocating
a new one or re-cycling an existing one.

@param newcount  the size of the buffer if one is created

Makes a new buffer available either by allocating a new one or re - cycling an existing one .


@Override
    public void write(byte[] b, int off, int len) {
        if ((off < 0)
                || (off > b.length)
                || (len < 0)
                || ((off + len) > b.length)
                || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        synchronized (this) {
            int newcount = count + len;
            int remaining = len;
            int inBufferPos = count - filledBufferSum;
            while (remaining > 0) {
                int part = Math.min(remaining, currentBuffer.length - inBufferPos);
                System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
                remaining -= part;
                if (remaining > 0) {
                    needNewBuffer(newcount);
                    inBufferPos = 0;
                }
            }
            count = newcount;
        }
    }

@ Override public void write ( byte [ ] b , int off , int len ) { if ( ( off < 0 ) || ( off > b . length ) || ( len < 0 ) || ( ( off + len ) > b . length ) || ( ( off + len ) < 0 ) ) { throw new IndexOutOfBoundsException ( ) ; } else if ( len == 0 ) { return ; } synchronized ( this ) { int newcount = count + len ; int remaining = len ; int inBufferPos = count - filledBufferSum ; while ( remaining > 0 ) { int part = Math . min ( remaining , currentBuffer . length - inBufferPos ) ; System . arraycopy ( b , off + len - remaining , currentBuffer , inBufferPos , part ) ; remaining -= part ; if ( remaining > 0 ) { needNewBuffer ( newcount ) ; inBufferPos = 0 ; } } count = newcount ; } }

Write the bytes to byte array.
@param b the bytes to write
@param off The start offset
@param len The number of bytes to write


@Override
    public synchronized void write(int b) {
        int inBufferPos = count - filledBufferSum;
        if (inBufferPos == currentBuffer.length) {
            needNewBuffer(count + 1);
            inBufferPos = 0;
        }
        currentBuffer[inBufferPos] = (byte) b;
        count++;
    }

@ Override public synchronized void write ( int b ) { int inBufferPos = count - filledBufferSum ; if ( inBufferPos == currentBuffer . length ) { needNewBuffer ( count + 1 ) ; inBufferPos = 0 ; } currentBuffer [ inBufferPos ] = ( byte ) b ; count ++ ; }

Write a byte to byte array.
@param b the byte to write


public synchronized int write(InputStream in) throws IOException {
        int readCount = 0;
        int inBufferPos = count - filledBufferSum;
        int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
        while (n != -1) {
            readCount += n;
            inBufferPos += n;
            count += n;
            if (inBufferPos == currentBuffer.length) {
                needNewBuffer(currentBuffer.length);
                inBufferPos = 0;
            }
            n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
        }
        return readCount;
    }

public synchronized int write ( InputStream in ) throws IOException { int readCount = 0 ; int inBufferPos = count - filledBufferSum ; int n = in . read ( currentBuffer , inBufferPos , currentBuffer . length - inBufferPos ) ; while ( n != - 1 ) { readCount += n ; inBufferPos += n ; count += n ; if ( inBufferPos == currentBuffer . length ) { needNewBuffer ( currentBuffer . length ) ; inBufferPos = 0 ; } n = in . read ( currentBuffer , inBufferPos , currentBuffer . length - inBufferPos ) ; } return readCount ; }

Writes the entire contents of the specified input stream to this
byte stream. Bytes from the input stream are read directly into the
internal buffers of this streams.

@param in the input stream to read from
@return total number of bytes read from the input stream
(and written to this stream)
@throws IOException if an I/O error occurs while reading the input stream
@since 1.4

Writes the entire contents of the specified input stream to this byte stream . Bytes from the input stream are read directly into the internal buffers of this streams .


public synchronized void writeTo(OutputStream out) throws IOException {
        int remaining = count;
        for (byte[] buf : buffers) {
            int c = Math.min(buf.length, remaining);
            out.write(buf, 0, c);
            remaining -= c;
            if (remaining == 0) {
                break;
            }
        }
    }

public synchronized void writeTo ( OutputStream out ) throws IOException { int remaining = count ; for ( byte [ ] buf : buffers ) { int c = Math . min ( buf . length , remaining ) ; out . write ( buf , 0 , c ) ; remaining -= c ; if ( remaining == 0 ) { break ; } } }

Writes the entire contents of this byte stream to the
specified output stream.

@param out  the output stream to write to
@throws IOException if an I/O error occurs, such as if the stream is closed
@see java.io.ByteArrayOutputStream#writeTo(OutputStream)

Writes the entire contents of this byte stream to the specified output stream .


public synchronized byte[] toByteArray() {
        int remaining = count;
        if (remaining == 0) {
            return EMPTY_BYTE_ARRAY;
        }
        byte newbuf[] = new byte[remaining];
        int pos = 0;
        for (byte[] buf : buffers) {
            int c = Math.min(buf.length, remaining);
            System.arraycopy(buf, 0, newbuf, pos, c);
            pos += c;
            remaining -= c;
            if (remaining == 0) {
                break;
            }
        }
        return newbuf;
    }

public synchronized byte [ ] toByteArray ( ) { int remaining = count ; if ( remaining == 0 ) { return EMPTY_BYTE_ARRAY ; } byte newbuf [ ] = new byte [ remaining ] ; int pos = 0 ; for ( byte [ ] buf : buffers ) { int c = Math . min ( buf . length , remaining ) ; System . arraycopy ( buf , 0 , newbuf , pos , c ) ; pos += c ; remaining -= c ; if ( remaining == 0 ) { break ; } } return newbuf ; }

Gets the curent contents of this byte stream as a byte array.
The result is independent of this stream.

@return the current contents of this output stream, as a byte array
@see java.io.ByteArrayOutputStream#toByteArray()

Gets the curent contents of this byte stream as a byte array . The result is independent of this stream .


@Override
    public void setFontSize(int size) {
        if (mFontSize != null) {
            if (size <= 0) {
                mFontSizeAdapter.updateSpinnerTitle("");
                mFontSizeAdapter.setSelectedItem(0);
                mFontSize.setSelection(0);
            } else {
                size = Helper.convertSpToPx(size);
                mFontSizeAdapter.updateSpinnerTitle(Integer.toString(size));
                for (int pos = 0; pos < mFontSizeAdapter.getCount(); pos++) {
                    FontSizeSpinnerItem item = mFontSizeAdapter.getItem(pos);
                    if (size == item.getFontSize()) {
                        mFontSizeAdapter.setSelectedItem(pos);
                        mFontSize.setSelection(pos);
                        break;
                    }
                }
            }
        }
    }

@ Override public void setFontSize ( int size ) { if ( mFontSize != null ) { if ( size <= 0 ) { mFontSizeAdapter . updateSpinnerTitle ( "" ) ; mFontSizeAdapter . setSelectedItem ( 0 ) ; mFontSize . setSelection ( 0 ) ; } else { size = Helper . convertSpToPx ( size ) ; mFontSizeAdapter . updateSpinnerTitle ( Integer . toString ( size ) ) ; for ( int pos = 0 ; pos < mFontSizeAdapter . getCount ( ) ; pos ++ ) { FontSizeSpinnerItem item = mFontSizeAdapter . getItem ( pos ) ; if ( size == item . getFontSize ( ) ) { mFontSizeAdapter . setSelectedItem ( pos ) ; mFontSize . setSelection ( pos ) ; break ; } } } } }

Set the text size.

@param size the text size, if -1 then no text size is set (e.g. when selection spans more than one text size)


public static String separatorsToUnix(String path) {
        if (path == null || path.indexOf(WINDOWS_SEPARATOR) == -1) {
            return path;
        }
        return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR);
    }

public static String separatorsToUnix ( String path ) { if ( path == null || path . indexOf ( WINDOWS_SEPARATOR ) == - 1 ) { return path ; } return path . replace ( WINDOWS_SEPARATOR , UNIX_SEPARATOR ) ; }

Converts all separators to the Unix separator of forward slash.

@param path  the path to be changed, null ignored
@return the updated path

Converts all separators to the Unix separator of forward slash .


private void recomputeFocus(MotionEvent event) {
        for (int i = 0; i < mHighlightViews.size(); i++) {
            HighlightView hv = mHighlightViews.get(i);
            hv.setFocus(false);
            hv.invalidate();
        }

        for (int i = 0; i < mHighlightViews.size(); i++) {
            HighlightView hv = mHighlightViews.get(i);
            int edge = hv.getHit(event.getX(), event.getY());
            if (edge != HighlightView.GROW_NONE) {
                if (!hv.hasFocus()) {
                    hv.setFocus(true);
                    hv.invalidate();
                }
                break;
            }
        }
        invalidate();
    }

private void recomputeFocus ( MotionEvent event ) { for ( int i = 0 ; i < mHighlightViews . size ( ) ; i ++ ) { HighlightView hv = mHighlightViews . get ( i ) ; hv . setFocus ( false ) ; hv . invalidate ( ) ; } for ( int i = 0 ; i < mHighlightViews . size ( ) ; i ++ ) { HighlightView hv = mHighlightViews . get ( i ) ; int edge = hv . getHit ( event . getX ( ) , event . getY ( ) ) ; if ( edge != HighlightView . GROW_NONE ) { if ( ! hv . hasFocus ( ) ) { hv . setFocus ( true ) ; hv . invalidate ( ) ; } break ; } } invalidate ( ) ; }


private void ensureVisible(HighlightView hv) {
        Rect r = hv.mDrawRect;

        int panDeltaX1 = Math.max(0, mLeft - r.left);
        int panDeltaX2 = Math.min(0, mRight - r.right);

        int panDeltaY1 = Math.max(0, mTop - r.top);
        int panDeltaY2 = Math.min(0, mBottom - r.bottom);

        int panDeltaX = panDeltaX1 != 0 ? panDeltaX1 : panDeltaX2;
        int panDeltaY = panDeltaY1 != 0 ? panDeltaY1 : panDeltaY2;

        if (panDeltaX != 0 || panDeltaY != 0) {
            panBy(panDeltaX, panDeltaY);
        }
    }

private void ensureVisible ( HighlightView hv ) { Rect r = hv . mDrawRect ; int panDeltaX1 = Math . max ( 0 , mLeft - r . left ) ; int panDeltaX2 = Math . min ( 0 , mRight - r . right ) ; int panDeltaY1 = Math . max ( 0 , mTop - r . top ) ; int panDeltaY2 = Math . min ( 0 , mBottom - r . bottom ) ; int panDeltaX = panDeltaX1 != 0 ? panDeltaX1 : panDeltaX2 ; int panDeltaY = panDeltaY1 != 0 ? panDeltaY1 : panDeltaY2 ; if ( panDeltaX != 0 || panDeltaY != 0 ) { panBy ( panDeltaX , panDeltaY ) ; } }

Pan the displayed image to make sure the cropping rectangle is visible.

Pan the displayed image to make sure the cropping rectangle is visible .


private void centerBasedOnHighlightView(HighlightView hv) {
        Rect drawRect = hv.mDrawRect;

        float width = drawRect.width();
        float height = drawRect.height();

        float thisWidth = getWidth();
        float thisHeight = getHeight();

        float z1 = thisWidth / width * .8F;
        float z2 = thisHeight / height * .8F;

        float zoom = Math.min(z1, z2);
        zoom = zoom * this.getScale();
        zoom = Math.max(1F, zoom);
        if ((Math.abs(zoom - getScale()) / zoom) > .1) {
            float[] coordinates = new float[]{hv.mCropRect.centerX(), hv.mCropRect.centerY()};
            getImageMatrix().mapPoints(coordinates);
            zoomTo(zoom, coordinates[0], coordinates[1], 300F);
        }

        ensureVisible(hv);
    }

private void centerBasedOnHighlightView ( HighlightView hv ) { Rect drawRect = hv . mDrawRect ; float width = drawRect . width ( ) ; float height = drawRect . height ( ) ; float thisWidth = getWidth ( ) ; float thisHeight = getHeight ( ) ; float z1 = thisWidth / width * .8F ; float z2 = thisHeight / height * .8F ; float zoom = Math . min ( z1 , z2 ) ; zoom = zoom * this . getScale ( ) ; zoom = Math . max ( 1F , zoom ) ; if ( ( Math . abs ( zoom - getScale ( ) ) / zoom ) > .1 ) { float [ ] coordinates = new float [ ] { hv . mCropRect . centerX ( ) , hv . mCropRect . centerY ( ) } ; getImageMatrix ( ) . mapPoints ( coordinates ) ; zoomTo ( zoom , coordinates [ 0 ] , coordinates [ 1 ] , 300F ) ; } ensureVisible ( hv ) ; }

view's center and scale according to the cropping rectangle.

view s center and scale according to the cropping rectangle .


public void resetDocumentLocator(String publicid, String systemid) {
        thePublicid = publicid;
        theSystemid = systemid;
        theLastLine = theLastColumn = theCurrentLine = theCurrentColumn = 0;
    }

public void resetDocumentLocator ( String publicid , String systemid ) { thePublicid = publicid ; theSystemid = systemid ; theLastLine = theLastColumn = theCurrentLine = theCurrentColumn = 0 ; }

Reset document locator, supplying systemid and publicid.

@param systemid System id
@param publicid Public id

Reset document locator supplying systemid and publicid .


public String validate(String value) {
        if (value == null) {
            return null;
        }
        for (int i = 0; i < patterns.length; i++) {
            Matcher matcher = patterns[i].matcher(value);
            if (matcher.matches()) {
                int count = matcher.groupCount();
                if (count == 1) {
                    return matcher.group(1);
                }
                StringBuffer buffer = new StringBuffer();
                for (int j = 0; j < count; j++) {
                    String component = matcher.group(j+1);
                    if (component != null) {
                        buffer.append(component);
                    }
                }
                return buffer.toString();
            }
        }
        return null;
    }

public String validate ( String value ) { if ( value == null ) { return null ; } for ( int i = 0 ; i < patterns . length ; i ++ ) { Matcher matcher = patterns [ i ] . matcher ( value ) ; if ( matcher . matches ( ) ) { int count = matcher . groupCount ( ) ; if ( count == 1 ) { return matcher . group ( 1 ) ; } StringBuffer buffer = new StringBuffer ( ) ; for ( int j = 0 ; j < count ; j ++ ) { String component = matcher . group ( j + 1 ) ; if ( component != null ) { buffer . append ( component ) ; } } return buffer . toString ( ) ; } } return null ; }

Validate a value against the set of regular expressions
returning a String value of the aggregated groups.

@param value The value to validate.
@return Aggregated String value comprised of the
groups matched if valid or null if invalid

Validate a value against the set of regular expressions returning a String value of the aggregated groups .


@SuppressLint("DefaultLocale")
    public void elementType(String name, int model, int memberOf, int flags) {
        ElementType e = new ElementType(name, model, memberOf, flags, this);
        theElementTypes.put(name.toLowerCase(), e);
        if (memberOf == M_ROOT)
            theRoot = e;
    }

@ SuppressLint ( "DefaultLocale" ) public void elementType ( String name , int model , int memberOf , int flags ) { ElementType e = new ElementType ( name , model , memberOf , flags , this ) ; theElementTypes . put ( name . toLowerCase ( ) , e ) ; if ( memberOf == M_ROOT ) theRoot = e ; }

Add or replace an element type for this schema.

@param name     Name (Qname) of the element
@param model    Models of the element's content as a vector of bits
@param memberOf Models the element is a member of as a vector of bits
@param flags    Flags for the element

Add or replace an element type for this schema .


public void attribute(String elemName, String attrName, String type,
                          String value) {
        ElementType e = getElementType(elemName);
        if (e == null) {
            throw new Error("Attribute " + attrName
                    + " specified for unknown element type " + elemName);
        }
        e.setAttribute(attrName, type, value);
    }

public
void
attribute
(
String
elemName
,
String
attrName
,
String
type
,
String
value
)
{
ElementType
e
=
getElementType
(
elemName
)
;
if
(
e
==
)
{
throw
new
Error
(
"Attribute "
+
attrName
+
" specified for unknown element type "
+
elemName
)
;
}
e
.
setAttribute
(
attrName
,
type
,
value
)
;
}

Add or replace a default attribute for an element type in this schema.

@param elemName Name (Qname) of the element type
@param attrName Name (Qname) of the attribute
@param type     Type of the attribute
@param value    Default value of the attribute; null if no default

Add or replace a default attribute for an element type in this schema .


public void parent(String name, String parentName) {
        ElementType child = getElementType(name);
        ElementType parent = getElementType(parentName);
        if (child == null) {
            throw new Error("No child " + name + " for parent " + parentName);
        }
        if (parent == null) {
            throw new Error("No parent " + parentName + " for child " + name);
        }
        child.setParent(parent);
    }

public
void
parent
(
String
name
,
String
parentName
)
{
ElementType
child
=
getElementType
(
name
)
;
ElementType
parent
=
getElementType
(
parentName
)
;
if
(
child
==
)
{
throw
new
Error
(
"No child "
+
name
+
" for parent "
+
parentName
)
;
}
if
(
parent
==
)
{
throw
new
Error
(
"No parent "
+
parentName
+
" for child "
+
name
)
;
}
child
.
setParent
(
parent
)
;
}

Specify natural parent of an element in this schema.

@param name       Name of the child element
@param parentName Name of the parent element

Specify natural parent of an element in this schema .


@SuppressLint("DefaultLocale")
    public ElementType getElementType(String name) {
        return (ElementType) (theElementTypes.get(name.toLowerCase()));
    }

@ SuppressLint ( "DefaultLocale" ) public ElementType getElementType ( String name ) { return ( ElementType ) ( theElementTypes . get ( name . toLowerCase ( ) ) ) ; }

Get an ElementType by name.

@param name Name (Qname) of the element type
@return The corresponding ElementType


public int getEntity(String name) {
        // System.err.println("%% Looking up entity " + name);
        Integer ch = (Integer) theEntities.get(name);
        if (ch == null)
            return 0;
        return ch.intValue();
    }

Get an entity value by name.

@param name Name of the entity
@return The corresponding character, or 0 if none


public static void cleanupParagraphs(RTEditText editor, Effect...exclude) {
        cleanupParagraphs(editor, Effects.ALIGNMENT, exclude);
        cleanupParagraphs(editor, Effects.INDENTATION, exclude);
        cleanupParagraphs(editor, Effects.BULLET, exclude);
        cleanupParagraphs(editor, Effects.NUMBER, exclude);
    }

public static void cleanupParagraphs ( RTEditText editor , Effect ... exclude ) { cleanupParagraphs ( editor , Effects . ALIGNMENT , exclude ) ; cleanupParagraphs ( editor , Effects . INDENTATION , exclude ) ; cleanupParagraphs ( editor , Effects . BULLET , exclude ) ; cleanupParagraphs ( editor , Effects . NUMBER , exclude ) ; }

This important method makes sure that all paragraph effects are applied to whole paragraphs.
While it's optimized for performance it's still an expensive operation so it shouldn't be
called too often.

@param exclude if an Effect has just been applied, there's no need to cleanup that Effect.

This important method makes sure that all paragraph effects are applied to whole paragraphs . While it s optimized for performance it s still an expensive operation so it shouldn t be called too often .


public void applyToSelection(RTEditText editor, V value) {
        Selection selection = getSelection(editor);
        // SPAN_INCLUSIVE_INCLUSIVE is default for empty spans
        int flags = selection.isEmpty() ? Spanned.SPAN_INCLUSIVE_INCLUSIVE : Spanned.SPAN_EXCLUSIVE_INCLUSIVE;

        Spannable str = editor.getText();

        for (RTSpan<V> span : getSpans(str, selection, SpanCollectMode.SPAN_FLAGS)) {
            boolean sameSpan = span.getValue().equals(value);
            int spanStart = str.getSpanStart(span);
            if (spanStart < selection.start()) {
                // process preceding spans
                if (sameSpan) {
                    // we have a preceding span --> use SPAN_EXCLUSIVE_INCLUSIVE instead of SPAN_INCLUSIVE_INCLUSIVE
                    flags = Spanned.SPAN_EXCLUSIVE_INCLUSIVE;
                    selection.offset(selection.start() - spanStart, 0);
                } else {
                    str.setSpan(newSpan(span.getValue()), spanStart, selection.start(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
            }
            int spanEnd = str.getSpanEnd(span);
            if (spanEnd > selection.end()) {
                // process succeeding spans
                if (sameSpan) {
                    selection.offset(0, spanEnd - selection.end());
                } else {
                    str.setSpan(newSpan(span.getValue()), selection.end(), spanEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
                }
            }
            str.removeSpan(span);
        }

        if (value != null) {
            RTSpan<V> newSpan = newSpan(value);
            if (newSpan != null) {
                str.setSpan(newSpan, selection.start(), selection.end(), flags);
            }
        }
    }

Apply this effect to the selection.
If value is Null then the effect will be removed from the current selection.

@param editor The editor to apply the effect to (current selection)
@param value  The value to apply (depends on the Effect)

Apply this effect to the selection . If value is Null then the effect will be removed from the current selection .


protected boolean isValidDomain(String domain) {
        // see if domain is an IP address in brackets
        Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain);

        if (ipDomainMatcher.matches()) {
            InetAddressValidator inetAddressValidator =
                    InetAddressValidator.getInstance();
            return inetAddressValidator.isValid(ipDomainMatcher.group(1));
        }
        // Domain is symbolic name
        DomainValidator domainValidator =
                DomainValidator.getInstance(allowLocal);
        return domainValidator.isValid(domain) ||
                domainValidator.isValidTld(domain);
    }

Returns true if the domain component of an email address is valid.

@param domain being validated, may be in IDN format
@return true if the email address's domain is valid.

Returns true if the domain component of an email address is valid .


synchronized void executed(RTEditText editor, Operation op) {
        Stack<Operation> undoStack = getUndoStack(editor);
        Stack<Operation> redoStack = getRedoStack(editor);

        // if operations are executed in a quick succession we "merge" them to have but one
        // -> saves memory and makes more sense from a user perspective (each key stroke an undo? -> no way)
        while (!undoStack.empty() && op.canMerge(undoStack.peek())) {
            Operation previousOp = undoStack.pop();
            op.merge(previousOp);
        }

        push(op, undoStack);
        redoStack.clear();
    }

Call this when an operation is performed to add it to the undo stack.

@param editor The rich text editor the operation was performed on
@param op     The Operation that was performed

Call this when an operation is performed to add it to the undo stack .


synchronized void redo(RTEditText editor) {
        Stack<Operation> redoStack = getRedoStack(editor);
        if (!redoStack.empty()) {
            Stack<Operation> undoStack = getUndoStack(editor);
            Operation op = redoStack.pop();
            push(op, undoStack);
            op.redo(editor);
            while (!redoStack.empty() && op.canMerge(redoStack.peek())) {
                op = redoStack.pop();
                push(op, undoStack);
                op.redo(editor);
            }
        }
    }

synchronized void redo ( RTEditText editor ) { Stack < Operation > redoStack = getRedoStack ( editor ) ; if ( ! redoStack . empty ( ) ) { Stack < Operation > undoStack = getUndoStack ( editor ) ; Operation op = redoStack . pop ( ) ; push ( op , undoStack ) ; op . redo ( editor ) ; while ( ! redoStack . empty ( ) && op . canMerge ( redoStack . peek ( ) ) ) { op = redoStack . pop ( ) ; push ( op , undoStack ) ; op . redo ( editor ) ; } } }

Re-do the last undone operation for a specific rich text editor

@param editor Re-do an operation for this rich text editor

Re - do the last undone operation for a specific rich text editor


private static String replaceAll(String source, String search, String replace) {
        if (USE_REPLACE_ALL) {
            return source.replaceAll(search, replace);
        } else {
            Pattern p = Pattern.compile(search);
            Matcher m = p.matcher(source);
            StringBuffer sb = new StringBuffer();
            boolean atLeastOneFound = false;
            while (m.find()) {
                m.appendReplacement(sb, replace);
                atLeastOneFound = true;
            }
            if (atLeastOneFound) {
                m.appendTail(sb);
                return sb.toString();
            } else {
                return source;
            }
        }
    }

private static String replaceAll ( String source , String search , String replace ) { if ( USE_REPLACE_ALL ) { return source . replaceAll ( search , replace ) ; } else { Pattern p = Pattern . compile ( search ) ; Matcher m = p . matcher ( source ) ; StringBuffer sb = new StringBuffer ( ) ; boolean atLeastOneFound = false ; while ( m . find ( ) ) { m . appendReplacement ( sb , replace ) ; atLeastOneFound = true ; } if ( atLeastOneFound ) { m . appendTail ( sb ) ; return sb . toString ( ) ; } else { return source ; } } }

A memory optimized algorithm for String.replaceAll

A memory optimized algorithm for String . replaceAll


private synchronized ThreadStatus getOrCreateThreadStatus(Thread t) {
        ThreadStatus status = mThreadStatus.get(t);
        if (status == null) {
            status = new ThreadStatus();
            mThreadStatus.put(t, status);
        }
        return status;
    }

private synchronized ThreadStatus getOrCreateThreadStatus ( Thread t ) { ThreadStatus status = mThreadStatus . get ( t ) ; if ( status == null ) { status = new ThreadStatus ( ) ; mThreadStatus . put ( t , status ) ; } return status ; }

Get thread status and create one if specified.

Get thread status and create one if specified .


private synchronized void setDecodingOptions(Thread t, BitmapFactory.Options options) {
        getOrCreateThreadStatus(t).mOptions = options;
    }

private synchronized void setDecodingOptions ( Thread t , BitmapFactory . Options options ) { getOrCreateThreadStatus ( t ) . mOptions = options ; }

The following three methods are used to keep track of
BitmapFaction.Options used for decoding and cancelling.

The following three methods are used to keep track of BitmapFaction . Options used for decoding and cancelling .


public synchronized boolean canThreadDecoding(Thread t) {
        ThreadStatus status = mThreadStatus.get(t);
        if (status == null) {
            // allow decoding by default
            return true;
        }

        return (status.mState != State.CANCEL);
    }

public
synchronized
boolean
canThreadDecoding
(
Thread
t
)
{
ThreadStatus
status
=
mThreadStatus
.
get
(
t
)
;
if
(
status
==
)
{
// allow decoding by default
return
true
;
}
return
(
status
.
mState
!=
State
.
CANCEL
)
;
}

The following three methods are used to keep track of which thread is
being disabled for bitmap decoding.

The following three methods are used to keep track of which thread is being disabled for bitmap decoding .


public Bitmap decodeFileDescriptor(FileDescriptor fd, BitmapFactory.Options options) {
        if (options.mCancel) {
            return null;
        }

        Thread thread = Thread.currentThread();
        if (!canThreadDecoding(thread)) {
            return null;
        }

        setDecodingOptions(thread, options);
        Bitmap b = BitmapFactory.decodeFileDescriptor(fd, null, options);

        removeDecodingOptions(thread);
        return b;
    }

public Bitmap decodeFileDescriptor ( FileDescriptor fd , BitmapFactory . Options options ) { if ( options . mCancel ) { return null ; } Thread thread = Thread . currentThread ( ) ; if ( ! canThreadDecoding ( thread ) ) { return null ; } setDecodingOptions ( thread , options ) ; Bitmap b = BitmapFactory . decodeFileDescriptor ( fd , null , options ) ; removeDecodingOptions ( thread ) ; return b ; }

The real place to delegate bitmap decoding to BitmapFactory.

The real place to delegate bitmap decoding to BitmapFactory .


public static SortedSet<RTTypeface> getFonts(Context context) {
        /*
         * Fonts from the assets folder
         */
        Map<String, String> assetFonts = getAssetFonts(context);
        AssetManager assets = context.getResources().getAssets();
        for (String fontName : assetFonts.keySet()) {
            String filePath = assetFonts.get(fontName);
            if (!ALL_FONTS.contains(fontName)) {
                try {
                    Typeface typeface = Typeface.createFromAsset(assets, filePath);
                    ALL_FONTS.add(new RTTypeface(fontName, typeface));
                }
                catch (Exception e) {
                    // this can happen if we don't have access to the font or it's not a font or...
                }
            }
        }

        /*
         * Fonts from the system
         */
        Map<String, String> systemFonts = getSystemFonts();
        for (String fontName : systemFonts.keySet()) {
            String filePath = systemFonts.get(fontName);
            if (!ALL_FONTS.contains(fontName)) {
                try {
                    Typeface typeface = Typeface.createFromFile(filePath);
                    ALL_FONTS.add(new RTTypeface(fontName, typeface));
                }
                catch (Exception e) {
                    // this can happen if we don't have access to the font or it's not a font or...
                }
            }
        }

        return ALL_FONTS;
    }

/*
         * Fonts from the assets folder
         */

/*
         * Fonts from the system
         */

Retrieve the fonts from the asset and the system folder.

@return A Map mapping the name of the font to the Typeface.
If the name can't be retrieved the file name will be used (e.g. arial.ttf).

Retrieve the fonts from the asset and the system folder .


private static Map<String, String> getAssetFonts(Context context) {
        synchronized (ASSET_FONTS_BY_NAME) {
            /*
             * Let's do this only once because it's expensive and the result won't change in any case.
             */
            if (ASSET_FONTS_BY_NAME.isEmpty()) {
                AssetManager assets = context.getResources().getAssets();

                Collection<String> fontFiles = AssetIndex.getAssetIndex(context);
                if (fontFiles == null || fontFiles.isEmpty()) {
                    fontFiles = listFontFiles(context.getResources());
                }

                for (String filePath : fontFiles) {
                    if (filePath.toLowerCase(Locale.getDefault()).endsWith("ttf")) {
                        String fontName = TTFAnalyzer.getFontName(assets, filePath);
                        if (fontName == null) {
                            fontName = getFileName(filePath);
                        }
                        ASSET_FONTS_BY_NAME.put(fontName, filePath);
                    }
                }
            }

            return ASSET_FONTS_BY_NAME;
        }
    }

Retrieve the fonts from the asset folder.

@return A Map mapping name of the font to the file path.
If the name can't be retrieved the file name will be used (e.g. arial.ttf).

Retrieve the fonts from the asset folder .


private static Map<String, String> getSystemFonts() {
        synchronized (SYSTEM_FONTS_BY_NAME) {
            for (String fontDir : FONT_DIRS) {
                File dir = new File(fontDir);

                if (!dir.exists()) continue;

                File[] files = dir.listFiles();

                if (files == null) continue;

                for (File file : files) {
                    String filePath = file.getAbsolutePath();
                    if (!SYSTEM_FONTS_BY_PATH.containsKey(filePath)) {
                        String fontName = TTFAnalyzer.getFontName(file.getAbsolutePath());
                        if (fontName == null) {
                            fontName = getFileName(filePath);
                        }
                        SYSTEM_FONTS_BY_PATH.put(filePath, fontName);
                        SYSTEM_FONTS_BY_NAME.put(fontName, filePath);
                    }
                }
            }

            return SYSTEM_FONTS_BY_NAME;
        }
    }

private static Map < String , String > getSystemFonts ( ) { synchronized ( SYSTEM_FONTS_BY_NAME ) { for ( String fontDir : FONT_DIRS ) { File dir = new File ( fontDir ) ; if ( ! dir . exists ( ) ) continue ; File [ ] files = dir . listFiles ( ) ; if ( files == null ) continue ; for ( File file : files ) { String filePath = file . getAbsolutePath ( ) ; if ( ! SYSTEM_FONTS_BY_PATH . containsKey ( filePath ) ) { String fontName = TTFAnalyzer . getFontName ( file . getAbsolutePath ( ) ) ; if ( fontName == null ) { fontName = getFileName ( filePath ) ; } SYSTEM_FONTS_BY_PATH . put ( filePath , fontName ) ; SYSTEM_FONTS_BY_NAME . put ( fontName , filePath ) ; } } } return SYSTEM_FONTS_BY_NAME ; } }

Retrieve the fonts from the system folders.

@return A Map mapping name of the font to the file path.
If the name can't be retrieved the file name will be used (e.g. arial.ttf).

Retrieve the fonts from the system folders .


private void setup() {
        if (theSchema == null) theSchema = new HTMLSchema();
        if (theScanner == null) theScanner = new HTMLScanner();
        if (theAutoDetector == null) {
            theAutoDetector = new AutoDetector() {
                public Reader autoDetectingReader(InputStream i) {
                    return new InputStreamReader(i);
                }
            };
        }
        theStack = new Element(theSchema.getElementType("<root>"), defaultAttributes);
        thePCDATA = new Element(theSchema.getElementType("<pcdata>"), defaultAttributes);
        theNewElement = null;
        theAttributeName = null;
        thePITarget = null;
        theSaved = null;
        theEntity = 0;
        virginStack = true;
        theDoctypeName = theDoctypePublicId = theDoctypeSystemId = null;
    }

private void setup ( ) { if ( theSchema == null ) theSchema = new HTMLSchema ( ) ; if ( theScanner == null ) theScanner = new HTMLScanner ( ) ; if ( theAutoDetector == null ) { theAutoDetector = new AutoDetector ( ) { public Reader autoDetectingReader ( InputStream i ) { return new InputStreamReader ( i ) ; } } ; } theStack = new Element ( theSchema . getElementType ( "<root>" ) , defaultAttributes ) ; thePCDATA = new Element ( theSchema . getElementType ( "<pcdata>" ) , defaultAttributes ) ; theNewElement = null ; theAttributeName = null ; thePITarget = null ; theSaved = null ; theEntity = 0 ; virginStack = true ; theDoctypeName = theDoctypePublicId = theDoctypeSystemId = null ; }

Sets up instance variables that haven't been set by setFeature

Sets up instance variables that haven t been set by setFeature


private Reader getReader(InputSource s) throws SAXException, IOException {
        Reader r = s.getCharacterStream();
        InputStream i = s.getByteStream();
        String encoding = s.getEncoding();
        String publicid = s.getPublicId();
        String systemid = s.getSystemId();
        if (r == null) {
            if (i == null)
                i = getInputStream(publicid, systemid);
            // i = new BufferedInputStream(i);
            if (encoding == null) {
                r = theAutoDetector.autoDetectingReader(i);
            } else {
                try {
                    r = new InputStreamReader(i, encoding);
                } catch (UnsupportedEncodingException e) {
                    r = new InputStreamReader(i);
                }
            }
        }
        // r = new BufferedReader(r);
        return r;
    }

private
Reader
getReader
(
InputSource
s
)
throws
SAXException
,
IOException
{
Reader
r
=
s
.
getCharacterStream
(
)
;
InputStream
i
=
s
.
getByteStream
(
)
;
String
encoding
=
s
.
getEncoding
(
)
;
String
publicid
=
s
.
getPublicId
(
)
;
String
systemid
=
s
.
getSystemId
(
)
;
if
(
r
==
)
{
if
(
i
==
)
i
=
getInputStream
(
publicid
,
systemid
)
;
// i = new BufferedInputStream(i);
if
(
encoding
==
)
{
r
=
theAutoDetector
.
autoDetectingReader
(
i
)
;
}
else
{
try
{
r
=
new
InputStreamReader
(
i
,
encoding
)
;
}
catch
(
UnsupportedEncodingException
e
)
{
r
=
new
InputStreamReader
(
i
)
;
}
}
}
// r = new BufferedReader(r);
return
r
;
}

Buffer both the InputStream and the Reader


private InputStream getInputStream(String publicid, String systemid) throws IOException, SAXException {
        URL basis = new URL("file", "", System.getProperty("user.dir") + "/.");
        URL url = new URL(basis, systemid);
        URLConnection c = url.openConnection();
        return c.getInputStream();
    }

private InputStream getInputStream ( String publicid , String systemid ) throws IOException , SAXException { URL basis = new URL ( "file" , "" , System . getProperty ( "user.dir" ) + "/." ) ; URL url = new URL ( basis , systemid ) ; URLConnection c = url . openConnection ( ) ; return c . getInputStream ( ) ; }

Get an InputStream based on a publicid and a systemid


@Override
    public void adup(char[] buff, int offset, int length) throws SAXException {
        if (theNewElement != null && theAttributeName != null) {
            theNewElement.setAttribute(theAttributeName, null, theAttributeName);
            theAttributeName = null;
        }
    }

@ Override public void adup ( char [ ] buff , int offset , int length ) throws SAXException { if ( theNewElement != null && theAttributeName != null ) { theNewElement . setAttribute ( theAttributeName , null , theAttributeName ) ; theAttributeName = null ; } }


private String expandEntities(String src) {
        int refStart = -1;
        int len = src.length();
        char[] dst = new char[len];
        int dstlen = 0;
        for (int i = 0; i < len; i++) {
            char ch = src.charAt(i);
            dst[dstlen++] = ch;
            if (ch == '&' && refStart == -1) {
                // start of a ref excluding &
                refStart = dstlen;
            } else if (refStart == -1) {
                // not in a ref
            } else if (Character.isLetter(ch) || Character.isDigit(ch)
                    || ch == '#') {
                // valid entity char
            } else if (ch == ';') {
                // properly terminated ref
                int ent = lookupEntity(dst, refStart, dstlen - refStart - 1);
                if (ent > 0xFFFF) {
                    ent -= 0x10000;
                    dst[refStart - 1] = (char) ((ent >> 10) + 0xD800);
                    dst[refStart] = (char) ((ent & 0x3FF) + 0xDC00);
                    dstlen = refStart + 1;
                } else if (ent != 0) {
                    dst[refStart - 1] = (char) ent;
                    dstlen = refStart;
                }
                refStart = -1;
            } else {
                // improperly terminated ref
                refStart = -1;
            }
        }
        return new String(dst, 0, dstlen);
    }

private
String
expandEntities
(
String
src
)
{
int
refStart
=
1
;
int
len
=
src
.
length
(
)
;
char
[
]
dst
=
new
char
[
len
]
;
int
dstlen
=
0
;
for
(
int
i
=
0
;
i
<
len
;
i
++
)
{
char
ch
=
src
.
charAt
(
i
)
;
dst
[
dstlen
++
]
=
ch
;
if
(
ch
==
'
'
&&
refStart
==
1
)
{
// start of a ref excluding &
refStart
=
dstlen
;
}
else
if
(
refStart
==
1
)
{
// not in a ref
}
else
if
(
Character
.
isLetter
(
ch
)
||
Character
.
isDigit
(
ch
)
||
ch
==
'
'
)
{
// valid entity char
}
else
if
(
ch
==
'
'
)
{
// properly terminated ref
int
ent
=
lookupEntity
(
dst
,
refStart
,
dstlen
refStart
1
)
;
if
(
ent
>
0xFFFF
)
{
ent
-=
0x10000
;
dst
[
refStart
1
]
=
(
char
)
(
(
ent
>>
10
)
+
0xD800
)
;
dst
[
refStart
]
=
(
char
)
(
(
ent
&
0x3FF
)
+
0xDC00
)
;
dstlen
=
refStart
+
1
;
}
else
if
(
ent
!=
0
)
{
dst
[
refStart
1
]
=
(
char
)
ent
;
dstlen
=
refStart
;
}
refStart
=
1
;
}
else
{
// improperly terminated ref
refStart
=
1
;
}
}
return
new
String
(
dst
,
0
,
dstlen
)
;
}


private int lookupEntity(char[] buff, int offset, int length) {
        int result = 0;
        if (length < 1)
            return result;
        // length) + "]");
        if (buff[offset] == '#') {
            if (length > 1
                    && (buff[offset + 1] == 'x' || buff[offset + 1] == 'X')) {
                try {
                    return Integer.parseInt(new String(buff, offset + 2,
                            length - 2), 16);
                } catch (NumberFormatException e) {
                    return 0;
                }
            }
            try {
                return Integer.parseInt(
                        new String(buff, offset + 1, length - 1), 10);
            } catch (NumberFormatException e) {
                return 0;
            }
        }
        return theSchema.getEntity(new String(buff, offset, length));
    }

private
int
lookupEntity
(
char
[
]
buff
,
int
offset
,
int
length
)
{
int
result
=
0
;
if
(
length
<
1
)
return
result
;
// length) + "]");
if
(
buff
[
offset
]
==
'
'
)
{
if
(
length
>
1
&&
(
buff
[
offset
+
1
]
==
'
'
||
buff
[
offset
+
1
]
==
'
'
)
)
{
try
{
return
Integer
.
parseInt
(
new
String
(
buff
,
offset
+
2
,
length
2
)
,
16
)
;
}
catch
(
NumberFormatException
e
)
{
return
0
;
}
}
try
{
return
Integer
.
parseInt
(
new
String
(
buff
,
offset
+
1
,
length
1
)
,
10
)
;
}
catch
(
NumberFormatException
e
)
{
return
0
;
}
}
return
theSchema
.
getEntity
(
new
String
(
buff
,
offset
,
length
)
)
;
}

deferring to the schema for named ones .


private void restart(Element e) throws SAXException {
        while (theSaved != null && theStack.canContain(theSaved) && (e == null || theSaved.canContain(e))) {
            Element next = theSaved.next();
            push(theSaved);
            theSaved = next;
        }
    }

private void restart ( Element e ) throws SAXException { while ( theSaved != null && theStack . canContain ( theSaved ) && ( e == null || theSaved . canContain ( e ) ) ) { Element next = theSaved . next ( ) ; push ( theSaved ) ; theSaved = next ; } }

e is the next element to be started, if we know what it is

e is the next element to be started if we know what it is


private void pop() throws SAXException {
        if (theStack == null)
            return; // empty stack
        String name = theStack.name();
        String localName = theStack.localName();
        String namespace = theStack.namespace();
        String prefix = prefixOf(name);

        if (!namespaces)
            namespace = localName = "";
        theContentHandler.endElement(namespace, localName, name);
        if (foreign(prefix, namespace)) {
            theContentHandler.endPrefixMapping(prefix);
            // "] for elements to " + namespace);
        }
        Attributes atts = theStack.atts();
        for (int i = atts.getLength() - 1; i >= 0; i--) {
            String attNamespace = atts.getURI(i);
            String attPrefix = prefixOf(atts.getQName(i));
            if (foreign(attPrefix, attNamespace)) {
                theContentHandler.endPrefixMapping(attPrefix);
                // "] for attributes to " + attNamespace);
            }
        }
        theStack = theStack.next();
    }


private void restartablyPop() throws SAXException {
        Element popped = theStack;
        pop();
        if (restartElements && (popped.flags() & Schema.F_RESTART) != 0) {
            popped.anonymize();
            popped.setNext(theSaved);
            theSaved = popped;
        }
    }

private void restartablyPop ( ) throws SAXException { Element popped = theStack ; pop ( ) ; if ( restartElements && ( popped . flags ( ) & Schema . F_RESTART ) != 0 ) { popped . anonymize ( ) ; popped . setNext ( theSaved ) ; theSaved = popped ; } }


private String prefixOf(String name) {
        int i = name.indexOf(':');
        String prefix = "";
        if (i != -1)
            prefix = name.substring(0, i);
        return prefix;
    }

private String prefixOf ( String name ) { int i = name . indexOf ( ' ' ) ; String prefix = "" ; if ( i != - 1 ) prefix = name . substring ( 0 , i ) ; return prefix ; }


private boolean foreign(String prefix, String namespace) {
        // " for foreignness -- ");
        boolean foreign = !(prefix.equals("") || namespace.equals("") || namespace.equals(theSchema.getURI()));
        return foreign;
    }

private
boolean
foreign
(
String
prefix
,
String
namespace
)
{
// " for foreignness -- ");
boolean
foreign
=
!
(
prefix
.
equals
(
""
)
||
namespace
.
equals
(
""
)
||
namespace
.
equals
(
theSchema
.
getURI
(
)
)
)
;
return
foreign
;
}


private static String trimquotes(String in) {
        if (in == null)
            return in;
        int length = in.length();
        if (length == 0)
            return in;
        char s = in.charAt(0);
        char e = in.charAt(length - 1);
        if (s == e && (s == '\'' || s == '"')) {
            in = in.substring(1, in.length() - 1);
        }
        return in;
    }

private static String trimquotes ( String in ) { if ( in == null ) return in ; int length = in . length ( ) ; if ( length == 0 ) return in ; char s = in . charAt ( 0 ) ; char e = in . charAt ( length - 1 ) ; if ( s == e && ( s == ' ' || s == ' ' ) ) { in = in . substring ( 1 , in . length ( ) - 1 ) ; } return in ; }

If the String is quoted, trim the quotes.

If the String is quoted trim the quotes .


private static String[] split(String val) throws IllegalArgumentException {
        val = val.trim();
        if (val.length() == 0) {
            return new String[0];
        } else {
            ArrayList<String> l = new ArrayList<String>();
            int s = 0;
            int e = 0;
            boolean sq = false; // single quote
            boolean dq = false; // double quote
            char lastc = 0;
            int len = val.length();
            for (e = 0; e < len; e++) {
                char c = val.charAt(e);
                if (!dq && c == '\'' && lastc != '\\') {
                    sq = !sq;
                    if (s < 0)
                        s = e;
                } else if (!sq && c == '\"' && lastc != '\\') {
                    dq = !dq;
                    if (s < 0)
                        s = e;
                } else if (!sq && !dq) {
                    if (Character.isWhitespace(c)) {
                        if (s >= 0)
                            l.add(val.substring(s, e));
                        s = -1;
                    } else if (s < 0 && c != ' ') {
                        s = e;
                    }
                }
                lastc = c;
            }
            l.add(val.substring(s, e));
            return (String[]) l.toArray(new String[0]);
        }
    }

private
static
String
[
]
split
(
String
val
)
throws
IllegalArgumentException
{
val
=
val
.
trim
(
)
;
if
(
val
.
length
(
)
==
0
)
{
return
new
String
[
0
]
;
}
else
{
ArrayList
<
String
>
l
=
new
ArrayList
<
String
>
(
)
;
int
s
=
0
;
int
e
=
0
;
boolean
sq
=
false
;
// single quote
boolean
dq
=
false
;
// double quote
char
lastc
=
0
;
int
len
=
val
.
length
(
)
;
for
(
e
=
0
;
e
<
len
;
e
++
)
{
char
c
=
val
.
charAt
(
e
)
;
if
(
!
dq
&&
c
==
'
'
&&
lastc
!=
'
'
)
{
sq
=
!
sq
;
if
(
s
<
0
)
s
=
e
;
}
else
if
(
!
sq
&&
c
==
'
'
&&
lastc
!=
'
'
)
{
dq
=
!
dq
;
if
(
s
<
0
)
s
=
e
;
}
else
if
(
!
sq
&&
!
dq
)
{
if
(
Character
.
isWhitespace
(
c
)
)
{
if
(
s
>=
0
)
l
.
add
(
val
.
substring
(
s
,
e
)
)
;
s
=
1
;
}
else
if
(
s
<
0
&&
c
!=
'
'
)
{
s
=
e
;
}
}
lastc
=
c
;
}
l
.
add
(
val
.
substring
(
s
,
e
)
)
;
return
(
String
[
]
)
l
.
toArray
(
new
String
[
0
]
)
;
}
}

Recognises quotes around a phrase and doesn't split it.

Recognises quotes around a phrase and doesn t split it .


private void rectify(Element e) throws SAXException {
        Element sp;
        while (true) {
            for (sp = theStack; sp != null; sp = sp.next()) {
                if (sp.canContain(e))
                    break;
            }
            if (sp != null)
                break;
            ElementType parentType = e.parent();
            if (parentType == null)
                break;
            Element parent = new Element(parentType, defaultAttributes);
            // parent.name());
            parent.setNext(e);
            e = parent;
        }
        if (sp == null)
            return; // don't know what to do
        while (theStack != sp) {
            if (theStack == null || theStack.next() == null
                    || theStack.next().next() == null)
                break;
            restartablyPop();
        }
        while (e != null) {
            Element nexte = e.next();
            if (!e.name().equals("<pcdata>"))
                push(e);
            e = nexte;
            restart(e);
        }
        theNewElement = null;
    }

private
void
rectify
(
Element
e
)
throws
SAXException
{
Element
sp
;
while
(
true
)
{
for
(
sp
=
theStack
;
sp
!=
;
sp
=
sp
.
next
(
)
)
{
if
(
sp
.
canContain
(
e
)
)
break
;
}
if
(
sp
!=
)
break
;
ElementType
parentType
=
e
.
parent
(
)
;
if
(
parentType
==
)
break
;
Element
parent
=
new
Element
(
parentType
,
defaultAttributes
)
;
// parent.name());
parent
.
setNext
(
e
)
;
e
=
parent
;
}
if
(
sp
==
)
return
;
// don't know what to do
while
(
theStack
!=
sp
)
{
if
(
theStack
==
||
theStack
.
next
(
)
==
||
theStack
.
next
(
)
.
next
(
)
==
)
break
;
restartablyPop
(
)
;
}
while
(
e
!=
)
{
Element
nexte
=
e
.
next
(
)
;
if
(
!
e
.
name
(
)
.
equals
(
"<pcdata>"
)
)
push
(
e
)
;
e
=
nexte
;
restart
(
e
)
;
}
theNewElement
=
;
}

so that the argument can be safely pushed


private String makeName(char[] buff, int offset, int length) {
        StringBuffer dst = new StringBuffer(length + 2);
        boolean seenColon = false;
        boolean start = true;
        // String src = new String(buff, offset, length); // DEBUG
        for (; length-- > 0; offset++) {
            char ch = buff[offset];
            if (Character.isLetter(ch) || ch == '_') {
                start = false;
                dst.append(ch);
            } else if (Character.isDigit(ch) || ch == '-' || ch == '.') {
                if (start)
                    dst.append('_');
                start = false;
                dst.append(ch);
            } else if (ch == ':' && !seenColon) {
                seenColon = true;
                if (start)
                    dst.append('_');
                start = true;
                dst.append(translateColons ? '_' : ch);
            }
        }
        int dstLength = dst.length();
        if (dstLength == 0 || dst.charAt(dstLength - 1) == ':')
            dst.append('_');
        return dst.toString().intern();
    }


public void onSaveInstanceState(Bundle outState) {
        outState.putString("mToolbarVisibility", mToolbarVisibility.name());
        outState.putBoolean("mToolbarIsVisible", mToolbarIsVisible);
        outState.putInt("mActiveEditor", mActiveEditor);
        if (mLinkSelection != null) {
            outState.putSerializable("mLinkSelection", mLinkSelection);
        }
    }

public void onSaveInstanceState ( Bundle outState ) { outState . putString ( "mToolbarVisibility" , mToolbarVisibility . name ( ) ) ; outState . putBoolean ( "mToolbarIsVisible" , mToolbarIsVisible ) ; outState . putInt ( "mActiveEditor" , mActiveEditor ) ; if ( mLinkSelection != null ) { outState . putSerializable ( "mLinkSelection" , mLinkSelection ) ; } }

Called to retrieve per-instance state before being killed so that the
state can be restored in the constructor.

@param outState Bundle in which to place your saved state.

Called to retrieve per - instance state before being killed so that the state can be restored in the constructor .


public void onDestroy(boolean isSaved) {
        EventBus.getDefault().unregister(this);

        for (RTEditText editor : mEditors.values()) {
            editor.unregister();
            editor.onDestroy(isSaved);
        }
        mEditors.clear();

        for (RTToolbar toolbar : mToolbars.values()) {
            toolbar.removeToolbarListener();
        }
        mToolbars.clear();

        mRTApi = null;
    }

public void onDestroy ( boolean isSaved ) { EventBus . getDefault ( ) . unregister ( this ) ; for ( RTEditText editor : mEditors . values ( ) ) { editor . unregister ( ) ; editor . onDestroy ( isSaved ) ; } mEditors . clear ( ) ; for ( RTToolbar toolbar : mToolbars . values ( ) ) { toolbar . removeToolbarListener ( ) ; } mToolbars . clear ( ) ; mRTApi = null ; }

Perform any final cleanup before the component is destroyed.

@param isSaved True if the text is saved, False if it's dismissed. This is
needed to decide whether media (images etc.) are to be
deleted.

Perform any final cleanup before the component is destroyed .


@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
    public void onEventMainThread(MediaEvent event) {
        RTEditText editor = mEditors.get(mActiveEditor);
        RTMedia media = event.getMedia();
        if (editor != null && media instanceof RTImage) {
            insertImage(editor, (RTImage) media);
            EventBus.getDefault().removeStickyEvent(event);
            mActiveEditor = Integer.MAX_VALUE;
        }
    }

@ Subscribe ( sticky = true , threadMode = ThreadMode . MAIN ) public void onEventMainThread ( MediaEvent event ) { RTEditText editor = mEditors . get ( mActiveEditor ) ; RTMedia media = event . getMedia ( ) ; if ( editor != null && media instanceof RTImage ) { insertImage ( editor , ( RTImage ) media ) ; EventBus . getDefault ( ) . removeStickyEvent ( event ) ; mActiveEditor = Integer . MAX_VALUE ; } }

Media file was picked -> process the result.

Media file was picked - > process the result .


@Subscribe(threadMode = ThreadMode.MAIN)
    public void onEventMainThread(LinkEvent event) {
        final String fragmentTag = event.getFragmentTag();
        mRTApi.removeFragment(fragmentTag);

        if (!event.wasCancelled() && ID_01_LINK_FRAGMENT.equals(fragmentTag)) {

            RTEditText editor = getActiveEditor();
            if (editor != null) {

                Link link = event.getLink();
                String url = null;
                if (link != null && link.isValid()) {

                    // the mLinkSelection.end() <= editor.length() check is necessary since
                    // the editor text can change when the link fragment is open
                    Selection selection = mLinkSelection != null && mLinkSelection.end() <= editor.length() ? mLinkSelection : new Selection(editor);

                    String linkText = link.getLinkText();

                    // if no text is selected this inserts the entered link text
                    // if text is selected we replace it by the link text
                    Editable str = editor.getText();
                    str.replace(selection.start(), selection.end(), linkText);
                    editor.setSelection(selection.start(), selection.start() + linkText.length());

                    url = link.getUrl();

                }
                editor.applyEffect(Effects.LINK, url);    // if url == null -> remove the link

            }

        }
    }

LinkFragment has closed -> process the result.

LinkFragment has closed - > process the result .


private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix) {
        float viewWidth = getWidth();
        float viewHeight = getHeight();

        float w = bitmap.getWidth();
        float h = bitmap.getHeight();
        matrix.reset();

        // We limit up-scaling to 2x otherwise the result may look bad if it's
        // a small icon.
        float widthScale = Math.min(viewWidth / w, 2.0f);
        float heightScale = Math.min(viewHeight / h, 2.0f);
        float scale = Math.min(widthScale, heightScale);

        matrix.postConcat(bitmap.getRotateMatrix());
        matrix.postScale(scale, scale);

        matrix.postTranslate((viewWidth - w * scale) / 2F, (viewHeight - h * scale) / 2F);
    }

Setup the base matrix so that the image is centered and scaled properly.

Setup the base matrix so that the image is centered and scaled properly .


protected float maxZoom() {
        if (mBitmapDisplayed.getBitmap() == null) {
            return 1F;
        }

        float fw = (float) mBitmapDisplayed.getWidth() / (float) mThisWidth;
        float fh = (float) mBitmapDisplayed.getHeight() / (float) mThisHeight;
        float max = Math.max(fw, fh) * 4;
        return max;
    }

protected float maxZoom ( ) { if ( mBitmapDisplayed . getBitmap ( ) == null ) { return 1F ; } float fw = ( float ) mBitmapDisplayed . getWidth ( ) / ( float ) mThisWidth ; float fh = ( float ) mBitmapDisplayed . getHeight ( ) / ( float ) mThisHeight ; float max = Math . max ( fw , fh ) * 4 ; return max ; }

rather than the current 1024x768, this should be changed down to 200%.

rather than the current 1024x768 this should be changed down to 200% .


final public boolean existsInSelection(RTEditText editor) {
        Selection selection = getSelection(editor);
        List<RTSpan<V>> spans = getSpans(editor.getText(), selection, SpanCollectMode.SPAN_FLAGS);

        return ! spans.isEmpty();
    }

final public boolean existsInSelection ( RTEditText editor ) { Selection selection = getSelection ( editor ) ; List < RTSpan < V > > spans = getSpans ( editor . getText ( ) , selection , SpanCollectMode . SPAN_FLAGS ) ; return ! spans . isEmpty ( ) ; }

Check whether the effect exists in the currently selected text of the active RTEditText.

@param editor The RTEditText we want the check.

@return True if the effect exists in the current selection, False otherwise.

Check whether the effect exists in the currently selected text of the active RTEditText .


protected void findSpans2Remove(Spannable str, Paragraph paragraph,
                                    ParagraphSpanProcessor<V> spanProcessor) {
        List<RTSpan<V>> spans = getSpans(str, paragraph, SpanCollectMode.EXACT);
        spanProcessor.removeSpans(spans, paragraph);
    }

protected void findSpans2Remove ( Spannable str , Paragraph paragraph , ParagraphSpanProcessor < V > spanProcessor ) { List < RTSpan < V >> spans = getSpans ( str , paragraph , SpanCollectMode . EXACT ) ; spanProcessor . removeSpans ( spans , paragraph ) ; }

Find spans within that paragraph and add them to the ParagraphSpanProcessor to be removed
once the ParagraphSpanProcessor processes its spans.

Find spans within that paragraph and add them to the ParagraphSpanProcessor to be removed once the ParagraphSpanProcessor processes its spans .


public static String encodeUrl(String url) {
        Uri uri = Uri.parse(url);

        try {
            Map<String, List<String>> splitQuery = splitQuery(uri);
            StringBuilder encodedQuery = new StringBuilder();
            for (String key : splitQuery.keySet()) {
                for (String value : splitQuery.get(key)) {
                    if (encodedQuery.length() > 0) {
                        encodedQuery.append("&");
                    }
                    encodedQuery.append(key + "=" + URLEncoder.encode(value, "UTF-8"));
                }
            }
            String queryString = encodedQuery != null && encodedQuery.length() > 0 ? "?" + encodedQuery : "";

            URI baseUri = new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), null, uri.getFragment());
            return baseUri + queryString;
        }
        catch (UnsupportedEncodingException ignore) {}
        catch (URISyntaxException ignore) {}

        return uri.toString();
    }

public static String encodeUrl ( String url ) { Uri uri = Uri . parse ( url ) ; try { Map < String , List < String > > splitQuery = splitQuery ( uri ) ; StringBuilder encodedQuery = new StringBuilder ( ) ; for ( String key : splitQuery . keySet ( ) ) { for ( String value : splitQuery . get ( key ) ) { if ( encodedQuery . length ( ) > 0 ) { encodedQuery . append ( "&" ) ; } encodedQuery . append ( key + "=" + URLEncoder . encode ( value , "UTF-8" ) ) ; } } String queryString = encodedQuery != null && encodedQuery . length ( ) > 0 ? "?" + encodedQuery : "" ; URI baseUri = new URI ( uri . getScheme ( ) , uri . getAuthority ( ) , uri . getPath ( ) , null , uri . getFragment ( ) ) ; return baseUri + queryString ; } catch ( UnsupportedEncodingException ignore ) { } catch ( URISyntaxException ignore ) { } return uri . toString ( ) ; }

This method encodes the query part of an url
@param url an url (e.g. http://www.1gravity.com?query=üö)
@return The url with an encoded query, e.g. http://www.1gravity.com?query%3D%C3%BC%C3%B6


public static String decodeQuery(String url) {
        try {
            return URLDecoder.decode(url, "UTF-8");
        }
        catch (UnsupportedEncodingException ignore) {}

        return url;
    }

public static String decodeQuery ( String url ) { try { return URLDecoder . decode ( url , "UTF-8" ) ; } catch ( UnsupportedEncodingException ignore ) { } return url ; }

This method decodes an url with encoded query string
@param url an url with encoded query string (e.g. http://www.1gravity.com?query%C3%BC%C3%B6)
@return The decoded url, e.g. http://www.1gravity.com?query=üö


public static boolean pickDirectory(Activity activity, File startPath, int requestCode) {
        PackageManager packageMgr = activity.getPackageManager();
        for (String[] intent : PICK_DIRECTORY_INTENTS) {
            String intentAction = intent[0];
            String uriPrefix = intent[1];
            Intent startIntent = new Intent(intentAction)
                    .putExtra("org.openintents.extra.TITLE", activity.getString(R.string.save_as))
                    .setData(Uri.parse(uriPrefix + startPath.getPath()));

            try {
                if (startIntent.resolveActivity(packageMgr) != null) {
                    activity.startActivityForResult(startIntent, requestCode);
                    return true;
                }
            } catch (ActivityNotFoundException e) {
                showNoFilePickerError(activity, e);
            }
        }

        return false;
    }

public static boolean pickDirectory ( Activity activity , File startPath , int requestCode ) { PackageManager packageMgr = activity . getPackageManager ( ) ; for ( String [ ] intent : PICK_DIRECTORY_INTENTS ) { String intentAction = intent [ 0 ] ; String uriPrefix = intent [ 1 ] ; Intent startIntent = new Intent ( intentAction ) . putExtra ( "org.openintents.extra.TITLE" , activity . getString ( R . string . save_as ) ) . setData ( Uri . parse ( uriPrefix + startPath . getPath ( ) ) ) ; try { if ( startIntent . resolveActivity ( packageMgr ) != null ) { activity . startActivityForResult ( startIntent , requestCode ) ; return true ; } } catch ( ActivityNotFoundException e ) { showNoFilePickerError ( activity , e ) ; } } return false ; }

Tries to open a known file browsers to pick a directory.

@return True if a filebrowser has been found (the result will be in the onActivityResult), False otherwise

Tries to open a known file browsers to pick a directory .


private Bitmap rotateImage(Bitmap src, float degree) {
        // create new matrix
        Matrix matrix = new Matrix();
        // setup rotation degree
        matrix.postRotate(degree);
        Bitmap bmp = Bitmap.createBitmap(src, 0, 0, src.getWidth(),
                src.getHeight(), matrix, true);
        return bmp;
    }

private
Bitmap
rotateImage
(
Bitmap
src
,
float
degree
)
{
// create new matrix
Matrix
matrix
=
new
Matrix
(
)
;
// setup rotation degree
matrix
.
postRotate
(
degree
)
;
Bitmap
bmp
=
Bitmap
.
createBitmap
(
src
,
0
,
0
,
src
.
getWidth
(
)
,
src
.
getHeight
(
)
,
matrix
,
true
)
;
return
bmp
;
}


public void setOutput(Writer writer) {
        if (writer == null) {
            output = new OutputStreamWriter(System.out);
        } else {
            output = writer;
        }
    }

public void setOutput ( Writer writer ) { if ( writer == null ) { output = new OutputStreamWriter ( System . out ) ; } else { output = writer ; } }

Set a new output destination for the document.

@param writer The output destination, or null to use standard output.
@return The current output writer.
@see #flush

Set a new output destination for the document .


private void write(char c) throws SAXException {
        try {
            output.write(c);
        } catch (IOException e) {
            throw new SAXException(e);
        }
    }

private void write ( char c ) throws SAXException { try { output . write ( c ) ; } catch ( IOException e ) { throw new SAXException ( e ) ; } }

Write a raw character.

@param c The character to write.
@throws org.xml.sax.SAXException If there is an error writing the character, this method
will throw an IOException wrapped in a SAXException.


private void write(String s) throws SAXException {
        try {
            output.write(s);
        } catch (IOException e) {
            throw new SAXException(e);
        }
    }

private void write ( String s ) throws SAXException { try { output . write ( s ) ; } catch ( IOException e ) { throw new SAXException ( e ) ; } }

Write a raw string.

@param s
@throws org.xml.sax.SAXException If there is an error writing the string, this method will
throw an IOException wrapped in a SAXException


private boolean booleanAttribute(String localName, String qName,
                                     String value) {
        String name = localName;
        if (name == null) {
            int i = qName.indexOf(':');
            if (i != -1)
                name = qName.substring(i + 1, qName.length());
        }
        if (!name.equals(value))
            return false;
        for (int j = 0; j < booleans.length; j++) {
            if (name.equals(booleans[j]))
                return true;
        }
        return false;
    }

private boolean booleanAttribute ( String localName , String qName , String value ) { String name = localName ; if ( name == null ) { int i = qName . indexOf ( ' ' ) ; if ( i != - 1 ) name = qName . substring ( i + 1 , qName . length ( ) ) ; } if ( ! name . equals ( value ) ) return false ; for ( int j = 0 ; j < booleans . length ; j ++ ) { if ( name . equals ( booleans [ j ] ) ) return true ; } return false ; }

Return true if the attribute is an HTML boolean from the above list.

Return true if the attribute is an HTML boolean from the above list .


private void writeEscUTF16(String s, int start, int length, boolean isAttVal) throws SAXException {
        String subString = s.substring(start, start + length);
        write(StringEscapeUtils.escapeHtml4(subString));
    }

private void writeEscUTF16 ( String s , int start , int length , boolean isAttVal ) throws SAXException { String subString = s . substring ( start , start + length ) ; write ( StringEscapeUtils . escapeHtml4 ( subString ) ) ; }

Write an array of data characters with escaping.

@param ch       The array of characters.
@param start    The starting position.
@param length   The number of characters to use.
@param isAttVal true if this is an attribute value literal.
@throws org.xml.SAXException If there is an error writing the characters, this method
will throw an IOException wrapped in a SAXException.

Write an array of data characters with escaping .


@SuppressWarnings("unchecked")
    private void writeNSDecls() throws SAXException {
        Enumeration<String> prefixes = (Enumeration<String>) nsSupport.getDeclaredPrefixes();
        while (prefixes.hasMoreElements()) {
            String prefix = (String) prefixes.nextElement();
            String uri = nsSupport.getURI(prefix);
            if (uri == null) {
                uri = "";
            }
            write(' ');
            if ("".equals(prefix)) {
                write("xmlns=\"");
            } else {
                write("xmlns:");
                write(prefix);
                write("=\"");
            }
            writeEscUTF16(uri, 0, uri.length(), true);
            write('\"');
        }
    }

@ SuppressWarnings ( "unchecked" ) private void writeNSDecls ( ) throws SAXException { Enumeration < String > prefixes = ( Enumeration < String > ) nsSupport . getDeclaredPrefixes ( ) ; while ( prefixes . hasMoreElements ( ) ) { String prefix = ( String ) prefixes . nextElement ( ) ; String uri = nsSupport . getURI ( prefix ) ; if ( uri == null ) { uri = "" ; } write ( ' ' ) ; if ( "" . equals ( prefix ) ) { write ( "xmlns=\"" ) ; } else { write ( "xmlns:" ) ; write ( prefix ) ; write ( "=\"" ) ; } writeEscUTF16 ( uri , 0 , uri . length ( ) , true ) ; write ( ' ' ) ; } }

Write out the list of Namespace declarations.

@throws org.xml.sax.SAXException This method will throw an IOException wrapped in a
SAXException if there is an error writing the Namespace
declarations.

Write out the list of Namespace declarations .


private void writeName(String uri, String localName, String qName,
                           boolean isElement) throws SAXException {
        String prefix = doPrefix(uri, qName, isElement);
        if (prefix != null && !"".equals(prefix)) {
            write(prefix);
            write(':');
        }
        if (localName != null && !"".equals(localName)) {
            write(localName);
        } else {
            int i = qName.indexOf(':');
            write(qName.substring(i + 1, qName.length()));
        }
    }

private void writeName ( String uri , String localName , String qName , boolean isElement ) throws SAXException { String prefix = doPrefix ( uri , qName , isElement ) ; if ( prefix != null && ! "" . equals ( prefix ) ) { write ( prefix ) ; write ( ' ' ) ; } if ( localName != null && ! "" . equals ( localName ) ) { write ( localName ) ; } else { int i = qName . indexOf ( ' ' ) ; write ( qName . substring ( i + 1 , qName . length ( ) ) ) ; } }

Write an element or attribute name.

@param uri       The Namespace URI.
@param localName The local name.
@param qName     The prefixed name, if available, or the empty string.
@param isElement true if this is an element name, false if it is an attribute
name.
@throws org.xml.sax.SAXException This method will throw an IOException wrapped in a
SAXException if there is an error writing the name.


private static void createStreamIfNotExists(AmazonKinesis kinesis, String streamName, int shardCount) {
        performAmazonActionWithRetry("createStream", () -> {
            DescribeStreamRequest describeStreamRequest = new DescribeStreamRequest().withStreamName(streamName).withLimit(1);
            try {
                kinesis.describeStream(describeStreamRequest);
            } catch (ResourceNotFoundException e) {
                kinesis.createStream(streamName, shardCount);
            }
            return null;
        }, DEFAULT_RETRY_COUNT, DEFAULT_RETRY_DURATION_IN_MILLIS);
    }

private static void createStreamIfNotExists ( AmazonKinesis kinesis , String streamName , int shardCount ) { performAmazonActionWithRetry ( "createStream" , ( ) -> { DescribeStreamRequest describeStreamRequest = new DescribeStreamRequest ( ) . withStreamName ( streamName ) . withLimit ( 1 ) ; try { kinesis . describeStream ( describeStreamRequest ) ; } catch ( ResourceNotFoundException e ) { kinesis . createStream ( streamName , shardCount ) ; } return null ; } , DEFAULT_RETRY_COUNT , DEFAULT_RETRY_DURATION_IN_MILLIS ) ; }

Create a stream if it does not already exist.

@param kinesis AmazonKinesis client
@param streamName the name of the stream

Create a stream if it does not already exist .


private static void waitStreamActivation(AmazonKinesis consumer, String streamName, long streamCreationTimeoutMillis) {
        DescribeStreamRequest describeStreamRequest = new DescribeStreamRequest().withStreamName(streamName).withLimit(1);
        DescribeStreamResult describeStreamResult = null;
        String streamStatus = null;
        long endTime = System.currentTimeMillis() + streamCreationTimeoutMillis;
        do {
            try {
                describeStreamResult = consumer.describeStream(describeStreamRequest);
                streamStatus = describeStreamResult.getStreamDescription().getStreamStatus();
                if (ACTIVE_STREAM_STATUS.equals(streamStatus)) {
                    break;
                }
                Thread.sleep(100);
            } catch (ResourceNotFoundException | LimitExceededException ignored) {
                // ignored
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new AwsKinesisException("Thread interrupted while waiting for stream activation", args -> args.add("streamName", streamName), e);
            }
        } while (System.currentTimeMillis() < endTime);
        if (describeStreamResult == null || streamStatus == null || !streamStatus.equals(ACTIVE_STREAM_STATUS)) {
            throw new AwsKinesisException("Stream never went active",
                                          args -> args.add("streamName", streamName).add("streamCreationTimeoutMillis", streamCreationTimeoutMillis));
        }
    }

Waits that the stream has been created .


private static void completeStoreSnapshotWithMissingInstanceSnapshots(Path targetStoresPath) {
        String transactionIdRegexAlone = "\"transactionId\"\\s*:\\s*\\d+\\s*,";
        String transactionIdRegexReplace = "(.*\"transactionId\"\\s*:\\s*)\\d+(\\s*,.*)";
        Pattern transactionIdPattern = compile(transactionIdRegexAlone);
        Set<File> previousSnapshots = new HashSet<>();

        Arrays.stream(targetStoresPath.resolve(SNAPSHOT_DIRECTORY_NAME).toFile().listFiles()).
                sorted().
                forEach(snapshot -> {
                    Set<String> snapshotNames = Arrays.stream(snapshot.listFiles()).map(File::getName).collect(toSet());
                    previousSnapshots.stream().
                            filter(previousSnapshot -> !snapshotNames.contains(previousSnapshot.getName())).
                            forEach(previousSnapshot -> {
                                try {
                                    Path targetPath = snapshot.toPath().resolve(previousSnapshot.getName());
                                    Path sourcePath = previousSnapshot.toPath();
                                    long count = countTransactionId(transactionIdPattern, sourcePath);
                                    if (count != 1L) {
                                        throw new StoreException("transactionId not found once", args -> args.add("found count", count));
                                    }
                                    BigInteger newTransactionId = new BigInteger(snapshot.getName());
                                    replaceTransactionIdValue(transactionIdRegexReplace, sourcePath, targetPath, newTransactionId.toString());
                                } catch (IOException e) {
                                    throw new UncheckedIOException(e);
                                }
                            });
                    previousSnapshots.clear();
                    previousSnapshots.addAll(Arrays.stream(snapshot.listFiles()).collect(toSet()));
                });
    }

Complete snapshot N with lacking instance snapshots from snapshot N - 1

@param targetStoresPath path to store target iron


private boolean waitTheMinimalDurationToExecuteTheNextProvisioningRequest() {
        if (m_lastGetShardIteratorRequestTime != null) {
            long delay = m_durationBetweenRequests.get() - (System.currentTimeMillis() - m_lastGetShardIteratorRequestTime);
            if (delay > 0) {
                try {
                    Thread.sleep(delay);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return false;
                }
            }
        }
        m_lastGetShardIteratorRequestTime = System.currentTimeMillis();
        return true;
    }

Wait that the minimum duration between two GetShardIteratorRequests has elapsed.

@return true if the duration elapsed, false if the thread has been interrupted

Wait that the minimum duration between two GetShardIteratorRequests has elapsed .


@Nullable
    private List<Record> getRecords(GetRecordsRequest getRecordsRequest) {
        return tryAmazonAction("", () -> {
            GetRecordsResult getRecordsResult = m_kinesis.getRecords(getRecordsRequest);
            m_shardIterator = getRecordsResult.getNextShardIterator();
            List<Record> records = getRecordsResult.getRecords();
            LOG.trace("Get records", args -> args.add("streamName", m_streamName).add("record number", records.size())
                    .add("millisBehindLatest", getRecordsResult.getMillisBehindLatest()));
            return records;
        }, m_durationBetweenRequests).orElse(List.of());
    }

@
Nullable
private
List
<
Record
>
getRecords
(
GetRecordsRequest
getRecordsRequest
)
{
return
tryAmazonAction
(
""
,
(
)
->
{
GetRecordsResult
getRecordsResult
=
m_kinesis
.
getRecords
(
getRecordsRequest
)
;
m_shardIterator
=
getRecordsResult
.
getNextShardIterator
(
)
;
List
<
Record
>
records
=
getRecordsResult
.
getRecords
(
)
;
LOG
.
trace
(
"Get records"
,
args
->
args
.
add
(
"streamName"
,
m_streamName
)
.
add
(
"record number"
,
records
.
size
(
)
)
.
add
(
"millisBehindLatest"
,
getRecordsResult
.
getMillisBehindLatest
(
)
)
)
;
return
records
;
}
,
m_durationBetweenRequests
)
.
orElse
(
List
.
of
(
)
)
;
}

Retrieves records corresponding to the request.

@param getRecordsRequest the request
@return records corresponding to the request

Retrieves records corresponding to the request .


public static <T> T performAmazonActionWithRetry(String actionLabel, Supplier<T> action, int retryLimit, int durationInMillis) {
        int retryCount = 0;
        do {
            try {
                return action.get();
            } catch (LimitExceededException | ProvisionedThroughputExceededException | KMSThrottlingException e) {
                // We should just wait a little time before trying again
                int remainingRetries = retryLimit - retryCount;
                LOG.debug("Amazon exception caught",
                          args -> args.add("exception", e.getClass().getName()).add("action", actionLabel).add("remainingRetryCount", remainingRetries));
            }
            sleepUntilInterrupted(actionLabel, durationInMillis);
        } while (retryCount++ < retryLimit);
        throw new AwsException("Limit exceeded, all retries failed", args -> args.add("action", actionLabel).add("retryLimit", retryLimit));
    }

Handle retry for amazon quotas

@param <T> the action return type
@param actionLabel action label used for logging purpose only
@param action the action to retry
@param retryLimit retry number limit
@param durationInMillis duration between each retry
@return the action result
@throws AwsException after retry exhausted


public static <T> Optional<T> tryAmazonAction(String actionLabel, Supplier<T> action, AtomicLong durationBetweenRequests) {
        try {
            return of(action.get());
        } catch (LimitExceededException | ProvisionedThroughputExceededException | KMSThrottlingException e) {
            int durationRandomModifier = 1 + RANDOM.nextInt(64);// random duration to make readers out of sync, avoiding simultaneous readings
            long updatedDuration = durationBetweenRequests.updateAndGet(duration -> duration * 2 // twice the duration
                    + duration * 2 / durationRandomModifier);// add random duration to avoid simultaneous reads
            LOG.debug("Update of minimal duration between two get shard iterator requests",
                      args -> args.add("actionLabel", actionLabel).add("new minimalDurationBetweenTwoGetShardIteratorRequests", updatedDuration));
        }
        return empty();
    }

Try to perform an Amazon action and increase the duration between requests if some exception is exceeding resource usage exception is thrown.

@param <T> the action return type
@param actionLabel action label used for logging purpose only
@param action the action to retry
@param durationBetweenRequests duration between each retry
@return the action result

Try to perform an Amazon action and increase the duration between requests if some exception is exceeding resource usage exception is thrown .


static String checkBucketIsAccessible(AmazonS3 amazonS3, String bucketName) {
        HeadBucketRequest headBucketRequest = new HeadBucketRequest(bucketName);
        try {
            amazonS3.headBucket(headBucketRequest);
        } catch (AmazonServiceException e) {
            throw new AwsS3Exception("Bucket is not accessible", args -> args.add("bucketName", bucketName), e);
        }
        return bucketName;
    }

static
String
checkBucketIsAccessible
(
AmazonS3
amazonS3
,
String
bucketName
)
{
HeadBucketRequest
headBucketRequest
=
new
HeadBucketRequest
(
bucketName
)
;
try
{
amazonS3
.
headBucket
(
headBucketRequest
)
;
}
catch
(
AmazonServiceException
e
)
{
throw
new
AwsS3Exception
(
"Bucket is not accessible"
,
args
->
args
.
add
(
"bucketName"
,
bucketName
)
,
e
)
;
}
return
bucketName
;
}

Throws an exception if the bucket does not exist or is not readable.

@param amazonS3 Amazon S3 client
@param bucketName bucket name

Throws an exception if the bucket does not exist or is not readable .