private void notifyRequestFinish(ResultType type, T cacheData, boolean outOfDate) {
        if (DEBUG) {
            CLog.d(LOG_TAG, "%s, notifyRequestFinish: %s, %s", getCacheKey(), type, outOfDate);
        }
        if (mHasNotified) {
            return;
        }
        mHasNotified = true;
        mHandler.onCacheAbleRequestFinish(cacheData, type, outOfDate);
    }

private
void
notifyRequestFinish
(
ResultType
type
,
T
cacheData
,
boolean
outOfDate
)
{
if
(
DEBUG
)
{
CLog
.
d
(
LOG_TAG
,
"%s, notifyRequestFinish: %s, %s"
,
getCacheKey
(
)
,
type
,
outOfDate
)
;
}
if
(
mHasNotified
)
{
return
;
}
mHasNotified
=
true
;
mHandler
.
onCacheAbleRequestFinish
(
cacheData
,
type
,
outOfDate
)
;
}

will only notify once

@param type
@param outOfDate


public void getResponse(StringBuilder sb) throws IOException {

        // checks server's status code first
        int status = mHttpURLConnection.getResponseCode();
        if (status == HttpURLConnection.HTTP_OK) {
            InputStream ips = new BufferedInputStream(mHttpURLConnection.getInputStream());
            BufferedReader reader = new BufferedReader(new InputStreamReader(ips, "UTF-8"));

            char[] buffer = new char[1024];
            int bufferLength;
            while ((bufferLength = reader.read(buffer, 0, buffer.length)) > 0) {
                sb.append(buffer, 0, bufferLength);
            }
            reader.close();
            ips.close();
            mHttpURLConnection.disconnect();
        } else {
            throw new IOException("Server returned non-OK status: " + status);
        }
    }

public
void
getResponse
(
StringBuilder
sb
)
throws
IOException
{
// checks server's status code first
int
status
=
mHttpURLConnection
.
getResponseCode
(
)
;
if
(
status
==
HttpURLConnection
.
HTTP_OK
)
{
InputStream
ips
=
new
BufferedInputStream
(
mHttpURLConnection
.
getInputStream
(
)
)
;
BufferedReader
reader
=
new
BufferedReader
(
new
InputStreamReader
(
ips
,
"UTF-8"
)
)
;
char
[
]
buffer
=
new
char
[
1024
]
;
int
bufferLength
;
while
(
(
bufferLength
=
reader
.
read
(
buffer
,
0
,
buffer
.
length
)
)
>
0
)
{
sb
.
append
(
buffer
,
0
,
bufferLength
)
;
}
reader
.
close
(
)
;
ips
.
close
(
)
;
mHttpURLConnection
.
disconnect
(
)
;
}
else
{
throw
new
IOException
(
"Server returned non-OK status: "
+
status
)
;
}
}

Completes the request and receives response from the server.

@return a list of Strings as response in case the server returned
status OK, otherwise an exception is thrown.
@throws IOException

Completes the request and receives response from the server .


public void setLoadingBitmap(Bitmap loadingBitmap) {
        if (Version.hasHoneycomb()) {
            mLoadingDrawable = new BitmapDrawable(mContext.getResources(), loadingBitmap);
        }
    }

public void setLoadingBitmap ( Bitmap loadingBitmap ) { if ( Version . hasHoneycomb ( ) ) { mLoadingDrawable = new BitmapDrawable ( mContext . getResources ( ) , loadingBitmap ) ; } }


public void setCustomizedRightView(View view) {
        RelativeLayout.LayoutParams lp = makeLayoutParams(view);
        lp.addRule(CENTER_VERTICAL);
        lp.addRule(ALIGN_PARENT_RIGHT);
        getRightViewContainer().addView(view, lp);
    }

public void setCustomizedRightView ( View view ) { RelativeLayout . LayoutParams lp = makeLayoutParams ( view ) ; lp . addRule ( CENTER_VERTICAL ) ; lp . addRule ( ALIGN_PARENT_RIGHT ) ; getRightViewContainer ( ) . addView ( view , lp ) ; }

set customized view to right side

@param view the view to be added to right side


public boolean downloadToStream(ImageTask imageTask, String urlString, OutputStream outputStream, ProgressUpdateHandler progressUpdateHandler) {
        disableConnectionReuseIfNecessary();
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;

        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setReadTimeout(0);
            int len = urlConnection.getContentLength();
            int total = 0;
            in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
            int b;
            while ((b = in.read()) != -1) {
                total++;
                out.write(b);
                if (progressUpdateHandler != null) {
                    progressUpdateHandler.onProgressUpdate(total, len);
                }
            }
            return true;
        } catch (final IOException e) {
            CLog.e(LOG_TAG, "Error in downloadBitmap - " + e);
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (final IOException e) {
            }
        }
        return false;
    }

public
boolean
downloadToStream
(
ImageTask
imageTask
,
String
urlString
,
OutputStream
outputStream
,
ProgressUpdateHandler
progressUpdateHandler
)
{
disableConnectionReuseIfNecessary
(
)
;
HttpURLConnection
urlConnection
=
;
BufferedOutputStream
out
=
;
BufferedInputStream
in
=
;
try
{
final
URL
url
=
new
URL
(
urlString
)
;
urlConnection
=
(
HttpURLConnection
)
url
.
openConnection
(
)
;
urlConnection
.
setReadTimeout
(
0
)
;
int
len
=
urlConnection
.
getContentLength
(
)
;
int
total
=
0
;
in
=
new
BufferedInputStream
(
urlConnection
.
getInputStream
(
)
,
IO_BUFFER_SIZE
)
;
out
=
new
BufferedOutputStream
(
outputStream
,
IO_BUFFER_SIZE
)
;
int
b
;
while
(
(
b
=
in
.
read
(
)
)
!=
1
)
{
total
++
;
out
.
write
(
b
)
;
if
(
progressUpdateHandler
!=
)
{
progressUpdateHandler
.
onProgressUpdate
(
total
,
len
)
;
}
}
return
true
;
}
catch
(
final
IOException
e
)
{
CLog
.
e
(
LOG_TAG
,
"Error in downloadBitmap - "
+
e
)
;
}
finally
{
if
(
urlConnection
!=
)
{
urlConnection
.
disconnect
(
)
;
}
try
{
if
(
out
!=
)
{
out
.
close
(
)
;
}
if
(
in
!=
)
{
in
.
close
(
)
;
}
}
catch
(
final
IOException
e
)
{
}
}
return
false
;
}

Download a bitmap from a URL and write the content to an output stream.

Download a bitmap from a URL and write the content to an output stream .


public void addImageView(CubeImageView imageView) {
        if (null == imageView) {
            return;
        }
        if (null == mFirstImageViewHolder) {
            mFirstImageViewHolder = new ImageViewHolder(imageView);
            return;
        }

        ImageViewHolder holder = mFirstImageViewHolder;
        for (; ; holder = holder.mNext) {
            if (holder.contains(imageView)) {
                return;
            }
            if (holder.mNext == null) {
                break;
            }
        }

        ImageViewHolder newHolder = new ImageViewHolder(imageView);
        newHolder.mPrev = holder;
        holder.mNext = newHolder;
    }

public void addImageView ( CubeImageView imageView ) { if ( null == imageView ) { return ; } if ( null == mFirstImageViewHolder ) { mFirstImageViewHolder = new ImageViewHolder ( imageView ) ; return ; } ImageViewHolder holder = mFirstImageViewHolder ; for ( ; ; holder = holder . mNext ) { if ( holder . contains ( imageView ) ) { return ; } if ( holder . mNext == null ) { break ; } } ImageViewHolder newHolder = new ImageViewHolder ( imageView ) ; newHolder . mPrev = holder ; holder . mNext = newHolder ; }

Bind ImageView with ImageTask

@param imageView


public void removeImageView(CubeImageView imageView) {

        if (null == imageView || null == mFirstImageViewHolder) {
            return;
        }

        ImageViewHolder holder = mFirstImageViewHolder;

        do {
            if (holder.contains(imageView)) {

                // Make sure entry is right.
                if (holder == mFirstImageViewHolder) {
                    mFirstImageViewHolder = holder.mNext;
                }
                if (null != holder.mNext) {
                    holder.mNext.mPrev = holder.mPrev;
                }
                if (null != holder.mPrev) {
                    holder.mPrev.mNext = holder.mNext;
                }
            }

        } while ((holder = holder.mNext) != null);
    }

public
void
removeImageView
(
CubeImageView
imageView
)
{
if
(
==
imageView
||
==
mFirstImageViewHolder
)
{
return
;
}
ImageViewHolder
holder
=
mFirstImageViewHolder
;
do
{
if
(
holder
.
contains
(
imageView
)
)
{
// Make sure entry is right.
if
(
holder
==
mFirstImageViewHolder
)
{
mFirstImageViewHolder
=
holder
.
mNext
;
}
if
(
!=
holder
.
mNext
)
{
holder
.
mNext
.
mPrev
=
holder
.
mPrev
;
}
if
(
!=
holder
.
mPrev
)
{
holder
.
mPrev
.
mNext
=
holder
.
mNext
;
}
}
}
while
(
(
holder
=
holder
.
mNext
)
!=
)
;
}

Remove the ImageView from ImageTask

@param imageView


public void onLoading(ImageLoadHandler handler) {
        mFlag = mFlag | STATUS_LOADING;

        if (null == handler) {
            return;
        }

        if (mFirstImageViewHolder == null) {
            handler.onLoading(this, null);
        } else {
            ImageViewHolder holder = mFirstImageViewHolder;
            do {
                final CubeImageView imageView = holder.getImageView();
                if (null != imageView) {
                    handler.onLoading(this, imageView);
                }
            } while ((holder = holder.mNext) != null);
        }
    }

public void onLoading ( ImageLoadHandler handler ) { mFlag = mFlag | STATUS_LOADING ; if ( null == handler ) { return ; } if ( mFirstImageViewHolder == null ) { handler . onLoading ( this , null ) ; } else { ImageViewHolder holder = mFirstImageViewHolder ; do { final CubeImageView imageView = holder . getImageView ( ) ; if ( null != imageView ) { handler . onLoading ( this , imageView ) ; } } while ( ( holder = holder . mNext ) != null ) ; } }

When loading from network

@param handler


public void onLoadTaskFinish(BitmapDrawable drawable, ImageLoadHandler handler) {

        mFlag &= ~STATUS_LOADING;

        if (null == handler) {
            return;
        }
        int errorCode = mFlag & ERROR_CODE_MASK;
        if (errorCode > 0) {
            onLoadError(errorCode, handler);
            return;
        }

        if (null != mImageTaskStatistics) {
            mImageTaskStatistics.s5_beforeShow();
        }
        if (mFirstImageViewHolder == null) {
            handler.onLoadFinish(this, null, drawable);
        } else {
            ImageViewHolder holder = mFirstImageViewHolder;
            do {
                final CubeImageView imageView = holder.getImageView();
                if (null != imageView) {
                    imageView.onLoadFinish();
                    handler.onLoadFinish(this, imageView, drawable);
                }
            } while ((holder = holder.mNext) != null);
        }
        if (null != mImageTaskStatistics) {
            mImageTaskStatistics.s6_afterShow(ImageProvider.getBitmapSize(drawable));
            ImagePerformanceStatistics.onImageLoaded(this, mImageTaskStatistics);
        }
    }

public void onLoadTaskFinish ( BitmapDrawable drawable , ImageLoadHandler handler ) { mFlag &= ~ STATUS_LOADING ; if ( null == handler ) { return ; } int errorCode = mFlag & ERROR_CODE_MASK ; if ( errorCode > 0 ) { onLoadError ( errorCode , handler ) ; return ; } if ( null != mImageTaskStatistics ) { mImageTaskStatistics . s5_beforeShow ( ) ; } if ( mFirstImageViewHolder == null ) { handler . onLoadFinish ( this , null , drawable ) ; } else { ImageViewHolder holder = mFirstImageViewHolder ; do { final CubeImageView imageView = holder . getImageView ( ) ; if ( null != imageView ) { imageView . onLoadFinish ( ) ; handler . onLoadFinish ( this , imageView , drawable ) ; } } while ( ( holder = holder . mNext ) != null ) ; } if ( null != mImageTaskStatistics ) { mImageTaskStatistics . s6_afterShow ( ImageProvider . getBitmapSize ( drawable ) ) ; ImagePerformanceStatistics . onImageLoaded ( this , mImageTaskStatistics ) ; } }

Will be called when begin load image data from dish or network

@param drawable


public static String joinSizeInfoToKey(String key, int w, int h) {
        if (w > 0 && h != Integer.MAX_VALUE && h > 0 && h != Integer.MAX_VALUE) {
            return new StringBuilder(key).append(SIZE_SP).append(w).append(SIZE_SP).append(h).toString();
        }
        return key;
    }

public static String joinSizeInfoToKey ( String key , int w , int h ) { if ( w > 0 && h != Integer . MAX_VALUE && h > 0 && h != Integer . MAX_VALUE ) { return new StringBuilder ( key ) . append ( SIZE_SP ) . append ( w ) . append ( SIZE_SP ) . append ( h ) . toString ( ) ; } return key ; }

Join the key and the size information.

@param key
@param w
@param h
@return "$key" + "_" + "$w" + "_" + "$h"


public static String joinSizeTagToKey(String key, String tag) {
        return new StringBuilder(key).append(SIZE_SP).append(tag).toString();
    }

public static String joinSizeTagToKey ( String key , String tag ) { return new StringBuilder ( key ) . append ( SIZE_SP ) . append ( tag ) . toString ( ) ; }

Join the tag with the key.

@param key
@param tag
@return "$key" + "_" + "$tag"


public void openDiskCacheAsync() {
        if (DEBUG) {
            CLog.d(LOG_TAG, "%s: initDiskCacheAsync", mDiskCache);
        }
        synchronized (mDiskCacheLock) {
            mDiskCacheStarting = true;
            new FileCacheTask(TASK_INIT_CACHE).executeNow();
        }
    }

public
void
openDiskCacheAsync
(
)
{
if
(
DEBUG
)
{
CLog
.
d
(
LOG_TAG
,
"%s: initDiskCacheAsync"
,
mDiskCache
)
;
}
synchronized
(
mDiskCacheLock
)
{
mDiskCacheStarting
=
true
;
new
FileCacheTask
(
TASK_INIT_CACHE
)
.
executeNow
(
)
;
}
}


public void flushDiskCacheAsyncWithDelay(int delay) {
        if (DEBUG) {
            CLog.d(LOG_TAG, "%s, flushDiskCacheAsyncWithDelay", delay);
        }
        if (mIsDelayFlushing) {
            return;
        }
        mIsDelayFlushing = true;
        new FileCacheTask(TASK_FLUSH_CACHE).executeAfter(delay);
    }

public
void
flushDiskCacheAsyncWithDelay
(
int
delay
)
{
if
(
DEBUG
)
{
CLog
.
d
(
LOG_TAG
,
"%s, flushDiskCacheAsyncWithDelay"
,
delay
)
;
}
if
(
mIsDelayFlushing
)
{
return
;
}
mIsDelayFlushing
=
true
;
new
FileCacheTask
(
TASK_FLUSH_CACHE
)
.
executeAfter
(
delay
)
;
}


public DiskCache getDiskCache() {
        if (!mDiskCacheReady) {
            if (DEBUG) {
                CLog.d(LOG_TAG, "%s, try to access disk cache, but it is not open, try to open it.", mDiskCache);
            }
            openDiskCacheAsync();
        }
        synchronized (mDiskCacheLock) {
            while (mDiskCacheStarting) {
                try {
                    if (DEBUG) {
                        CLog.d(LOG_TAG, "%s, try to access, but disk cache is not ready, wait", mDiskCache);
                    }
                    mDiskCacheLock.wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mDiskCache;
    }

If disk is not read, will prepare it first.

@return

If disk is not read will prepare it first .


public static void deleteDirectoryQuickly(File dir) throws IOException {

        if (!dir.exists()) {
            return;
        }
        final File to = new File(dir.getAbsolutePath() + System.currentTimeMillis());
        dir.renameTo(to);
        if (!dir.exists()) {
            // rebuild
            dir.mkdirs();
        }

        // try to run "rm -r" to remove the whole directory
        if (to.exists()) {
            String deleteCmd = "rm -r " + to;
            Runtime runtime = Runtime.getRuntime();
            try {
                Process process = runtime.exec(deleteCmd);
                process.waitFor();
            } catch (IOException e) {

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (!to.exists()) {
            return;
        }
        deleteDirectoryRecursively(to);
        if (to.exists()) {
            to.delete();
        }
    }


@TargetApi(Build.VERSION_CODES.FROYO)
    public static File getExternalCacheDir(Context context) {
        if (Version.hasFroyo()) {
            File path = context.getExternalCacheDir();

            // In some case, even the sd card is mounted, getExternalCacheDir will return null, may be it is nearly full.
            if (path != null) {
                return path;
            }
        }

        // Before Froyo or the path is null, we need to construct the external cache folder ourselves
        final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/";
        return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir);
    }

Get the external application cache directory.

@param context The context to use
@return The external cache folder : /storage/sdcard0/Android/data/com.srain.sdk/cache

Get the external application cache directory .


@SuppressWarnings("deprecation")
    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    public static long getUsableSpace(File path) {
        if (path == null) {
            return -1;
        }
        if (Version.hasGingerbread()) {
            return path.getUsableSpace();
        } else {
            if (!path.exists()) {
                return 0;
            } else {
                final StatFs stats = new StatFs(path.getPath());
                return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
            }
        }
    }

@ SuppressWarnings ( "deprecation" ) @ TargetApi ( Build . VERSION_CODES . GINGERBREAD ) public static long getUsableSpace ( File path ) { if ( path == null ) { return - 1 ; } if ( Version . hasGingerbread ( ) ) { return path . getUsableSpace ( ) ; } else { if ( ! path . exists ( ) ) { return 0 ; } else { final StatFs stats = new StatFs ( path . getPath ( ) ) ; return ( long ) stats . getBlockSize ( ) * ( long ) stats . getAvailableBlocks ( ) ; } } }

Check how much usable space is available at a given path.

@param path The path to check
@return The space available in bytes by user, not by root, -1 means path is null, 0 means path is not exist.

Check how much usable space is available at a given path .


private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) {
        if (drawable instanceof RecyclingBitmapDrawable) {
            // The drawable is a CountingBitmapDrawable, so notify it
            ((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed);
        } else if (drawable instanceof LayerDrawable) {
            // The drawable is a LayerDrawable, so recurse on each layer
            LayerDrawable layerDrawable = (LayerDrawable) drawable;
            for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) {
                notifyDrawable(layerDrawable.getDrawable(i), isDisplayed);
            }
        }
    }

Notifies the drawable that it's displayed state has changed.

@param drawable
@param isDisplayed

Notifies the drawable that it s displayed state has changed .


@Override
    public void onResume() {
        super.onResume();
        if (!mFirstResume) {
            onBack();
        }
        if (mFirstResume) {
            mFirstResume = false;
        }
        if (DEBUG) {
            showStatus("onResume");
        }
    }

@ Override public void onResume ( ) { super . onResume ( ) ; if ( ! mFirstResume ) { onBack ( ) ; } if ( mFirstResume ) { mFirstResume = false ; } if ( DEBUG ) { showStatus ( "onResume" ) ; } }

Only when Activity resume, not very precise.
When activity recover from partly invisible, onBecomesPartiallyInvisible will be triggered.

Only when Activity resume not very precise . When activity recover from partly invisible onBecomesPartiallyInvisible will be triggered .


public T lastItem() {
        if (mDataList == null || mDataList.size() == 0) {
            return null;
        }
        return mDataList.get(mDataList.size() - 1);
    }

public T lastItem ( ) { if ( mDataList == null || mDataList . size ( ) == 0 ) { return null ; } return mDataList . get ( mDataList . size ( ) - 1 ) ; }


@Override
	public void hide() {
		// Only use scale animation if FAB is visible
		if (getVisibility() == View.VISIBLE) {
			// Pivots indicate where the animation begins from
			float pivotX = getPivotX() + getTranslationX();
			float pivotY = getPivotY() + getTranslationY();

			// Animate FAB shrinking
			ScaleAnimation anim = new ScaleAnimation(1, 0, 1, 0, pivotX, pivotY);
			anim.setDuration(FAB_ANIM_DURATION);
			anim.setInterpolator(getInterpolator());
			startAnimation(anim);
		}
		setVisibility(View.INVISIBLE);
	}


public void alignSheetWithFab(View fab) {
		// NOTE: View.getLocationOnScreen() returns the view's coordinates on the screen
		// whereas other view methods like getRight() and getY() return coordinates relative
		// to the view's parent. Using those methods can lead to incorrect calculations when
		// the two views do not have the same parent.

		// Get FAB's coordinates
		int[] fabCoords = new int[2];
		fab.getLocationOnScreen(fabCoords);

		// Get sheet's coordinates
		int[] sheetCoords = new int[2];
		sheet.getLocationOnScreen(sheetCoords);

		// NOTE: Use the diffs between the positions of the FAB and sheet to align the sheet.
		// We have to use the diffs because the coordinates returned by getLocationOnScreen()
		// include the status bar and any other system UI elements, meaning the coordinates
		// aren't representative of the usable screen space.
		int leftDiff = sheetCoords[0] - fabCoords[0];
		int rightDiff = (sheetCoords[0] + sheet.getWidth()) - (fabCoords[0] + fab.getWidth());
		int topDiff = sheetCoords[1] - fabCoords[1];
		int bottomDiff = (sheetCoords[1] + sheet.getHeight()) - (fabCoords[1] + fab.getHeight());

		// NOTE: Preserve the sheet's margins to allow users to shift the sheet's position
		// to compensate for the fact that the design support library's FAB has extra
		// padding within the view
		ViewGroup.MarginLayoutParams sheetLayoutParams = (ViewGroup.MarginLayoutParams) sheet
				.getLayoutParams();

		// Set sheet's new coordinates (only if there is a change in coordinates because
		// setting the same coordinates can cause the view to "drift" - moving 0.5 to 1 pixels
		// around the screen)
		if (rightDiff != 0) {
			float sheetX = sheet.getX();
			// Align the right side of the sheet with the right side of the FAB if
			// doing so would not move the sheet off the screen
			if (rightDiff <= sheetX) {
				sheet.setX(sheetX - rightDiff - sheetLayoutParams.rightMargin);
				revealXDirection = RevealXDirection.LEFT;
			}
			// Otherwise, align the left side of the sheet with the left side of the FAB
			else if (leftDiff != 0 && leftDiff <= sheetX) {
				sheet.setX(sheetX - leftDiff + sheetLayoutParams.leftMargin);
				revealXDirection = RevealXDirection.RIGHT;
			}
		}

		if (bottomDiff != 0) {
			float sheetY = sheet.getY();
			// Align the bottom of the sheet with the bottom of the FAB
			if (bottomDiff <= sheetY) {
				sheet.setY(sheetY - bottomDiff - sheetLayoutParams.bottomMargin);
				revealYDirection = RevealYDirection.UP;
			}
			// Otherwise, align the top of the sheet with the top of the FAB
			else if (topDiff != 0 && topDiff <= sheetY) {
				sheet.setY(sheetY - topDiff + sheetLayoutParams.topMargin);
				revealYDirection = RevealYDirection.DOWN;
			}
		}
	}

Aligns the sheet's position with the FAB.

@param fab Floating action button

Aligns the sheet s position with the FAB .


public void morphFromFab(View fab, long showSheetDuration, long showSheetColorDuration,
			AnimationListener listener) {
		sheet.setVisibility(View.VISIBLE);
		revealSheetWithFab(fab, getFabRevealRadius(fab), getSheetRevealRadius(), showSheetDuration,
				fabColor, sheetColor, showSheetColorDuration, listener);
	}

public void morphFromFab ( View fab , long showSheetDuration , long showSheetColorDuration , AnimationListener listener ) { sheet . setVisibility ( View . VISIBLE ) ; revealSheetWithFab ( fab , getFabRevealRadius ( fab ) , getSheetRevealRadius ( ) , showSheetDuration , fabColor , sheetColor , showSheetColorDuration , listener ) ; }

Shows the sheet by morphing the FAB into the sheet.

@param fab Floating action button
@param showSheetDuration Duration of the sheet animation in milliseconds. Use 0 for no
animation.
@param showSheetColorDuration Duration of the color animation in milliseconds. Use 0 for no
animation.
@param listener Listener for animation events.

Shows the sheet by morphing the FAB into the sheet .


public void morphIntoFab(View fab, long hideSheetDuration, long hideSheetColorDuration,
			AnimationListener listener) {
		revealSheetWithFab(fab, getSheetRevealRadius(), getFabRevealRadius(fab), hideSheetDuration,
				sheetColor, fabColor, hideSheetColorDuration, listener);
	}

public void morphIntoFab ( View fab , long hideSheetDuration , long hideSheetColorDuration , AnimationListener listener ) { revealSheetWithFab ( fab , getSheetRevealRadius ( ) , getFabRevealRadius ( fab ) , hideSheetDuration , sheetColor , fabColor , hideSheetColorDuration , listener ) ; }

Hides the sheet by morphing the sheet into the FAB.

@param fab Floating action button
@param hideSheetDuration Duration of the sheet animation in milliseconds. Use 0 for no
animation.
@param hideSheetColorDuration Duration of the color animation in milliseconds. Use 0 for no
animation.
@param listener Listener for animation events.

Hides the sheet by morphing the sheet into the FAB .


public void hide(long duration, final AnimationListener listener) {
		overlay.animate().alpha(0).setDuration(duration).setInterpolator(interpolator)
				.setListener(new AnimatorListenerAdapter() {
					@Override
					public void onAnimationStart(Animator animation) {
						if (listener != null) {
							listener.onStart();
						}
					}

					@Override
					public void onAnimationEnd(Animator animation) {
						overlay.setVisibility(View.GONE);
						if (listener != null) {
							listener.onEnd();
						}
					}
				}).start();
	}

public void hide ( long duration , final AnimationListener listener ) { overlay . animate ( ) . alpha ( 0 ) . setDuration ( duration ) . setInterpolator ( interpolator ) . setListener ( new AnimatorListenerAdapter ( ) { @ Override public void onAnimationStart ( Animator animation ) { if ( listener != null ) { listener . onStart ( ) ; } } @ Override public void onAnimationEnd ( Animator animation ) { overlay . setVisibility ( View . GONE ) ; if ( listener != null ) { listener . onEnd ( ) ; } } } ) . start ( ) ; }

Hides the overlay.

@param duration Duration of the animation in milliseconds. Use 0 for no animation.
@param listener Listener for animation events.


public void showSheet() {
		if (isAnimating()) {
			return;
		}
		isShowing = true;

		// Show overlay
		overlayAnimation.show(SHOW_OVERLAY_ANIM_DURATION, null);

		// Morph FAB into sheet
		morphIntoSheet(new AnimationListener() {
			@Override
			public void onEnd() {
				// Call event listener
				if (eventListener != null) {
					eventListener.onSheetShown();
				}

				// Assuming that this is the last animation to finish
				isShowing = false;

				// Hide sheet after it is shown
				if (hideSheetAfterSheetIsShown) {
					hideSheet();
					hideSheetAfterSheetIsShown = false;
				}
			}
		});

		// Call event listener
		if (eventListener != null) {
			eventListener.onShowSheet();
		}
	}


private void setupActionBar() {
		setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
		getSupportActionBar().setDisplayHomeAsUpEnabled(true);
	}

private void setupActionBar ( ) { setSupportActionBar ( ( Toolbar ) findViewById ( R . id . toolbar ) ) ; getSupportActionBar ( ) . setDisplayHomeAsUpEnabled ( true ) ; }


private void setupDrawer() {
		drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
		drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.opendrawer,
				R.string.closedrawer);
		drawerLayout.setDrawerListener(drawerToggle);
	}

private void setupDrawer ( ) { drawerLayout = ( DrawerLayout ) findViewById ( R . id . drawer_layout ) ; drawerToggle = new ActionBarDrawerToggle ( this , drawerLayout , R . string . opendrawer , R . string . closedrawer ) ; drawerLayout . setDrawerListener ( drawerToggle ) ; }


private void setupTabs() {
		// Setup view pager
		ViewPager viewpager = (ViewPager) findViewById(R.id.viewpager);
		viewpager.setAdapter(new MainPagerAdapter(this, getSupportFragmentManager()));
		viewpager.setOffscreenPageLimit(MainPagerAdapter.NUM_ITEMS);
		updatePage(viewpager.getCurrentItem());

		// Setup tab layout
		TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
		tabLayout.setupWithViewPager(viewpager);
		viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
			@Override
			public void onPageScrolled(int i, float v, int i1) {
			}

			@Override
			public void onPageSelected(int i) {
				updatePage(i);
			}

			@Override
			public void onPageScrollStateChanged(int i) {
			}
		});
	}

private
void
setupTabs
(
)
{
// Setup view pager
ViewPager
viewpager
=
(
ViewPager
)
findViewById
(
R
.
id
.
viewpager
)
;
viewpager
.
setAdapter
(
new
MainPagerAdapter
(
this
,
getSupportFragmentManager
(
)
)
)
;
viewpager
.
setOffscreenPageLimit
(
MainPagerAdapter
.
NUM_ITEMS
)
;
updatePage
(
viewpager
.
getCurrentItem
(
)
)
;
// Setup tab layout
TabLayout
tabLayout
=
(
TabLayout
)
findViewById
(
R
.
id
.
tabs
)
;
tabLayout
.
setupWithViewPager
(
viewpager
)
;
viewpager
.
addOnPageChangeListener
(
new
ViewPager
.
OnPageChangeListener
(
)
{
@
Override
public
void
onPageScrolled
(
int
i
,
float
v
,
int
i1
)
{
}
@
Override
public
void
onPageSelected
(
int
i
)
{
updatePage
(
i
)
;
}
@
Override
public
void
onPageScrollStateChanged
(
int
i
)
{
}
}
)
;
}


private void setupFab() {

		Fab fab = (Fab) findViewById(R.id.fab);
		View sheetView = findViewById(R.id.fab_sheet);
		View overlay = findViewById(R.id.overlay);
		int sheetColor = getResources().getColor(R.color.background_card);
		int fabColor = getResources().getColor(R.color.theme_accent);

		// Create material sheet FAB
		materialSheetFab = new MaterialSheetFab<>(fab, sheetView, overlay, sheetColor, fabColor);

		// Set material sheet event listener
		materialSheetFab.setEventListener(new MaterialSheetFabEventListener() {
			@Override
			public void onShowSheet() {
				// Save current status bar color
				statusBarColor = getStatusBarColor();
				// Set darker status bar color to match the dim overlay
				setStatusBarColor(getResources().getColor(R.color.theme_primary_dark2));
			}

			@Override
			public void onHideSheet() {
				// Restore status bar color
				setStatusBarColor(statusBarColor);
			}
		});

		// Set material sheet item click listeners
		findViewById(R.id.fab_sheet_item_recording).setOnClickListener(this);
		findViewById(R.id.fab_sheet_item_reminder).setOnClickListener(this);
		findViewById(R.id.fab_sheet_item_photo).setOnClickListener(this);
		findViewById(R.id.fab_sheet_item_note).setOnClickListener(this);
	}


private void updateFab(int selectedPage) {
		switch (selectedPage) {
		case MainPagerAdapter.ALL_POS:
			materialSheetFab.showFab();
			break;
		case MainPagerAdapter.SHARED_POS:
			materialSheetFab.showFab(0,
					-getResources().getDimensionPixelSize(R.dimen.snackbar_height));
			break;
		case MainPagerAdapter.FAVORITES_POS:
		default:
			materialSheetFab.hideSheetThenFab();
			break;
		}
	}

private void updateFab ( int selectedPage ) { switch ( selectedPage ) { case MainPagerAdapter . ALL_POS : materialSheetFab . showFab ( ) ; break ; case MainPagerAdapter . SHARED_POS : materialSheetFab . showFab ( 0 , - getResources ( ) . getDimensionPixelSize ( R . dimen . snackbar_height ) ) ; break ; case MainPagerAdapter . FAVORITES_POS : default : materialSheetFab . hideSheetThenFab ( ) ; break ; } }

Updates the FAB based on the selected page

@param selectedPage selected page


private void updateSnackbar(int selectedPage) {
		View snackbar = findViewById(R.id.snackbar);
		switch (selectedPage) {
		case MainPagerAdapter.SHARED_POS:
			snackbar.setVisibility(View.VISIBLE);
			break;
		case MainPagerAdapter.ALL_POS:
		case MainPagerAdapter.FAVORITES_POS:
		default:
			snackbar.setVisibility(View.GONE);
			break;
		}
	}

private void updateSnackbar ( int selectedPage ) { View snackbar = findViewById ( R . id . snackbar ) ; switch ( selectedPage ) { case MainPagerAdapter . SHARED_POS : snackbar . setVisibility ( View . VISIBLE ) ; break ; case MainPagerAdapter . ALL_POS : case MainPagerAdapter . FAVORITES_POS : default : snackbar . setVisibility ( View . GONE ) ; break ; } }

Updates the snackbar based on the selected page

@param selectedPage selected page


public void morphIntoSheet(int endX, int endY, Side side, int arcDegrees, float scaleFactor,
			long duration, AnimationListener listener) {
		morph(endX, endY, side, arcDegrees, scaleFactor, duration, listener);
	}

public void morphIntoSheet ( int endX , int endY , Side side , int arcDegrees , float scaleFactor , long duration , AnimationListener listener ) { morph ( endX , endY , side , arcDegrees , scaleFactor , duration , listener ) ; }

Animates the FAB as if the FAB is morphing into a sheet.

@param endX The X coordinate that the FAB will be moved to.
@param endY The Y coordinate that the FAB will be moved to.
@param side The side of the arc animation.
@param arcDegrees Amount of arc in FAB movement animation.
@param scaleFactor Amount to scale FAB.
@param duration Duration of the animation in milliseconds. Use 0 for no animation.
@param listener Listener for animation events.

Animates the FAB as if the FAB is morphing into a sheet .


public void morphFromSheet(int endX, int endY, Side side, int arcDegrees, float scaleFactor,
			long duration, AnimationListener listener) {
		fab.setVisibility(View.VISIBLE);
		morph(endX, endY, side, arcDegrees, scaleFactor, duration, listener);
	}

public void morphFromSheet ( int endX , int endY , Side side , int arcDegrees , float scaleFactor , long duration , AnimationListener listener ) { fab . setVisibility ( View . VISIBLE ) ; morph ( endX , endY , side , arcDegrees , scaleFactor , duration , listener ) ; }

Animates the FAB as if a sheet is being morphed into a FAB.

@param endX The X coordinate that the FAB will be moved to.
@param endY The Y coordinate that the FAB will be moved to.
@param side The side of the arc animation.
@param arcDegrees Amount of arc in FAB movement animation.
@param scaleFactor Amount to scale FAB.
@param duration Duration of the animation in milliseconds. Use 0 for no animation.
@param listener Listener for animation events.

Animates the FAB as if a sheet is being morphed into a FAB .


private void hideSuggestionsIfNecessary(final @NonNull QueryToken currentQuery,
                                            final @NonNull TokenSource source) {
        String queryTS = currentQuery.getTokenString();
        String currentTS = source.getCurrentTokenString();
        if (!isWaitingForResults(currentQuery) && queryTS != null && queryTS.equals(currentTS)) {
            mSuggestionsVisibilityManager.displaySuggestions(false);
        }
    }

private void hideSuggestionsIfNecessary ( final @ NonNull QueryToken currentQuery , final @ NonNull TokenSource source ) { String queryTS = currentQuery . getTokenString ( ) ; String currentTS = source . getCurrentTokenString ( ) ; if ( ! isWaitingForResults ( currentQuery ) && queryTS != null && queryTS . equals ( currentTS ) ) { mSuggestionsVisibilityManager . displaySuggestions ( false ) ; } }

Hides the suggestions if there are no more incoming queries.

@param currentQuery the most recent {@link QueryToken}
@param source       the associated {@link TokenSource} to use for reference

Hides the suggestions if there are no more incoming queries .


public List<T> getSuggestions(QueryToken queryToken) {
        String prefix = queryToken.getKeywords().toLowerCase();
        List<T> suggestions = new ArrayList<>();
        if (mData != null) {
            for (T suggestion : mData) {
                String name = suggestion.getSuggestiblePrimaryText().toLowerCase();
                if (name.startsWith(prefix)) {
                    suggestions.add(suggestion);
                }
            }
        }
        return suggestions;
    }

public List < T > getSuggestions ( QueryToken queryToken ) { String prefix = queryToken . getKeywords ( ) . toLowerCase ( ) ; List < T > suggestions = new ArrayList <> ( ) ; if ( mData != null ) { for ( T suggestion : mData ) { String name = suggestion . getSuggestiblePrimaryText ( ) . toLowerCase ( ) ; if ( name . startsWith ( prefix ) ) { suggestions . add ( suggestion ) ; } } } return suggestions ; }


public boolean containsExplicitChar(final @NonNull CharSequence input) {
        if (!TextUtils.isEmpty(input)) {
            for (int i = 0; i < input.length(); i++) {
                char c = input.charAt(i);
                if (isExplicitChar(c)) {
                    return true;
                }
            }
        }
        return false;
    }

public boolean containsExplicitChar ( final @ NonNull CharSequence input ) { if ( ! TextUtils . isEmpty ( input ) ) { for ( int i = 0 ; i < input . length ( ) ; i ++ ) { char c = input . charAt ( i ) ; if ( isExplicitChar ( c ) ) { return true ; } } } return false ; }

Returns true if the input string contains an explicit character.

@param input a {@link CharSequence} to test

@return true if input contains an explicit character

Returns true if the input string contains an explicit character .


public boolean containsWordBreakingChar(final @NonNull CharSequence input) {
        if (!TextUtils.isEmpty(input)) {
            for (int i = 0; i < input.length(); i++) {
                char c = input.charAt(i);
                if (isWordBreakingChar(c)) {
                    return true;
                }
            }
        }
        return false;
    }

public boolean containsWordBreakingChar ( final @ NonNull CharSequence input ) { if ( ! TextUtils . isEmpty ( input ) ) { for ( int i = 0 ; i < input . length ( ) ; i ++ ) { char c = input . charAt ( i ) ; if ( isWordBreakingChar ( c ) ) { return true ; } } } return false ; }

Returns true if the input string contains a word-breaking character.

@param input a {@link CharSequence} to test

@return true if input contains a word-breaking character

Returns true if the input string contains a word - breaking character .


public boolean onlyLettersOrDigits(final @NonNull CharSequence input, final int numCharsToCheck, final int start) {

        // Starting position must be within the input string
        if (start < 0 || start > input.length()) {
            return false;
        }

        // Check the first "numCharsToCheck" characters to ensure they are a letter or digit
        for (int i = 0; i < numCharsToCheck; i++) {
            int positionToCheck = start + i;
            // Return false if we would throw an Out-of-Bounds exception
            if (positionToCheck >= input.length()) {
                return false;
            }
            // Return false early if current character is not a letter or digit
            char charToCheck = input.charAt(positionToCheck);
            if (!Character.isLetterOrDigit(charToCheck)) {
                return false;
            }
        }

        // First "numCharsToCheck" characters are either letters or digits, so return true
        return true;
    }

Given a string and starting index, return true if the first "numCharsToCheck" characters at
the starting index are either a letter or a digit.

@param input           a {@link CharSequence} to test
@param numCharsToCheck number of characters to examine at starting position
@param start           starting position within the input string

@return true if the first "numCharsToCheck" at the starting index are either letters or digits

Given a string and starting index return true if the first numCharsToCheck characters at the starting index are either a letter or a digit .


protected int getSearchStartIndex(final @NonNull Spanned text, int cursor) {
        if (cursor < 0 || cursor > text.length()) {
            cursor = 0;
        }

        // Get index of the end of the last span before the cursor (or 0 if does not exist)
        MentionSpan[] spans = text.getSpans(0, text.length(), MentionSpan.class);
        int closestToCursor = 0;
        for (MentionSpan span : spans) {
            int end = text.getSpanEnd(span);
            if (end > closestToCursor && end <= cursor) {
                closestToCursor = end;
            }
        }

        // Get the index of the start of the line
        String textString = text.toString().substring(0, cursor);
        int lineStartIndex = 0;
        if (textString.contains(mConfig.LINE_SEPARATOR)) {
            lineStartIndex = textString.lastIndexOf(mConfig.LINE_SEPARATOR) + 1;
        }

        // Return whichever is closer before to the cursor
        return Math.max(closestToCursor, lineStartIndex);
    }

Returns the index of the end of the last span before the cursor or
the start of the current line if there are no spans before the cursor.

@param text   the {@link Spanned} to examine
@param cursor position of the cursor in text

@return the furthest in front of the cursor to search for the current keywords

Returns the index of the end of the last span before the cursor or the start of the current line if there are no spans before the cursor .


protected int getSearchEndIndex(final @NonNull Spanned text, int cursor) {
        if (cursor < 0 || cursor > text.length()) {
            cursor = 0;
        }

        // Get index of the start of the first span after the cursor (or text.length() if does not exist)
        MentionSpan[] spans = text.getSpans(0, text.length(), MentionSpan.class);
        int closestAfterCursor = text.length();
        for (MentionSpan span : spans) {
            int start = text.getSpanStart(span);
            if (start < closestAfterCursor && start >= cursor) {
                closestAfterCursor = start;
            }
        }

        // Get the index of the end of the line
        String textString = text.toString().substring(cursor, text.length());
        int lineEndIndex = text.length();
        if (textString.contains(mConfig.LINE_SEPARATOR)) {
            lineEndIndex = cursor + textString.indexOf(mConfig.LINE_SEPARATOR);
        }

        // Return whichever is closest after the cursor
        return Math.min(closestAfterCursor, lineEndIndex);
    }

Returns the index of the beginning of the first span after the cursor or
length of the text if there are no spans after the cursor.

@param text   the {@link Spanned} to examine
@param cursor position of the cursor in text

@return the furthest behind the cursor to search for the current keywords

Returns the index of the beginning of the first span after the cursor or length of the text if there are no spans after the cursor .


public void displayTextCounter(boolean display) {
        if (display) {
            mTextCounterView.setVisibility(TextView.VISIBLE);
        } else {
            mTextCounterView.setVisibility(TextView.GONE);
        }
    }

public void displayTextCounter ( boolean display ) { if ( display ) { mTextCounterView . setVisibility ( TextView . VISIBLE ) ; } else { mTextCounterView . setVisibility ( TextView . GONE ) ; } }

Show or hide the text counter view.

@param display true to display the text counter view


private void disableSpellingSuggestions(boolean disable) {
        // toggling suggestions often resets the cursor location, but we don't want that to happen
        int start = mMentionsEditText.getSelectionStart();
        int end = mMentionsEditText.getSelectionEnd();
        // -1 means there is no selection or cursor.
        if (start == -1 || end == -1) {
            return;
        }
        if (disable) {
            // store the previous input type
            mOriginalInputType = mMentionsEditText.getInputType();
        }
        mMentionsEditText.setRawInputType(disable ? InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS : mOriginalInputType);
        mMentionsEditText.setSelection(start, end);
    }

Disables spelling suggestions from the user's keyboard.
This is necessary because some keyboards will replace the input text with
spelling suggestions automatically, which changes the suggestion results.
This results in a confusing user experience.

@param disable {@code true} if spelling suggestions should be disabled, otherwise {@code false}

Disables spelling suggestions from the user s keyboard . This is necessary because some keyboards will replace the input text with spelling suggestions automatically which changes the suggestion results . This results in a confusing user experience .


private void updateEditorTextCount() {
        if (mMentionsEditText != null && mTextCounterView != null) {
            int textCount = mMentionsEditText.getMentionsText().length();
            mTextCounterView.setText(String.valueOf(textCount));

            if (mTextCountLimit > 0 && textCount > mTextCountLimit) {
                mTextCounterView.setTextColor(mBeyondCountLimitTextColor);
            } else {
                mTextCounterView.setTextColor(mWithinCountLimitTextColor);
            }
        }
    }

private void updateEditorTextCount ( ) { if ( mMentionsEditText != null && mTextCounterView != null ) { int textCount = mMentionsEditText . getMentionsText ( ) . length ( ) ; mTextCounterView . setText ( String . valueOf ( textCount ) ) ; if ( mTextCountLimit > 0 && textCount > mTextCountLimit ) { mTextCounterView . setTextColor ( mBeyondCountLimitTextColor ) ; } else { mTextCounterView . setTextColor ( mWithinCountLimitTextColor ) ; } } }

Updates the TextView counting the number of characters in the editor. Sets not only the content
of the TextView, but also the color of the text depending if the limit has been reached.

Updates the TextView counting the number of characters in the editor . Sets not only the content of the TextView but also the color of the text depending if the limit has been reached .


public void setMentionSpanFactory(@NonNull final MentionsEditText.MentionSpanFactory factory) {
        if (mMentionsEditText != null) {
            mMentionsEditText.setMentionSpanFactory(factory);
        }
    }

public void setMentionSpanFactory ( @ NonNull final MentionsEditText . MentionSpanFactory factory ) { if ( mMentionsEditText != null ) { mMentionsEditText . setMentionSpanFactory ( factory ) ; } }

Sets the factory used to create MentionSpans within this class.

@param factory the {@link MentionsEditText.MentionSpanFactory} to use

Sets the factory used to create MentionSpans within this class .


private void copy(@IntRange(from = 0) int start, @IntRange(from = 0) int end) {
        MentionsEditable text = getMentionsText();
        SpannableStringBuilder copiedText = (SpannableStringBuilder) text.subSequence(start, end);
        MentionSpan[] spans = text.getSpans(start, end, MentionSpan.class);
        Intent intent = null;
        if (spans.length > 0) {
            // Save MentionSpan and it's start offset.
            intent = new Intent();
            intent.putExtra(KEY_MENTION_SPANS, spans);
            int[] spanStart = new int[spans.length];
            for (int i = 0; i < spans.length; i++) {
                spanStart[i] = copiedText.getSpanStart(spans[i]);
            }
            intent.putExtra(KEY_MENTION_SPAN_STARTS, spanStart);
        }
        saveToClipboard(copiedText, intent);
    }

Copy the text between start and end in clipboard.
If no span is present, text is saved as plain text but if span is present
save it in Clipboard using intent.

Copy the text between start and end in clipboard . If no span is present text is saved as plain text but if span is present save it in Clipboard using intent .


private void paste(@IntRange(from = 0) int min, @IntRange(from = 0) int max) {
        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
            android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
            MentionsEditable text = getMentionsText();
            text.replace(text.length(), text.length(), clipboard.getText());
        } else {
            pasteHoneycombImpl(min, max);
        }
    }

private void paste ( @ IntRange ( from = 0 ) int min , @ IntRange ( from = 0 ) int max ) { if ( android . os . Build . VERSION . SDK_INT < android . os . Build . VERSION_CODES . HONEYCOMB ) { android . text . ClipboardManager clipboard = ( android . text . ClipboardManager ) getContext ( ) . getSystemService ( Context . CLIPBOARD_SERVICE ) ; MentionsEditable text = getMentionsText ( ) ; text . replace ( text . length ( ) , text . length ( ) , clipboard . getText ( ) ) ; } else { pasteHoneycombImpl ( min , max ) ; } }

Paste clipboard content between min and max positions.

Paste clipboard content between min and max positions .


@TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void pasteHoneycombImpl(@IntRange(from = 0) int min, @IntRange(from = 0) int max) {
        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
        ClipData clip = clipboard.getPrimaryClip();
        if (clip != null) {
            for (int i = 0; i < clip.getItemCount(); i++) {
                ClipData.Item item = clip.getItemAt(i);
                String selectedText = item.coerceToText(getContext()).toString();
                MentionsEditable text = getMentionsText();
                MentionSpan[] spans = text.getSpans(min, max, MentionSpan.class);
                /*
                 * We need to remove the span between min and max. This is required because in
                 * {@link SpannableStringBuilder#replace(int, int, CharSequence)} existing spans within
                 * the Editable that entirely cover the replaced range are retained, but any that
                 * were strictly within the range that was replaced are removed. In our case the existing
                 * spans are retained if the selection entirely covers the span. So, we just remove
                 * the existing span and replace the new text with that span.
                 */
                for (MentionSpan span : spans) {
                    if (text.getSpanEnd(span) == min) {
                        // We do not want to remove the span, when we want to paste anything just next
                        // to the existing span. In this case "text.getSpanEnd(span)" will be equal
                        // to min.
                        continue;
                    }
                    text.removeSpan(span);
                }

                Intent intent = item.getIntent();
                // Just set the plain text if we do not have mentions data in the intent/bundle
                if (intent == null) {
                    text.replace(min, max, selectedText);
                    continue;
                }
                Bundle bundle = intent.getExtras();
                if (bundle == null) {
                    text.replace(min, max, selectedText);
                    continue;
                }
                bundle.setClassLoader(getContext().getClassLoader());
                int[] spanStart = bundle.getIntArray(KEY_MENTION_SPAN_STARTS);
                Parcelable[] parcelables = bundle.getParcelableArray(KEY_MENTION_SPANS);
                if (parcelables == null || parcelables.length <= 0 || spanStart == null || spanStart.length <= 0) {
                    text.replace(min, max, selectedText);
                    continue;
                }

                // Set the MentionSpan in text.
                SpannableStringBuilder s = new SpannableStringBuilder(selectedText);
                for (int j = 0; j < parcelables.length; j++) {
                    MentionSpan mentionSpan = (MentionSpan) parcelables[j];
                    s.setSpan(mentionSpan, spanStart[j], spanStart[j] + mentionSpan.getDisplayString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
                text.replace(min, max, s);
            }
        }
    }

Paste clipboard content between min and max positions. This method is supported for all the api above the 10.
If clipboard content contain the MentionSpan, set the span in copied text.

Paste clipboard content between min and max positions . This method is supported for all the api above the 10 . If clipboard content contain the MentionSpan set the span in copied text .


private void updateSelectionIfRequired(final int selStart, final int selEnd) {
        MentionsEditable text = getMentionsText();
        MentionSpan startMentionSpan = text.getMentionSpanAtOffset(selStart);
        MentionSpan endMentionSpan = text.getMentionSpanAtOffset(selEnd);
        boolean selChanged = false;
        int start = selStart;
        int end = selEnd;
        if (text.getSpanStart(startMentionSpan) < selStart && selStart < text.getSpanEnd(startMentionSpan)) {
            start = text.getSpanStart(startMentionSpan);
            selChanged = true;
        }
        if (text.getSpanStart(endMentionSpan) < selEnd && selEnd < text.getSpanEnd(endMentionSpan)) {
            end = text.getSpanEnd(endMentionSpan);
            selChanged = true;
        }
        if (selChanged) {
            setSelection(start, end);
        }
    }

private void updateSelectionIfRequired ( final int selStart , final int selEnd ) { MentionsEditable text = getMentionsText ( ) ; MentionSpan startMentionSpan = text . getMentionSpanAtOffset ( selStart ) ; MentionSpan endMentionSpan = text . getMentionSpanAtOffset ( selEnd ) ; boolean selChanged = false ; int start = selStart ; int end = selEnd ; if ( text . getSpanStart ( startMentionSpan ) < selStart && selStart < text . getSpanEnd ( startMentionSpan ) ) { start = text . getSpanStart ( startMentionSpan ) ; selChanged = true ; } if ( text . getSpanStart ( endMentionSpan ) < selEnd && selEnd < text . getSpanEnd ( endMentionSpan ) ) { end = text . getSpanEnd ( endMentionSpan ) ; selChanged = true ; } if ( selChanged ) { setSelection ( start , end ) ; } }

Don't allow user to set starting position or ending position of selection within the mention.

Don t allow user to set starting position or ending position of selection within the mention .


private boolean onCursorChanged(final int index) {
        Editable text = getText();
        if (text == null) {
            return false;
        }

        MentionSpan[] allSpans = text.getSpans(0, text.length(), MentionSpan.class);
        for (MentionSpan span : allSpans) {
            // Deselect span if the cursor is not on the span.
            if (span.isSelected() && (index < text.getSpanStart(span) || index > text.getSpanEnd(span))) {
                span.setSelected(false);
                updateSpan(span);
            }
        }

        // Do not allow the user to set the cursor within a span. If the user tries to do so, select
        // move the cursor to the end of it.
        MentionSpan[] currentSpans = text.getSpans(index, index, MentionSpan.class);
        if (currentSpans.length != 0) {
            MentionSpan span = currentSpans[0];
            int start = text.getSpanStart(span);
            int end = text.getSpanEnd(span);
            if (index > start && index < end) {
                super.setSelection(end);
                return true;
            }
        }

        return false;
    }

Method to handle the cursor changing positions. Returns true if handled, false if it should
be passed to the super method.

@param index int position of cursor within the text

@return true if handled

Method to handle the cursor changing positions . Returns true if handled false if it should be passed to the super method .


public void deselectAllSpans() {
        mBlockCompletion = true;
        Editable text = getText();
        MentionSpan[] spans = text.getSpans(0, text.length(), MentionSpan.class);
        for (MentionSpan span : spans) {
            if (span.isSelected()) {
                span.setSelected(false);
                updateSpan(span);
            }
        }
        mBlockCompletion = false;
    }

public void deselectAllSpans ( ) { mBlockCompletion = true ; Editable text = getText ( ) ; MentionSpan [ ] spans = text . getSpans ( 0 , text . length ( ) , MentionSpan . class ) ; for ( MentionSpan span : spans ) { if ( span . isSelected ( ) ) { span . setSelected ( false ) ; updateSpan ( span ) ; } } mBlockCompletion = false ; }

Deselects any spans in the editor that are currently selected.

Deselects any spans in the editor that are currently selected .


public static long parseDuration(String duration) {
        if (duration == null || duration.isEmpty()) {
            throw new IllegalArgumentException("duration may not be null");
        }
        long toAdd = -1;
        if (days.matcher(duration).matches()) {
            Matcher matcher = days.matcher(duration);
            matcher.matches();
            toAdd = Long.parseLong(matcher.group(1)) * 60 * 60 * 24 * 1000;
        } else if (hours.matcher(duration).matches()) {
            Matcher matcher = hours.matcher(duration);
            matcher.matches();
            toAdd = Long.parseLong(matcher.group(1)) * 60 * 60 * 1000;
        } else if (minutes.matcher(duration).matches()) {
            Matcher matcher = minutes.matcher(duration);
            matcher.matches();
            toAdd = Long.parseLong(matcher.group(1)) * 60 * 1000;
        } else if (seconds.matcher(duration).matches()) {
            Matcher matcher = seconds.matcher(duration);
            matcher.matches();
            toAdd = Long.parseLong(matcher.group(1)) * 1000;
        } else if (milliseconds.matcher(duration).matches()) {
            Matcher matcher = milliseconds.matcher(duration);
            matcher.matches();
            toAdd = Long.parseLong(matcher.group(1));
        }
        if (toAdd == -1) {
            throw new IllegalArgumentException("Invalid duration pattern : " + duration);
        }
        return toAdd;
    }

public
static
long
parseDuration
(
String
duration
)
{
if
(
duration
==
||
duration
.
isEmpty
(
)
)
{
throw
new
IllegalArgumentException
(
"duration may not be null"
)
;
}
long
toAdd
=
1
;
if
(
days
.
matcher
(
duration
)
.
matches
(
)
)
{
Matcher
matcher
=
days
.
matcher
(
duration
)
;
matcher
.
matches
(
)
;
toAdd
=
Long
.
parseLong
(
matcher
.
group
(
1
)
)
*
60
*
60
*
24
*
1000
;
}
else
if
(
hours
.
matcher
(
duration
)
.
matches
(
)
)
{
Matcher
matcher
=
hours
.
matcher
(
duration
)
;
matcher
.
matches
(
)
;
toAdd
=
Long
.
parseLong
(
matcher
.
group
(
1
)
)
*
60
*
60
*
1000
;
}
else
if
(
minutes
.
matcher
(
duration
)
.
matches
(
)
)
{
Matcher
matcher
=
minutes
.
matcher
(
duration
)
;
matcher
.
matches
(
)
;
toAdd
=
Long
.
parseLong
(
matcher
.
group
(
1
)
)
*
60
*
1000
;
}
else
if
(
seconds
.
matcher
(
duration
)
.
matches
(
)
)
{
Matcher
matcher
=
seconds
.
matcher
(
duration
)
;
matcher
.
matches
(
)
;
toAdd
=
Long
.
parseLong
(
matcher
.
group
(
1
)
)
*
1000
;
}
else
if
(
milliseconds
.
matcher
(
duration
)
.
matches
(
)
)
{
Matcher
matcher
=
milliseconds
.
matcher
(
duration
)
;
matcher
.
matches
(
)
;
toAdd
=
Long
.
parseLong
(
matcher
.
group
(
1
)
)
;
}
if
(
toAdd
==
1
)
{
throw
new
IllegalArgumentException
(
"Invalid duration pattern : "
+
duration
)
;
}
return
toAdd
;
}

Parse a duration

@param duration
1d, 3h, 2mn, 7s, 50ms
@return The number of milliseconds


public static String escapeForXML(String source) {
        Args.nullNotPermitted(source, "source");
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < source.length(); i++) {
            char c = source.charAt(i);
            switch (c) {
                case '<' : {
                    sb.append("<");
                    break;
                }
                case '>' : {
                    sb.append(">");
                    break;
                }
                case '&' : {
                    String next = source.substring(i, Math.min(i + 6,
                            source.length()));
                    if (next.startsWith("<") || next.startsWith(">")
                            || next.startsWith("&")
                            || next.startsWith("'")
                            || next.startsWith(""")) {
                        sb.append(c);
                    } else {
                        sb.append("&");
                    }
                    break;
                }
                case '\'' : {
                    sb.append("'");
                    break;
                }
                case '\"' : {
                    sb.append(""");
                    break;
                }
                default : sb.append(c);
            }
        }
        return sb.toString();
    }

public static String escapeForXML ( String source ) { Args . nullNotPermitted ( source , "source" ) ; StringBuilder sb = new StringBuilder ( ) ; for ( int i = 0 ; i < source . length ( ) ; i ++ ) { char c = source . charAt ( i ) ; switch ( c ) { case ' ' : { sb . append ( "<" ) ; break ; } case ' ' : { sb . append ( ">" ) ; break ; } case ' ' : { String next = source . substring ( i , Math . min ( i + 6 , source . length ( ) ) ) ; if ( next . startsWith ( "<" ) || next . startsWith ( ">" ) || next . startsWith ( "&" ) || next . startsWith ( "'" ) || next . startsWith ( """ ) ) { sb . append ( c ) ; } else { sb . append ( "&" ) ; } break ; } case ' ' : { sb . append ( "'" ) ; break ; } case ' ' : { sb . append ( """ ) ; break ; } default : sb . append ( c ) ; } } return sb . toString ( ) ; }

Returns a new string where any special characters in the source string
have been encoded.

@param source  the source string ({@code null} not permitted).

@return A new string with special characters escaped for XML.

@since 1.5

Returns a new string where any special characters in the source string have been encoded .


public static void writeToHTML(File file, String title, String svgElement)
            throws IOException {
        BufferedWriter writer = null;
        try {
            FileOutputStream fos = new FileOutputStream(file);
            OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
            writer = new BufferedWriter(osw);
            writer.write("<!DOCTYPE html>\n");
            writer.write("\n");
            writer.write("\n");
            writer.write("<title>" + title + "</title>\n");
            writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n");
            writer.write("\n");
            writer.write("\n");
            writer.write(svgElement + "\n");
            writer.write("\n");
            writer.write("\n");
            writer.flush();
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException ex) {
                Logger.getLogger(SVGUtils.class.getName()).log(Level.SEVERE,
                        null, ex);
            }
        }
    }

Writes an HTML file containing an SVG element.

@param file  the file.
@param title  the title.
@param svgElement  the SVG element.

@throws IOException if there is an I/O problem.

Writes an HTML file containing an SVG element .


@Override
    public BufferedImage createCompatibleImage(int width, int height) {
        ColorModel model = getColorModel();
        WritableRaster raster = model.createCompatibleWritableRaster(width,
                height);
        return new BufferedImage(model, raster, model.isAlphaPremultiplied(),
                null);
    }

@ Override public BufferedImage createCompatibleImage ( int width , int height ) { ColorModel model = getColorModel ( ) ; WritableRaster raster = model . createCompatibleWritableRaster ( width , height ) ; return new BufferedImage ( model , raster , model . isAlphaPremultiplied ( ) , null ) ; }

Creates a compatible image. This override is only here to provide
support for Java 6 because from Java 7 onwards the super class has a
non-abstract implementation for this method.

@param width  the width.
@param height  the height.

@return A compatible image.

Creates a compatible image . This override is only here to provide support for Java 6 because from Java 7 onwards the super class has a non - abstract implementation for this method .


@Override
    public VolatileImage createCompatibleVolatileImage(int width, int height,
            ImageCapabilities caps, int transparency) throws AWTException {
        if (img == null) {
            img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
            gc = img.createGraphics().getDeviceConfiguration();
        }
        return gc.createCompatibleVolatileImage(width, height, caps,
                transparency);
    }

@ Override public VolatileImage createCompatibleVolatileImage ( int width , int height , ImageCapabilities caps , int transparency ) throws AWTException { if ( img == null ) { img = new BufferedImage ( 1 , 1 , BufferedImage . TYPE_INT_ARGB ) ; gc = img . createGraphics ( ) . getDeviceConfiguration ( ) ; } return gc . createCompatibleVolatileImage ( width , height , caps , transparency ) ; }

Returns a volatile image.  This method is a workaround for a
ClassCastException that occurs on MacOSX when exporting a Swing UI
that uses the Nimbus Look and Feel to SVG.

@param width  the image width.
@param height  the image height.
@param caps  the image capabilities.
@param transparency  the transparency.

@return The volatile image.

@throws AWTException if there is a problem creating the image.

Returns a volatile image . This method is a workaround for a ClassCastException that occurs on MacOSX when exporting a Swing UI that uses the Nimbus Look and Feel to SVG .


public String valueStr() {
        return new StringBuilder().append(this.minX).append(" ")
                .append(this.minY).append(" ").append(this.width).append(" ")
                .append(this.height).toString();
    }

public
String
valueStr
(
)
{
return
new
StringBuilder
(
)
.
append
(
this
.
minX
)
.
append
(
" "
)
.
append
(
this
.
minY
)
.
append
(
" "
)
.
append
(
this
.
width
)
.
append
(
" "
)
.
append
(
this
.
height
)
.
toString
(
)
;
}

Returns a string containing the view box coordinates and dimensions.

@return A string containing the view box coordinates and dimensions.

Returns a string containing the view box coordinates and dimensions .


@Override
    public String mapFont(String family) {
        Args.nullNotPermitted(family, "family");
        String alternate = this.alternates.get(family);
        if (alternate != null) {
            return alternate;
        }
        return family;
    }

@ Override public String mapFont ( String family ) { Args . nullNotPermitted ( family , "family" ) ; String alternate = this . alternates . get ( family ) ; if ( alternate != null ) { return alternate ; } return family ; }

Maps the specified font family name to an alternative, or else returns
the same family name.

@param family  the font family name ({@code null} not permitted).

@return The same font family name or an alternative (never {@code null}).

Maps the specified font family name to an alternative or else returns the same family name .


public void setShapeRendering(String value) {
        if (!value.equals("auto") && !value.equals("crispEdges")
                && !value.equals("geometricPrecision")
                && !value.equals("optimizeSpeed")) {
            throw new IllegalArgumentException("Unrecognised value: " + value);
        }
        this.shapeRendering = value;
    }

public
void
setShapeRendering
(
String
value
)
{
if
(
!
value
.
equals
(
"auto"
)
&&
!
value
.
equals
(
"crispEdges"
)
&&
!
value
.
equals
(
"geometricPrecision"
)
&&
!
value
.
equals
(
"optimizeSpeed"
)
)
{
throw
new
IllegalArgumentException
(
"Unrecognised value: "
+
value
)
;
}
this
.
shapeRendering
=
value
;
}

Sets the value of the 'shape-rendering' property that will be written to
the SVG element.  Permitted values are "auto", "crispEdges",
"geometricPrecision", "inherit" and "optimizeSpeed".

@param value  the new value.

@since 2.0

Sets the value of the shape - rendering property that will be written to the SVG element . Permitted values are auto crispEdges geometricPrecision inherit and optimizeSpeed .


public void setTextRendering(String value) {
        if (!value.equals("auto") && !value.equals("optimizeSpeed")
                && !value.equals("optimizeLegibility")
                && !value.equals("geometricPrecision")) {
            throw new IllegalArgumentException("Unrecognised value: " + value);
        }
        this.textRendering = value;
    }

public
void
setTextRendering
(
String
value
)
{
if
(
!
value
.
equals
(
"auto"
)
&&
!
value
.
equals
(
"optimizeSpeed"
)
&&
!
value
.
equals
(
"optimizeLegibility"
)
&&
!
value
.
equals
(
"geometricPrecision"
)
)
{
throw
new
IllegalArgumentException
(
"Unrecognised value: "
+
value
)
;
}
this
.
textRendering
=
value
;
}

Sets the value of the 'text-rendering' property that will be written to
the SVG element.  Permitted values are "auto", "optimizeSpeed",
"optimizeLegibility" and "geometricPrecision".

@param value  the new value.

@since 2.0

Sets the value of the text - rendering property that will be written to the SVG element . Permitted values are auto optimizeSpeed optimizeLegibility and geometricPrecision .


private void appendOptionalElementIDFromHint(StringBuilder sb) {
        String elementID = (String) this.hints.get(SVGHints.KEY_ELEMENT_ID);
        if (elementID != null) {
            this.hints.put(SVGHints.KEY_ELEMENT_ID, null); // clear it
            if (this.elementIDs.contains(elementID)) {
                throw new IllegalStateException("The element id "
                        + elementID + " is already used.");
            } else {
                this.elementIDs.add(elementID);
            }
            this.sb.append("id=\"").append(elementID).append("\" ");
        }
    }

private
void
appendOptionalElementIDFromHint
(
StringBuilder
sb
)
{
String
elementID
=
(
String
)
this
.
hints
.
get
(
SVGHints
.
KEY_ELEMENT_ID
)
;
if
(
elementID
!=
)
{
this
.
hints
.
put
(
SVGHints
.
KEY_ELEMENT_ID
,
)
;
// clear it
if
(
this
.
elementIDs
.
contains
(
elementID
)
)
{
throw
new
IllegalStateException
(
"The element id "
+
elementID
+
" is already used."
)
;
}
else
{
this
.
elementIDs
.
add
(
elementID
)
;
}
this
.
sb
.
append
(
"id=\""
)
.
append
(
elementID
)
.
append
(
"\" "
)
;
}
}

A utility method that appends an optional element id if one is
specified via the rendering hints.

@param sb  the string builder ({@code null} not permitted).

A utility method that appends an optional element id if one is specified via the rendering hints .


private String getSVGPathData(Path2D path) {
        StringBuilder b = new StringBuilder("d=\"");
        float[] coords = new float[6];
        boolean first = true;
        PathIterator iterator = path.getPathIterator(null);
        while (!iterator.isDone()) {
            int type = iterator.currentSegment(coords);
            if (!first) {
                b.append(" ");
            }
            first = false;
            switch (type) {
            case (PathIterator.SEG_MOVETO):
                b.append("M ").append(geomDP(coords[0])).append(" ")
                        .append(geomDP(coords[1]));
                break;
            case (PathIterator.SEG_LINETO):
                b.append("L ").append(geomDP(coords[0])).append(" ")
                        .append(geomDP(coords[1]));
                break;
            case (PathIterator.SEG_QUADTO):
                b.append("Q ").append(geomDP(coords[0]))
                        .append(" ").append(geomDP(coords[1]))
                        .append(" ").append(geomDP(coords[2]))
                        .append(" ").append(geomDP(coords[3]));
                break;
            case (PathIterator.SEG_CUBICTO):
                b.append("C ").append(geomDP(coords[0])).append(" ")
                        .append(geomDP(coords[1])).append(" ")
                        .append(geomDP(coords[2])).append(" ")
                        .append(geomDP(coords[3])).append(" ")
                        .append(geomDP(coords[4])).append(" ")
                        .append(geomDP(coords[5]));
                break;
            case (PathIterator.SEG_CLOSE):
                b.append("Z ");
                break;
            default:
                break;
            }
            iterator.next();
        }
        return b.append("\"").toString();
    }

private
String
getSVGPathData
(
Path2D
path
)
{
StringBuilder
b
=
new
StringBuilder
(
"d=\""
)
;
float
[
]
coords
=
new
float
[
6
]
;
boolean
first
=
true
;
PathIterator
iterator
=
path
.
getPathIterator
(
)
;
while
(
!
iterator
.
isDone
(
)
)
{
int
type
=
iterator
.
currentSegment
(
coords
)
;
if
(
!
first
)
{
b
.
append
(
" "
)
;
}
first
=
false
;
switch
(
type
)
{
case
(
PathIterator
.
SEG_MOVETO
)
:
b
.
append
(
"M "
)
.
append
(
geomDP
(
coords
[
0
]
)
)
.
append
(
" "
)
.
append
(
geomDP
(
coords
[
1
]
)
)
;
break
;
case
(
PathIterator
.
SEG_LINETO
)
:
b
.
append
(
"L "
)
.
append
(
geomDP
(
coords
[
0
]
)
)
.
append
(
" "
)
.
append
(
geomDP
(
coords
[
1
]
)
)
;
break
;
case
(
PathIterator
.
SEG_QUADTO
)
:
b
.
append
(
"Q "
)
.
append
(
geomDP
(
coords
[
0
]
)
)
.
append
(
" "
)
.
append
(
geomDP
(
coords
[
1
]
)
)
.
append
(
" "
)
.
append
(
geomDP
(
coords
[
2
]
)
)
.
append
(
" "
)
.
append
(
geomDP
(
coords
[
3
]
)
)
;
break
;
case
(
PathIterator
.
SEG_CUBICTO
)
:
b
.
append
(
"C "
)
.
append
(
geomDP
(
coords
[
0
]
)
)
.
append
(
" "
)
.
append
(
geomDP
(
coords
[
1
]
)
)
.
append
(
" "
)
.
append
(
geomDP
(
coords
[
2
]
)
)
.
append
(
" "
)
.
append
(
geomDP
(
coords
[
3
]
)
)
.
append
(
" "
)
.
append
(
geomDP
(
coords
[
4
]
)
)
.
append
(
" "
)
.
append
(
geomDP
(
coords
[
5
]
)
)
;
break
;
case
(
PathIterator
.
SEG_CLOSE
)
:
b
.
append
(
"Z "
)
;
break
;
default
:
break
;
}
iterator
.
next
(
)
;
}
return
b
.
append
(
"\""
)
.
toString
(
)
;
}

Creates an SVG path string for the supplied Java2D path.

@param path  the path ({@code null} not permitted).

@return An SVG path string.

Creates an SVG path string for the supplied Java2D path .


private String rgbColorStr(Color c) {
        StringBuilder b = new StringBuilder("rgb(");
        b.append(c.getRed()).append(",").append(c.getGreen()).append(",")
                .append(c.getBlue()).append(")");
        return b.toString();
    }

private String rgbColorStr ( Color c ) { StringBuilder b = new StringBuilder ( "rgb(" ) ; b . append ( c . getRed ( ) ) . append ( "," ) . append ( c . getGreen ( ) ) . append ( "," ) . append ( c . getBlue ( ) ) . append ( ")" ) ; return b . toString ( ) ; }

Returns the SVG RGB color string for the specified color.

@param c  the color ({@code null} not permitted).

@return The SVG RGB color string.

Returns the SVG RGB color string for the specified color .


private String rgbaColorStr(Color c) {
        StringBuilder b = new StringBuilder("rgba(");
        double alphaPercent = c.getAlpha() / 255.0;
        b.append(c.getRed()).append(",").append(c.getGreen()).append(",")
                .append(c.getBlue());
        b.append(",").append(transformDP(alphaPercent));
        b.append(")");
        return b.toString();
    }

private String rgbaColorStr ( Color c ) { StringBuilder b = new StringBuilder ( "rgba(" ) ; double alphaPercent = c . getAlpha ( ) / 255.0 ; b . append ( c . getRed ( ) ) . append ( "," ) . append ( c . getGreen ( ) ) . append ( "," ) . append ( c . getBlue ( ) ) ; b . append ( "," ) . append ( transformDP ( alphaPercent ) ) ; b . append ( ")" ) ; return b . toString ( ) ; }

Returns a string representing the specified color in RGBA format.

@param c  the color ({@code null} not permitted).

@return The SVG RGBA color string.

Returns a string representing the specified color in RGBA format .


private String strokeStyle() {
        double strokeWidth = 1.0f;
        String strokeCap = DEFAULT_STROKE_CAP;
        String strokeJoin = DEFAULT_STROKE_JOIN;
        float miterLimit = DEFAULT_MITER_LIMIT;
        float[] dashArray = new float[0];
        if (this.stroke instanceof BasicStroke) {
            BasicStroke bs = (BasicStroke) this.stroke;
            strokeWidth = bs.getLineWidth() > 0.0 ? bs.getLineWidth()
                    : this.zeroStrokeWidth;
            switch (bs.getEndCap()) {
                case BasicStroke.CAP_ROUND:
                    strokeCap = "round";
                    break;
                case BasicStroke.CAP_SQUARE:
                    strokeCap = "square";
                    break;
                case BasicStroke.CAP_BUTT:
                default:
                    // already set to "butt"
            }
            switch (bs.getLineJoin()) {
                case BasicStroke.JOIN_BEVEL:
                    strokeJoin = "bevel";
                    break;
                case BasicStroke.JOIN_ROUND:
                    strokeJoin = "round";
                    break;
                case BasicStroke.JOIN_MITER:
                default:
                    // already set to "miter"
            }
            miterLimit = bs.getMiterLimit();
            dashArray = bs.getDashArray();
        }
        StringBuilder b = new StringBuilder();
        b.append("stroke-width: ").append(strokeWidth).append(";");
        b.append("stroke: ").append(svgColorStr()).append(";");
        b.append("stroke-opacity: ").append(getColorAlpha() * getAlpha())
                .append(";");
        if (!strokeCap.equals(DEFAULT_STROKE_CAP)) {
            b.append("stroke-linecap: ").append(strokeCap).append(";");
        }
        if (!strokeJoin.equals(DEFAULT_STROKE_JOIN)) {
            b.append("stroke-linejoin: ").append(strokeJoin).append(";");
        }
        if (Math.abs(DEFAULT_MITER_LIMIT - miterLimit) < 0.001) {
            b.append("stroke-miterlimit: ").append(geomDP(miterLimit));
        }
        if (dashArray != null && dashArray.length != 0) {
            b.append("stroke-dasharray: ");
            for (int i = 0; i < dashArray.length; i++) {
                if (i != 0) b.append(", ");
                b.append(dashArray[i]);
            }
            b.append(";");
        }
        if (this.checkStrokeControlHint) {
            Object hint = getRenderingHint(RenderingHints.KEY_STROKE_CONTROL);
            if (RenderingHints.VALUE_STROKE_NORMALIZE.equals(hint)
                    && !this.shapeRendering.equals("crispEdges")) {
                b.append("shape-rendering:crispEdges;");
            }
            if (RenderingHints.VALUE_STROKE_PURE.equals(hint)
                    && !this.shapeRendering.equals("geometricPrecision")) {
                b.append("shape-rendering:geometricPrecision;");
            }
        }
        return b.toString();
    }

private
String
strokeStyle
(
)
{
double
strokeWidth
=
1.0f
;
String
strokeCap
=
DEFAULT_STROKE_CAP
;
String
strokeJoin
=
DEFAULT_STROKE_JOIN
;
float
miterLimit
=
DEFAULT_MITER_LIMIT
;
float
[
]
dashArray
=
new
float
[
0
]
;
if
(
this
.
stroke
instanceof
BasicStroke
)
{
BasicStroke
bs
=
(
BasicStroke
)
this
.
stroke
;
strokeWidth
=
bs
.
getLineWidth
(
)
>
0.0
bs
.
getLineWidth
(
)
:
this
.
zeroStrokeWidth
;
switch
(
bs
.
getEndCap
(
)
)
{
case
BasicStroke
.
CAP_ROUND
:
strokeCap
=
"round"
;
break
;
case
BasicStroke
.
CAP_SQUARE
:
strokeCap
=
"square"
;
break
;
case
BasicStroke
.
CAP_BUTT
:
default
:
// already set to "butt"
}
switch
(
bs
.
getLineJoin
(
)
)
{
case
BasicStroke
.
JOIN_BEVEL
:
strokeJoin
=
"bevel"
;
break
;
case
BasicStroke
.
JOIN_ROUND
:
strokeJoin
=
"round"
;
break
;
case
BasicStroke
.
JOIN_MITER
:
default
:
// already set to "miter"
}
miterLimit
=
bs
.
getMiterLimit
(
)
;
dashArray
=
bs
.
getDashArray
(
)
;
}
StringBuilder
b
=
new
StringBuilder
(
)
;
b
.
append
(
"stroke-width: "
)
.
append
(
strokeWidth
)
.
append
(
";"
)
;
b
.
append
(
"stroke: "
)
.
append
(
svgColorStr
(
)
)
.
append
(
";"
)
;
b
.
append
(
"stroke-opacity: "
)
.
append
(
getColorAlpha
(
)
*
getAlpha
(
)
)
.
append
(
";"
)
;
if
(
!
strokeCap
.
equals
(
DEFAULT_STROKE_CAP
)
)
{
b
.
append
(
"stroke-linecap: "
)
.
append
(
strokeCap
)
.
append
(
";"
)
;
}
if
(
!
strokeJoin
.
equals
(
DEFAULT_STROKE_JOIN
)
)
{
b
.
append
(
"stroke-linejoin: "
)
.
append
(
strokeJoin
)
.
append
(
";"
)
;
}
if
(
Math
.
abs
(
DEFAULT_MITER_LIMIT
miterLimit
)
<
0.001
)
{
b
.
append
(
"stroke-miterlimit: "
)
.
append
(
geomDP
(
miterLimit
)
)
;
}
if
(
dashArray
!=
&&
dashArray
.
length
!=
0
)
{
b
.
append
(
"stroke-dasharray: "
)
;
for
(
int
i
=
0
;
i
<
dashArray
.
length
;
i
++
)
{
if
(
i
!=
0
)
b
.
append
(
", "
)
;
b
.
append
(
dashArray
[
i
]
)
;
}
b
.
append
(
";"
)
;
}
if
(
this
.
checkStrokeControlHint
)
{
Object
hint
=
getRenderingHint
(
RenderingHints
.
KEY_STROKE_CONTROL
)
;
if
(
RenderingHints
.
VALUE_STROKE_NORMALIZE
.
equals
(
hint
)
&&
!
this
.
shapeRendering
.
equals
(
"crispEdges"
)
)
{
b
.
append
(
"shape-rendering:crispEdges;"
)
;
}
if
(
RenderingHints
.
VALUE_STROKE_PURE
.
equals
(
hint
)
&&
!
this
.
shapeRendering
.
equals
(
"geometricPrecision"
)
)
{
b
.
append
(
"shape-rendering:geometricPrecision;"
)
;
}
}
return
b
.
toString
(
)
;
}

Returns a stroke style string based on the current stroke and
alpha settings.

@return A stroke style string.

Returns a stroke style string based on the current stroke and alpha settings .


private String getSVGFillStyle() {
        StringBuilder b = new StringBuilder();
        b.append("fill: ").append(svgColorStr()).append("; ");
        b.append("fill-opacity: ").append(getColorAlpha() * getAlpha());
        return b.toString();
    }

private
String
getSVGFillStyle
(
)
{
StringBuilder
b
=
new
StringBuilder
(
)
;
b
.
append
(
"fill: "
)
.
append
(
svgColorStr
(
)
)
.
append
(
"; "
)
;
b
.
append
(
"fill-opacity: "
)
.
append
(
getColorAlpha
(
)
*
getAlpha
(
)
)
;
return
b
.
toString
(
)
;
}

Returns a fill style string based on the current paint and
alpha settings.

@return A fill style string.

Returns a fill style string based on the current paint and alpha settings .


private String getSVGFontStyle() {
        StringBuilder b = new StringBuilder();
        b.append("fill: ").append(svgColorStr()).append("; ");
        b.append("fill-opacity: ").append(getColorAlpha() * getAlpha())
                .append("; ");
        String fontFamily = this.fontMapper.mapFont(this.font.getFamily());
        b.append("font-family: ").append(fontFamily).append("; ");
        b.append("font-size: ").append(this.font.getSize()).append(this.fontSizeUnits).append(";");
        if (this.font.isBold()) {
            b.append(" font-weight: bold;");
        }
        if (this.font.isItalic()) {
            b.append(" font-style: italic;");
        }
        return b.toString();
    }

private
String
getSVGFontStyle
(
)
{
StringBuilder
b
=
new
StringBuilder
(
)
;
b
.
append
(
"fill: "
)
.
append
(
svgColorStr
(
)
)
.
append
(
"; "
)
;
b
.
append
(
"fill-opacity: "
)
.
append
(
getColorAlpha
(
)
*
getAlpha
(
)
)
.
append
(
"; "
)
;
String
fontFamily
=
this
.
fontMapper
.
mapFont
(
this
.
font
.
getFamily
(
)
)
;
b
.
append
(
"font-family: "
)
.
append
(
fontFamily
)
.
append
(
"; "
)
;
b
.
append
(
"font-size: "
)
.
append
(
this
.
font
.
getSize
(
)
)
.
append
(
this
.
fontSizeUnits
)
.
append
(
";"
)
;
if
(
this
.
font
.
isBold
(
)
)
{
b
.
append
(
" font-weight: bold;"
)
;
}
if
(
this
.
font
.
isItalic
(
)
)
{
b
.
append
(
" font-style: italic;"
)
;
}
return
b
.
toString
(
)
;
}

Returns a string containing font style info.

@return A string containing font style info.

Returns a string containing font style info .


@Override
    public FontMetrics getFontMetrics(Font f) {
        if (this.fmImage == null) {
            this.fmImage = new BufferedImage(10, 10,
                    BufferedImage.TYPE_INT_RGB);
            this.fmImageG2D = this.fmImage.createGraphics();
            this.fmImageG2D.setRenderingHint(
                    RenderingHints.KEY_FRACTIONALMETRICS,
                    RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        }
        return this.fmImageG2D.getFontMetrics(f);
    }

@ Override public FontMetrics getFontMetrics ( Font f ) { if ( this . fmImage == null ) { this . fmImage = new BufferedImage ( 10 , 10 , BufferedImage . TYPE_INT_RGB ) ; this . fmImageG2D = this . fmImage . createGraphics ( ) ; this . fmImageG2D . setRenderingHint ( RenderingHints . KEY_FRACTIONALMETRICS , RenderingHints . VALUE_FRACTIONALMETRICS_ON ) ; } return this . fmImageG2D . getFontMetrics ( f ) ; }

Returns the font metrics for the specified font.

@param f  the font.

@return The font metrics.

Returns the font metrics for the specified font .


@Override
    public void scale(double sx, double sy) {
        AffineTransform t = getTransform();
        t.scale(sx, sy);
        setTransform(t);
    }

@ Override public void scale ( double sx , double sy ) { AffineTransform t = getTransform ( ) ; t . scale ( sx , sy ) ; setTransform ( t ) ; }

Applies a scale transformation.

@param sx  the x-scaling factor.
@param sy  the y-scaling factor.


@Override
    public void transform(AffineTransform t) {
        AffineTransform tx = getTransform();
        tx.concatenate(t);
        setTransform(tx);
    }

@ Override public void transform ( AffineTransform t ) { AffineTransform tx = getTransform ( ) ; tx . concatenate ( t ) ; setTransform ( tx ) ; }

Applies this transform to the existing transform by concatenating it.

@param t  the transform ({@code null} not permitted).

Applies this transform to the existing transform by concatenating it .


@Override
    public void setTransform(AffineTransform t) {
        if (t == null) {
            this.transform = new AffineTransform();
        } else {
            this.transform = new AffineTransform(t);
        }
        this.clipRef = null;
    }

@ Override public void setTransform ( AffineTransform t ) { if ( t == null ) { this . transform = new AffineTransform ( ) ; } else { this . transform = new AffineTransform ( t ) ; } this . clipRef = null ; }

Sets the transform.

@param t  the new transform ({@code null} permitted, resets to the
identity transform).

@see #getTransform()


@Override
    public void setClip(Shape shape) {
        // null is handled fine here...
        this.clip = this.transform.createTransformedShape(shape);
        this.clipRef = null;
    }

@
Override
public
void
setClip
(
Shape
shape
)
{
// null is handled fine here...
this
.
clip
=
this
.
transform
.
createTransformedShape
(
shape
)
;
this
.
clipRef
=
;
}

Sets the user clipping region.

@param shape  the new user clipping region ({@code null} permitted).

@see #getClip()


private String registerClip(Shape clip) {
        if (clip == null) {
            this.clipRef = null;
            return null;
        }
        // generate the path
        String pathStr = getSVGPathData(new Path2D.Double(clip));
        int index = this.clipPaths.indexOf(pathStr);
        if (index < 0) {
            this.clipPaths.add(pathStr);
            index = this.clipPaths.size() - 1;
        }
        return this.defsKeyPrefix + CLIP_KEY_PREFIX + index;
    }

private
String
registerClip
(
Shape
clip
)
{
if
(
clip
==
)
{
this
.
clipRef
=
;
return
;
}
// generate the path
String
pathStr
=
getSVGPathData
(
new
Path2D
.
Double
(
clip
)
)
;
int
index
=
this
.
clipPaths
.
indexOf
(
pathStr
)
;
if
(
index
<
0
)
{
this
.
clipPaths
.
add
(
pathStr
)
;
index
=
this
.
clipPaths
.
size
(
)
1
;
}
return
this
.
defsKeyPrefix
+
CLIP_KEY_PREFIX
+
index
;
}

Registers the clip so that we can later write out all the clip
definitions in the DEFS element.

@param clip  the clip (ignored if {@code null})

Registers the clip so that we can later write out all the clip definitions in the DEFS element .


@Override
    public void clip(Shape s) {
        if (s instanceof Line2D) {
            s = s.getBounds2D();
        }
        if (this.clip == null) {
            setClip(s);
            return;
        }
        Shape ts = this.transform.createTransformedShape(s);
        if (!ts.intersects(this.clip.getBounds2D())) {
            setClip(new Rectangle2D.Double());
        } else {
          Area a1 = new Area(ts);
          Area a2 = new Area(this.clip);
          a1.intersect(a2);
          this.clip = new Path2D.Double(a1);
        }
        this.clipRef = null;
    }

@ Override public void clip ( Shape s ) { if ( s instanceof Line2D ) { s = s . getBounds2D ( ) ; } if ( this . clip == null ) { setClip ( s ) ; return ; } Shape ts = this . transform . createTransformedShape ( s ) ; if ( ! ts . intersects ( this . clip . getBounds2D ( ) ) ) { setClip ( new Rectangle2D . Double ( ) ) ; } else { Area a1 = new Area ( ts ) ; Area a2 = new Area ( this . clip ) ; a1 . intersect ( a2 ) ; this . clip = new Path2D . Double ( a1 ) ; } this . clipRef = null ; }

Clips to the intersection of the current clipping region and the
specified shape.

According to the Oracle API specification, this method will accept a
{@code null} argument, but there is an open bug report (since 2004)
that suggests this is wrong:

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6206189

@param s  the clip shape ({@code null} not permitted).

Clips to the intersection of the current clipping region and the specified shape .


@Override
    public void clipRect(int x, int y, int width, int height) {
        setRect(x, y, width, height);
        clip(this.rect);
    }

@ Override public void clipRect ( int x , int y , int width , int height ) { setRect ( x , y , width , height ) ; clip ( this . rect ) ; }

Clips to the intersection of the current clipping region and the
specified rectangle.

@param x  the x-coordinate.
@param y  the y-coordinate.
@param width  the width.
@param height  the height.

Clips to the intersection of the current clipping region and the specified rectangle .


@Override
    public void setClip(int x, int y, int width, int height) {
        setRect(x, y, width, height);
        setClip(this.rect);
    }

@ Override public void setClip ( int x , int y , int width , int height ) { setRect ( x , y , width , height ) ; setClip ( this . rect ) ; }

Sets the user clipping region to the specified rectangle.

@param x  the x-coordinate.
@param y  the y-coordinate.
@param width  the width.
@param height  the height.

@see #getClip()

Sets the user clipping region to the specified rectangle .


private byte[] getPNGBytes(Image img) {
        RenderedImage ri;
        if (img instanceof RenderedImage) {
            ri = (RenderedImage) img;
        } else {
            BufferedImage bi = new BufferedImage(img.getWidth(null),
                    img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2 = bi.createGraphics();
            g2.drawImage(img, 0, 0, null);
            ri = bi;
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            ImageIO.write(ri, "png", baos);
        } catch (IOException ex) {
            Logger.getLogger(SVGGraphics2D.class.getName()).log(Level.SEVERE,
                    "IOException while writing PNG data.", ex);
        }
        return baos.toByteArray();
    }

private
byte
[
]
getPNGBytes
(
Image
img
)
{
RenderedImage
ri
;
if
(
img
instanceof
RenderedImage
)
{
ri
=
(
RenderedImage
)
img
;
}
else
{
BufferedImage
bi
=
new
BufferedImage
(
img
.
getWidth
(
)
,
img
.
getHeight
(
)
,
BufferedImage
.
TYPE_INT_ARGB
)
;
Graphics2D
g2
=
bi
.
createGraphics
(
)
;
g2
.
drawImage
(
img
,
0
,
0
,
)
;
ri
=
bi
;
}
ByteArrayOutputStream
baos
=
new
ByteArrayOutputStream
(
)
;
try
{
ImageIO
.
write
(
ri
,
"png"
,
baos
)
;
}
catch
(
IOException
ex
)
{
Logger
.
getLogger
(
SVGGraphics2D
.
class
.
getName
(
)
)
.
log
(
Level
.
SEVERE
,
"IOException while writing PNG data."
,
ex
)
;
}
return
baos
.
toByteArray
(
)
;
}

Returns the bytes representing a PNG format image.

@param img  the image to encode.

@return The bytes representing a PNG format image.

Returns the bytes representing a PNG format image .


@Override
    public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
        BufferedImage bi = GraphicsUtils.convertRenderedImage(img);
        drawImage(bi, xform, null);
    }

@ Override public void drawRenderedImage ( RenderedImage img , AffineTransform xform ) { BufferedImage bi = GraphicsUtils . convertRenderedImage ( img ) ; drawImage ( bi , xform , null ) ; }

Draws the rendered image.

@param img  the image.
@param xform  the transform.


@Override
    public void drawRenderableImage(RenderableImage img,
            AffineTransform xform) {
        RenderedImage ri = img.createDefaultRendering();
        drawRenderedImage(ri, xform);
    }

@ Override public void drawRenderableImage ( RenderableImage img , AffineTransform xform ) { RenderedImage ri = img . createDefaultRendering ( ) ; drawRenderedImage ( ri , xform ) ; }

Draws the renderable image.

@param img  the renderable image.
@param xform  the transform.


private String getRadialGradientElement(String id, RadialGradientPaint rgp) {
        StringBuilder b = new StringBuilder("<radialGradient id=\"").append(id)
                .append("\" gradientUnits=\"userSpaceOnUse\" ");
        Point2D center = rgp.getCenterPoint();
        Point2D focus = rgp.getFocusPoint();
        float radius = rgp.getRadius();
        b.append("cx=\"").append(geomDP(center.getX())).append("\" ");
        b.append("cy=\"").append(geomDP(center.getY())).append("\" ");
        b.append("r=\"").append(geomDP(radius)).append("\" ");
        b.append("fx=\"").append(geomDP(focus.getX())).append("\" ");
        b.append("fy=\"").append(geomDP(focus.getY())).append("\">");

        Color[] colors = rgp.getColors();
        float[] fractions = rgp.getFractions();
        for (int i = 0; i < colors.length; i++) {
            Color c = colors[i];
            float f = fractions[i];
            b.append("<stop offset=\"").append(geomDP(f * 100)).append("%\" ");
            b.append("stop-color=\"").append(rgbColorStr(c)).append("\"");
            if (c.getAlpha() < 255) {
                double alphaPercent = c.getAlpha() / 255.0;
                b.append(" stop-opacity=\"").append(transformDP(alphaPercent))
                        .append("\"");
            }
            b.append("/>");
        }
        return b.append("</radialGradient>").toString();
    }

private
String
getRadialGradientElement
(
String
id
,
RadialGradientPaint
rgp
)
{
StringBuilder
b
=
new
StringBuilder
(
"<radialGradient id=\""
)
.
append
(
id
)
.
append
(
"\" gradientUnits=\"userSpaceOnUse\" "
)
;
Point2D
center
=
rgp
.
getCenterPoint
(
)
;
Point2D
focus
=
rgp
.
getFocusPoint
(
)
;
float
radius
=
rgp
.
getRadius
(
)
;
b
.
append
(
"cx=\""
)
.
append
(
geomDP
(
center
.
getX
(
)
)
)
.
append
(
"\" "
)
;
b
.
append
(
"cy=\""
)
.
append
(
geomDP
(
center
.
getY
(
)
)
)
.
append
(
"\" "
)
;
b
.
append
(
"r=\""
)
.
append
(
geomDP
(
radius
)
)
.
append
(
"\" "
)
;
b
.
append
(
"fx=\""
)
.
append
(
geomDP
(
focus
.
getX
(
)
)
)
.
append
(
"\" "
)
;
b
.
append
(
"fy=\""
)
.
append
(
geomDP
(
focus
.
getY
(
)
)
)
.
append
(
"\">"
)
;
Color
[
]
colors
=
rgp
.
getColors
(
)
;
float
[
]
fractions
=
rgp
.
getFractions
(
)
;
for
(
int
i
=
0
;
i
<
colors
.
length
;
i
++
)
{
Color
c
=
colors
[
i
]
;
float
f
=
fractions
[
i
]
;
b
.
append
(
"<stop offset=\""
)
.
append
(
geomDP
(
f
*
100
)
)
.
append
(
"%\" "
)
;
b
.
append
(
"stop-color=\""
)
.
append
(
rgbColorStr
(
c
)
)
.
append
(
"\""
)
;
if
(
c
.
getAlpha
(
)
<
255
)
{
double
alphaPercent
=
c
.
getAlpha
(
)
/
255.0
;
b
.
append
(
" stop-opacity=\""
)
.
append
(
transformDP
(
alphaPercent
)
)
.
append
(
"\""
)
;
}
b
.
append
(
"/>"
)
;
}
return
b
.
append
(
"</radialGradient>"
)
.
toString
(
)
;
}

Returns an element to represent a radial gradient.  All the radial
gradients that are used get written to the DEFS element in the SVG.

@param id  the reference id.
@param rgp  the radial gradient.

@return The SVG element.

Returns an element to represent a radial gradient . All the radial gradients that are used get written to the DEFS element in the SVG .


private String getClipPathRef() {
        if (this.clip == null) {
            return "";
        }
        if (this.clipRef == null) {
            this.clipRef = registerClip(getClip());
        }
        StringBuilder b = new StringBuilder();
        b.append("clip-path=\"url(#").append(this.clipRef).append(")\"");
        return b.toString();
    }

private String getClipPathRef ( ) { if ( this . clip == null ) { return "" ; } if ( this . clipRef == null ) { this . clipRef = registerClip ( getClip ( ) ) ; } StringBuilder b = new StringBuilder ( ) ; b . append ( "clip-path=\"url(#" ) . append ( this . clipRef ) . append ( ")\"" ) ; return b . toString ( ) ; }

Returns a clip path reference for the current user clip.  This is
written out on all SVG elements that draw or fill shapes or text.

@return A clip path reference.

Returns a clip path reference for the current user clip . This is written out on all SVG elements that draw or fill shapes or text .


public static List<String> getParts(String string, final String... regexpList) {
    final List<String> parts = new ArrayList<>();
    for (final String regexp : regexpList) {
      final Pattern pattern = Pattern.compile(regexp);
      final Matcher matcher = pattern.matcher(string);
      final boolean found = matcher.find();
      if (!found) {
        return new ArrayList<>();
      }
      final String part = matcher.group(1).trim();
      parts.add(part);
      string = string.replaceFirst(quote(matcher.group()), "").trim();
    }
    return parts;
  }

public static List < String > getParts ( String string , final String ... regexpList ) { final List < String > parts = new ArrayList <> ( ) ; for ( final String regexp : regexpList ) { final Pattern pattern = Pattern . compile ( regexp ) ; final Matcher matcher = pattern . matcher ( string ) ; final boolean found = matcher . find ( ) ; if ( ! found ) { return new ArrayList <> ( ) ; } final String part = matcher . group ( 1 ) . trim ( ) ; parts . add ( part ) ; string = string . replaceFirst ( quote ( matcher . group ( ) ) , "" ) . trim ( ) ; } return parts ; }

Match one regexp at a time. Remove the matched part from the string, trim, and match next
regexp on that string...

Match one regexp at a time . Remove the matched part from the string trim and match next regexp on that string ...


protected void recordLevel(final String statKey, final long level) {
        final String longKey = getActualStatKey(statKey);
        statsCollector.recordLevel(longKey, level);
    }

protected void recordLevel ( final String statKey , final long level ) { final String longKey = getActualStatKey ( statKey ) ; statsCollector . recordLevel ( longKey , level ) ; }

Prefixes the stats key with the stat key prefix, and then calls statsCollector.recordLevel.

@param statKey statKey
@param level   level

Prefixes the stats key with the stat key prefix and then calls statsCollector . recordLevel .