+ * TODO: Replace all uses of this class before publishing your app.
+ */
+public class DummyRepositories {
+
+ /**
+ * An array of sample (dummy) items.
+ */
+ private static final List
+ * Lhe será fornecido um username, uma senha e um authTokenType, que podem ser utilizados
+ * para requisitar o seu serviço de autenticação.
+ *
+ * @param username um nome de usuário obtido através do {@link Account#name}.
+ * @param password uma senha obtida através do {@link AccountManager#getPassword(Account)}.
+ * @param authTokenType um TokenType
+ * @return Deve retornar um token de autorização
+ */
+ protected abstract String signIn(@NonNull String username, @NonNull String password,
+ @NonNull String authTokenType);
+
+ @NonNull
+ private Bundle makeReturnableBundle(Intent intent) {
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(AccountManager.KEY_INTENT, intent);
+ return bundle;
+ }
+
+ private void log(String message) {
+ Timber.v(message);
+ }
+
+ /**
+ * Adds an account of the specified accountType.
+ *
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param accountType the type of account to add, will never be null
+ * @param authTokenType the type of auth token to retrieve after adding the account, may be null
+ * @param requiredFeatures a String array of authenticator-specific features that the added
+ * account must support, may be null
+ * @param options a Bundle of authenticator-specific options, may be null
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ *
+ * If not {@code null}, the resultant {@link Bundle} will contain different sets of keys
+ * depending on whether a token was successfully issued and, if not, whether one
+ * could be issued via some {@link Activity}.
+ *
+ * If a token cannot be provided without some additional activity, the Bundle should contain
+ * {@link AccountManager#KEY_INTENT} with an associated {@link Intent}. On the other hand, if
+ * there is no such activity, then a Bundle containing
+ * {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} should be
+ * returned.
+ *
+ * If a token can be successfully issued, the implementation should return the
+ * {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of the
+ * account associated with the token as well as the {@link AccountManager#KEY_AUTHTOKEN}. In
+ * addition {@link AbstractAccountAuthenticator} implementations that declare themselves
+ * {@code android:customTokens=true} may also provide a non-negative {@link
+ * #KEY_CUSTOM_TOKEN_EXPIRY} long value containing the expiration timestamp of the expiration
+ * time (in millis since the unix epoch).
+ *
+ * Implementers should assume that tokens will be cached on the basis of account and
+ * authTokenType. The system may ignore the contents of the supplied options Bundle when
+ * determining to re-use a cached token. Furthermore, implementers should assume a supplied
+ * expiration time will be treated as non-binding advice.
+ *
+ * Finally, note that for android:customTokens=false authenticators, tokens are cached
+ * indefinitely until some client calls {@link
+ * AccountManager#invalidateAuthToken(String, String)}.
+ *
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account whose credentials are to be retrieved, will never be null
+ * @param authTokenType the type of auth token to retrieve, will never be null
+ * @param options a Bundle of authenticator-specific options, may be null
+ * @return a Bundle result or null if the result is to be returned via the response.
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ @Override
+ public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType,
+ Bundle options) throws NetworkErrorException {
+ log("FeatherAccountAuthenticator#getAuthToken()");
+
+ // If the caller requested an authToken type we don't support, then
+ // return an error
+ if (mTokenTypes.contains(authTokenType)) {
+ final Bundle result = new Bundle();
+ result.putString(AccountManager.KEY_ERROR_MESSAGE, "invalid authTokenType");
+ return result;
+ }
+
+ // Extract the username and password from the Account Manager, and ask
+ // the server for an appropriate AuthToken.
+ final AccountManager am = AccountManager.get(mContext);
+
+ //noinspection MissingPermission
+ String authToken = am.peekAuthToken(account, authTokenType);
+
+ log("FeatherAccountAuthenticator#getAuthToken() > peekAuthToken returned: " + authToken);
+
+ // Lets give another try to authenticate the user
+ if (TextUtils.isEmpty(authToken)) {
+ //noinspection MissingPermission
+ final String password = am.getPassword(account);
+ if (password != null) {
+ try {
+ log("FeatherAccountAuthenticator#getAuthToken() > re-authenticating with the existing password");
+ authToken = signIn(account.name, password, authTokenType);
+ } catch (Exception e) {
+ Timber.w(e);
+ }
+ }
+ }
+
+ // If we get an authToken - we return it
+ if (!TextUtils.isEmpty(authToken)) {
+ final Bundle result = new Bundle();
+ result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+ result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
+ return result;
+ }
+
+ // If we get here, then we couldn't access the user's password - so we
+ // need to re-prompt them for their credentials. We do that by creating
+ // an intent to display our AuthenticatorActivity.
+ final Intent intent = new Intent(mContext, provideAccountAuthenticatorActivityClass());
+ intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
+ intent.putExtra(FeatherAccountAuthenticatorActivity.ARG_ACCOUNT_TYPE, account.type);
+ intent.putExtra(FeatherAccountAuthenticatorActivity.ARG_AUTH_TOKEN_TYPE, authTokenType);
+ intent.putExtra(FeatherAccountAuthenticatorActivity.ARG_ACCOUNT_NAME, account.name);
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(AccountManager.KEY_INTENT, intent);
+ return bundle;
+ }
+
+ /**
+ * Ask the authenticator for a localized label for the given authTokenType.
+ *
+ * @param authTokenType the authTokenType whose label is to be returned, will never be null
+ * @return the localized label of the auth token type, may be null if the type isn't known
+ */
+ @Override
+ public String getAuthTokenLabel(String authTokenType) {
+ return mTokenTypes.getLabel(authTokenType);
+ }
+
+ /**
+ * Checks that the user knows the credentials of an account.
+ *
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account whose credentials are to be checked, will never be null
+ * @param options a Bundle of authenticator-specific options, may be null
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ *
+ * - toContentValues(boolean): define que uma entidade do ContentResolver
+ * deve saber se serializar em ContentValues
+ *
+ * - fillFromCursor(Cursor): define que uma entidade do ContentResolver
+ * deve saber se desserializar a partir de um Cursor.
+ *
+ * @param
+ * Recomenda-se utilizar o {@link ContentValuesBuilder} para auxiliar este trabalho.
+ *
+ * @param onlyNonNull {@code true} se devem ser serializados apenas os atributos não-nulos
+ * @return um {@link ContentValues} com os valores deste objeto
+ */
+ @NonNull
+ ContentValues toContentValues(boolean onlyNonNull);
+
+ /**
+ * A entidade que implementa este método deve se desserializar a partir de um {@link Cursor}
+ * e retornar uma auto-referência:
+ *
+ *
+ * Deve-se ainda ficar atento para que todas as propriedades do objeto desserializado
+ * sejam refletidas. Ou seja, atribuir null a todos os atributos que não obtiverem valor
+ * a partir do cursor.
+ *
+ * Recomenda-se utilizar o {@link CursorReader} para auxiliar este trabalho.
+ *
+ * @param cursor um Cursor
+ * @return uma auto-referência ao objeto
+ */
+ T fillFromCursor(@NonNull Cursor cursor);
+}
diff --git a/commons-android/src/main/java/net/hugonardo/android/commons/content/ContentValuesBuilder.java b/commons-android/src/main/java/net/hugonardo/android/commons/content/ContentValuesBuilder.java
new file mode 100644
index 00000000..b4328df8
--- /dev/null
+++ b/commons-android/src/main/java/net/hugonardo/android/commons/content/ContentValuesBuilder.java
@@ -0,0 +1,108 @@
+package net.hugonardo.android.commons.content;
+
+import android.content.ContentValues;
+import android.support.annotation.NonNull;
+
+import java.util.Calendar;
+import java.util.Date;
+
+public class ContentValuesBuilder {
+
+ private final ContentValues mValues;
+ private final boolean mOnlyNonNull;
+
+ public static ContentValuesBuilder create(boolean onlyNonNull) {
+ return new ContentValuesBuilder(onlyNonNull);
+ }
+
+ public static ContentValuesBuilder create(@NonNull ContentValues initialValues, boolean onlyNonNull) {
+ return new ContentValuesBuilder(initialValues, onlyNonNull);
+ }
+
+ private ContentValuesBuilder(boolean onlyNonNull) {
+ this(new ContentValues(), onlyNonNull);
+ }
+
+ private ContentValuesBuilder(@NonNull ContentValues initialValues, boolean onlyNonNull) {
+ mValues = initialValues;
+ mOnlyNonNull = onlyNonNull;
+ }
+
+ public ContentValuesBuilder put(String key, String value) {
+ if (isToPut(value)) {
+ mValues.put(key, value);
+ }
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Long value) {
+ if (isToPut(value)) {
+ mValues.put(key, value);
+ }
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Boolean value) {
+ if (isToPut(value)) {
+ mValues.put(key, value);
+ }
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Date value) {
+ if (isToPut(value)) {
+ Long timeMillis = value != null ? value.getTime() : null;
+ mValues.put(key, timeMillis);
+ }
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Calendar value) {
+ if (isToPut(value)) {
+ Long timeInMillis = value != null ? value.getTimeInMillis() : null;
+ mValues.put(key, timeInMillis);
+ }
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Integer value) {
+ if (isToPut(value)) {
+ mValues.put(key, value);
+ }
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Double value) {
+ if (isToPut(value)) {
+ mValues.put(key, value);
+ }
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Enum value) {
+ if (isToPut(value)) {
+ mValues.put(key, value.name());
+ }
+ return this;
+ }
+
+ public ContentValuesBuilder putNull(String key) {
+ if (isToPut(null)) {
+ mValues.putNull(key);
+ }
+ return this;
+ }
+
+ public ContentValuesBuilder putAll(ContentValues other) {
+ mValues.putAll(other);
+ return this;
+ }
+
+ private boolean isToPut(Object value) {
+ return !mOnlyNonNull || value != null;
+ }
+
+ public ContentValues build() {
+ return mValues;
+ }
+}
diff --git a/commons-android/src/main/java/net/hugonardo/android/commons/content/CursorReader.java b/commons-android/src/main/java/net/hugonardo/android/commons/content/CursorReader.java
new file mode 100644
index 00000000..9d085ae2
--- /dev/null
+++ b/commons-android/src/main/java/net/hugonardo/android/commons/content/CursorReader.java
@@ -0,0 +1,73 @@
+package net.hugonardo.android.commons.content;
+
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.Calendar;
+import java.util.Date;
+
+public class CursorReader {
+
+ @Nullable
+ public static Long readLong(@NonNull Cursor cursor, @NonNull String columnName) {
+ int columnIndex = cursor.getColumnIndex(columnName);
+ return hasValue(cursor, columnIndex)
+ ? cursor.getLong(columnIndex)
+ : null;
+ }
+
+ @Nullable
+ public static String readString(@NonNull Cursor cursor, @NonNull String columnName) {
+ int columnIndex = cursor.getColumnIndex(columnName);
+ return hasValue(cursor, columnIndex)
+ ? cursor.getString(columnIndex)
+ : null;
+ }
+
+ @Nullable
+ public static Date readDateFromMillis(@NonNull Cursor cursor, @NonNull String columnName) {
+ Long timeInMillis = readLong(cursor, columnName);
+ return timeInMillis != null
+ ? new Date(timeInMillis)
+ : null;
+ }
+
+ @Nullable
+ public static Calendar readCalendarFromMillis(@NonNull Cursor cursor, @NonNull String columnName) {
+ Long timeInMillis = readLong(cursor, columnName);
+ if (timeInMillis == null) return null;
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(timeInMillis);
+ return calendar;
+ }
+
+ @Nullable
+ public static Boolean readBoolean(@NonNull Cursor cursor, @NonNull String columnName) {
+ Long longValue = readLong(cursor, columnName);
+ return longValue != null
+ ? longValue.equals(1L)
+ : null;
+ }
+
+ @Nullable
+ public static Integer readInteger(@NonNull Cursor cursor, @NonNull String columnName) {
+ int columnIndex = cursor.getColumnIndex(columnName);
+ return hasValue(cursor, columnIndex)
+ ? cursor.getInt(columnIndex)
+ : null;
+ }
+
+ @Nullable
+ public static Double readDouble(@NonNull Cursor cursor, @NonNull String columnName) {
+ int columnIndex = cursor.getColumnIndex(columnName);
+ return hasValue(cursor, columnIndex)
+ ? cursor.getDouble(columnIndex)
+ : null;
+ }
+
+ private static boolean hasValue(Cursor cursor, int columnIndex) {
+ return (columnIndex > -1) && !cursor.isNull(columnIndex);
+ }
+}
diff --git a/commons-android/src/main/java/net/hugonardo/android/commons/content/UriUtils.java b/commons-android/src/main/java/net/hugonardo/android/commons/content/UriUtils.java
new file mode 100644
index 00000000..c0175a48
--- /dev/null
+++ b/commons-android/src/main/java/net/hugonardo/android/commons/content/UriUtils.java
@@ -0,0 +1,9 @@
+package net.hugonardo.android.commons.content;
+
+import android.net.Uri;
+
+public class UriUtils {
+ public static Uri contentAuthority(String authority) {
+ return Uri.parse("content://" + authority);
+ }
+}
diff --git a/commons-android/src/main/java/net/hugonardo/android/commons/network/LiveNetworkMonitor.java b/commons-android/src/main/java/net/hugonardo/android/commons/network/LiveNetworkMonitor.java
new file mode 100644
index 00000000..c098e1bf
--- /dev/null
+++ b/commons-android/src/main/java/net/hugonardo/android/commons/network/LiveNetworkMonitor.java
@@ -0,0 +1,25 @@
+package net.hugonardo.android.commons.network;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+import net.hugonardo.java.commons.network.NetworkMonitor;
+
+public class LiveNetworkMonitor implements NetworkMonitor {
+
+ private final Context mApplicationContext;
+
+ public LiveNetworkMonitor(Context context) {
+ mApplicationContext = context.getApplicationContext();
+ }
+
+ public boolean isConnected() {
+ ConnectivityManager cm =
+ (ConnectivityManager) mApplicationContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
+ return activeNetwork != null &&
+ activeNetwork.isConnectedOrConnecting();
+ }
+}
\ No newline at end of file
diff --git a/commons-android/src/main/java/net/hugonardo/android/commons/os/ProcessUtils.java b/commons-android/src/main/java/net/hugonardo/android/commons/os/ProcessUtils.java
new file mode 100644
index 00000000..d1fb9649
--- /dev/null
+++ b/commons-android/src/main/java/net/hugonardo/android/commons/os/ProcessUtils.java
@@ -0,0 +1,25 @@
+package net.hugonardo.android.commons.os;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import static net.hugonardo.java.commons.text.StringUtils.EMPTY;
+
+public class ProcessUtils {
+
+ @NonNull public static String getCurrentProcessName(@NonNull Context context) {
+ String processName = EMPTY;
+ int pid = android.os.Process.myPid();
+ ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ if (manager != null) {
+ for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {
+ if (processInfo.pid == pid) {
+ processName = processInfo.processName;
+ break;
+ }
+ }
+ }
+ return processName;
+ }
+}
diff --git a/commons-android/src/main/java/net/hugonardo/android/commons/recyclerview/AdapterUtils.java b/commons-android/src/main/java/net/hugonardo/android/commons/recyclerview/AdapterUtils.java
new file mode 100644
index 00000000..74c115b6
--- /dev/null
+++ b/commons-android/src/main/java/net/hugonardo/android/commons/recyclerview/AdapterUtils.java
@@ -0,0 +1,21 @@
+package net.hugonardo.android.commons.recyclerview;
+
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+
+import com.jude.easyrecyclerview.adapter.RecyclerArrayAdapter;
+
+public class AdapterUtils {
+
+ public static boolean isNotEmpty(@Nullable RecyclerView.Adapter adapter) {
+ return count(adapter) > 0;
+ }
+
+ public static int count(@Nullable RecyclerView.Adapter adapter) {
+ return adapter == null
+ ? 0
+ : adapter instanceof RecyclerArrayAdapter
+ ? ((RecyclerArrayAdapter) adapter).getCount()
+ : adapter.getItemCount();
+ }
+}
diff --git a/commons-android/src/main/java/net/hugonardo/android/commons/recyclerview/EasyRecyclerViewUtils.java b/commons-android/src/main/java/net/hugonardo/android/commons/recyclerview/EasyRecyclerViewUtils.java
new file mode 100644
index 00000000..8048db22
--- /dev/null
+++ b/commons-android/src/main/java/net/hugonardo/android/commons/recyclerview/EasyRecyclerViewUtils.java
@@ -0,0 +1,52 @@
+package net.hugonardo.android.commons.recyclerview;
+
+import android.support.annotation.Nullable;
+
+import com.jude.easyrecyclerview.EasyRecyclerView;
+
+public class EasyRecyclerViewUtils {
+
+ private static boolean isListShown(@Nullable EasyRecyclerView recyclerView) {
+ return recyclerView != null && recyclerView.getRecyclerView() != null
+ && recyclerView.getRecyclerView().isShown();
+ }
+
+ private static boolean isProgressShown(@Nullable EasyRecyclerView recyclerView) {
+ return recyclerView != null && recyclerView.getRecyclerView() != null
+ && recyclerView.getProgressView().isShown();
+ }
+
+ private static boolean isEmptyShown(@Nullable EasyRecyclerView recyclerView) {
+ return recyclerView != null && recyclerView.getRecyclerView() != null
+ && recyclerView.getEmptyView().isShown();
+ }
+
+ private static boolean isErrorShown(@Nullable EasyRecyclerView recyclerView) {
+ return recyclerView != null && recyclerView.getRecyclerView() != null
+ && recyclerView.getErrorView().isShown();
+ }
+
+ public static void showList(@Nullable EasyRecyclerView recyclerView) {
+ if (recyclerView != null && !isListShown(recyclerView)) {
+ recyclerView.showRecycler();
+ }
+ }
+
+ public static void showProgress(@Nullable EasyRecyclerView recyclerView) {
+ if (recyclerView != null && !isProgressShown(recyclerView)) {
+ recyclerView.showProgress();
+ }
+ }
+
+ public static void showEmpty(@Nullable EasyRecyclerView recyclerView) {
+ if (recyclerView != null && !isEmptyShown(recyclerView)) {
+ recyclerView.showEmpty();
+ }
+ }
+
+ public static void showError(@Nullable EasyRecyclerView recyclerView) {
+ if (recyclerView != null && !isErrorShown(recyclerView)) {
+ recyclerView.showError();
+ }
+ }
+}
diff --git a/commons-android/src/main/java/net/hugonardo/android/commons/sync/SyncResultUtil.java b/commons-android/src/main/java/net/hugonardo/android/commons/sync/SyncResultUtil.java
new file mode 100644
index 00000000..3163a9d1
--- /dev/null
+++ b/commons-android/src/main/java/net/hugonardo/android/commons/sync/SyncResultUtil.java
@@ -0,0 +1,69 @@
+package net.hugonardo.android.commons.sync;
+
+import android.content.SyncResult;
+import android.content.SyncStats;
+import android.support.annotation.NonNull;
+
+public final class SyncResultUtil {
+
+ /**
+ * Soma os dados de {@code source} em {@code target}.
+ *
+ * Os dados de {@code source} não serão alterados. {@code target} terá seus valores incrementados em seus
+ * correspondentes de source.
+ *
+ * @param target para onde os dados serão somados
+ * @param source de onde os dados serão obtidos
+ * @return o target
+ */
+ public static SyncResult sum(@NonNull SyncResult target, @NonNull SyncResult source) {
+ SyncStats targetStats = target.stats;
+ SyncStats sourceStats = source.stats;
+ sumParseExceptions(targetStats, sourceStats);
+ sumAuthExceptions(targetStats, sourceStats);
+ sumConflictDetectedExceptions(targetStats, sourceStats);
+ sumIoExceptions(targetStats, sourceStats);
+ sumSkippedEntries(targetStats, sourceStats);
+ sumEntries(targetStats, sourceStats);
+ sumInserts(targetStats, sourceStats);
+ sumUpdates(targetStats, sourceStats);
+ sumDeletes(targetStats, sourceStats);
+ return target;
+ }
+
+ private static void sumParseExceptions(SyncStats targetStats, SyncStats sourceStats) {
+ targetStats.numParseExceptions += sourceStats.numParseExceptions;
+ }
+
+ private static void sumAuthExceptions(SyncStats targetStats, SyncStats sourceStats) {
+ targetStats.numAuthExceptions += sourceStats.numAuthExceptions;
+ }
+
+ private static void sumConflictDetectedExceptions(SyncStats targetStats, SyncStats sourceStats) {
+ targetStats.numConflictDetectedExceptions += sourceStats.numConflictDetectedExceptions;
+ }
+
+ private static void sumIoExceptions(SyncStats targetStats, SyncStats sourceStats) {
+ targetStats.numIoExceptions += sourceStats.numIoExceptions;
+ }
+
+ private static void sumSkippedEntries(SyncStats targetStats, SyncStats sourceStats) {
+ targetStats.numSkippedEntries += sourceStats.numSkippedEntries;
+ }
+
+ private static void sumEntries(SyncStats targetStats, SyncStats sourceStats) {
+ targetStats.numEntries += sourceStats.numEntries;
+ }
+
+ private static void sumInserts(SyncStats targetStats, SyncStats sourceStats) {
+ targetStats.numInserts += sourceStats.numInserts;
+ }
+
+ private static void sumUpdates(SyncStats targetStats, SyncStats sourceStats) {
+ targetStats.numUpdates += sourceStats.numUpdates;
+ }
+
+ private static void sumDeletes(SyncStats targetStats, SyncStats sourceStats) {
+ targetStats.numDeletes += sourceStats.numDeletes;
+ }
+}
diff --git a/commons-android/src/main/java/net/hugonardo/android/commons/text/TextNormalizer.java b/commons-android/src/main/java/net/hugonardo/android/commons/text/TextNormalizer.java
new file mode 100644
index 00000000..47a21fa9
--- /dev/null
+++ b/commons-android/src/main/java/net/hugonardo/android/commons/text/TextNormalizer.java
@@ -0,0 +1,48 @@
+package net.hugonardo.android.commons.text;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import java.text.Normalizer;
+
+public class TextNormalizer {
+
+ public static String removeAccents(String input) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+ return removeAccentsWithNormalizer(input);
+ } else {
+ return removeAccentsCompat(input);
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.GINGERBREAD)
+ private static String removeAccentsWithNormalizer(String input) {
+ return Normalizer.normalize(input, Normalizer.Form.NFD)
+ .replaceAll("[^\\p{ASCII}]", "");
+ }
+
+ private static String removeAccentsCompat(String input) {
+ input = input.replaceAll("[àáâäã]","a");
+ input = input.replaceAll("[ÀÁÂÄÃ]","A");
+
+ input = input.replaceAll("[èéêë]","e");
+ input = input.replaceAll("[ÈÉÊË]","E");
+
+ input = input.replaceAll("[ïîíì]","i");
+ input = input.replaceAll("[ÏÎÍÌ]","I");
+
+ input = input.replaceAll("[òóôöõ]","o");
+ input = input.replaceAll("[ÒÓÔÖÕ]","O");
+
+ input = input.replaceAll("[ùúûü]","u");
+ input = input.replaceAll("[ÙÚÛÜ]","U");
+
+ input = input.replaceAll("[ç]","c");
+ input = input.replaceAll("[Ç]","C");
+
+ input = input.replaceAll("[ñ]","n");
+ input = input.replaceAll("[Ñ]","N");
+
+ return input;
+ }
+}
diff --git a/commons-android/src/main/java/net/hugonardo/android/commons/ui/UiUtils.java b/commons-android/src/main/java/net/hugonardo/android/commons/ui/UiUtils.java
new file mode 100644
index 00000000..e4fd503f
--- /dev/null
+++ b/commons-android/src/main/java/net/hugonardo/android/commons/ui/UiUtils.java
@@ -0,0 +1,48 @@
+package net.hugonardo.android.commons.ui;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Context;
+import android.os.IBinder;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+
+public class UiUtils {
+
+ public static void hideKeyboard(Activity activity, int flags) {
+ InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ View viewWithFocus = activity.getCurrentFocus();
+ IBinder windowToken = viewWithFocus != null ? viewWithFocus.getWindowToken() : null;
+
+ if (windowToken != null) {
+ imm.hideSoftInputFromWindow(windowToken, flags);
+ }
+ }
+
+ public static void hideKeyboard(Activity activity) {
+ hideKeyboard(activity, InputMethodManager.HIDE_NOT_ALWAYS);
+ }
+
+ public static void hideKeyboard(Fragment fragment, int flags) {
+ Activity activity = fragment.getActivity();
+ if (activity != null) {
+ hideKeyboard(activity, flags);
+ }
+ }
+
+ public static void hideKeyboard(Fragment fragment) {
+ hideKeyboard(fragment, InputMethodManager.HIDE_NOT_ALWAYS);
+ }
+
+ public static void hideKeyboard(android.support.v4.app.Fragment fragment, int flags) {
+ Activity activity = fragment.getActivity();
+ if (activity != null) {
+ hideKeyboard(activity, flags);
+ }
+ }
+
+ public static void hideKeyboard(android.support.v4.app.Fragment fragment) {
+ hideKeyboard(fragment, InputMethodManager.HIDE_NOT_ALWAYS);
+ }
+}
diff --git a/commons-android/src/test/java/net/hugonardo/android/commons/RobolectricTest.java b/commons-android/src/test/java/net/hugonardo/android/commons/RobolectricTest.java
new file mode 100644
index 00000000..3e82c04a
--- /dev/null
+++ b/commons-android/src/test/java/net/hugonardo/android/commons/RobolectricTest.java
@@ -0,0 +1,9 @@
+package net.hugonardo.android.commons;
+
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(constants = BuildConfig.class)
+public abstract class RobolectricTest {}
diff --git a/commons-android/src/test/java/net/hugonardo/android/commons/sync/SyncResultUtilRobolectricTest.java b/commons-android/src/test/java/net/hugonardo/android/commons/sync/SyncResultUtilRobolectricTest.java
new file mode 100644
index 00000000..87fdbfcc
--- /dev/null
+++ b/commons-android/src/test/java/net/hugonardo/android/commons/sync/SyncResultUtilRobolectricTest.java
@@ -0,0 +1,120 @@
+package net.hugonardo.android.commons.sync;
+
+import android.content.SyncResult;
+import android.content.SyncStats;
+
+import net.hugonardo.android.commons.RobolectricTest;
+
+import org.junit.Test;
+
+import static net.hugonardo.android.commons.sync.SyncResultUtil.sum;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class SyncResultUtilRobolectricTest extends RobolectricTest {
+
+ private final SyncResult target = new SyncResult();
+ private final SyncResult source = new SyncResult();
+
+ private final SyncStats targetStats = target.stats;
+ private final SyncStats sourceStats = source.stats;
+
+ @Test public void sumParseExceptions() throws Exception {
+ sourceStats.numParseExceptions = 5;
+ sum(target, source);
+ assertThat(targetStats.numParseExceptions, is(5L));
+
+ targetStats.numParseExceptions = 4;
+ sourceStats.numParseExceptions = 2;
+ sum(target, source);
+ assertThat(targetStats.numParseExceptions, is(6L));
+ }
+
+ @Test public void sumAuthExceptions() throws Exception {
+ sourceStats.numAuthExceptions = 5;
+ sum(target, source);
+ assertThat(targetStats.numAuthExceptions, is(5L));
+
+ targetStats.numAuthExceptions = 4;
+ sourceStats.numAuthExceptions = 2;
+ sum(target, source);
+ assertThat(targetStats.numAuthExceptions, is(6L));
+ }
+
+ @Test public void sumConflictDetectedExceptions() throws Exception {
+ sourceStats.numConflictDetectedExceptions = 5;
+ sum(target, source);
+ assertThat(targetStats.numConflictDetectedExceptions, is(5L));
+
+ targetStats.numConflictDetectedExceptions = 4;
+ sourceStats.numConflictDetectedExceptions = 2;
+ sum(target, source);
+ assertThat(targetStats.numConflictDetectedExceptions, is(6L));
+ }
+
+ @Test public void sumIoExceptions() throws Exception {
+ sourceStats.numIoExceptions = 5;
+ sum(target, source);
+ assertThat(targetStats.numIoExceptions, is(5L));
+
+ targetStats.numIoExceptions = 4;
+ sourceStats.numIoExceptions = 2;
+ sum(target, source);
+ assertThat(targetStats.numIoExceptions, is(6L));
+ }
+
+ @Test public void sumSkippedEntries() throws Exception {
+ sourceStats.numSkippedEntries = 5;
+ sum(target, source);
+ assertThat(targetStats.numSkippedEntries, is(5L));
+
+ targetStats.numSkippedEntries = 4;
+ sourceStats.numSkippedEntries = 2;
+ sum(target, source);
+ assertThat(targetStats.numSkippedEntries, is(6L));
+ }
+
+ @Test public void sumEntries() throws Exception {
+ sourceStats.numEntries = 5;
+ sum(target, source);
+ assertThat(targetStats.numEntries, is(5L));
+
+ targetStats.numEntries = 4;
+ sourceStats.numEntries = 2;
+ sum(target, source);
+ assertThat(targetStats.numEntries, is(6L));
+ }
+
+ @Test public void sumInserts() throws Exception {
+ sourceStats.numInserts = 5;
+ sum(target, source);
+ assertThat(targetStats.numInserts, is(5L));
+
+ targetStats.numInserts = 4;
+ sourceStats.numInserts = 2;
+ sum(target, source);
+ assertThat(targetStats.numInserts, is(6L));
+ }
+
+ @Test public void sumUpdates() throws Exception {
+ sourceStats.numUpdates = 5;
+ sum(target, source);
+ assertThat(targetStats.numUpdates, is(5L));
+
+ targetStats.numUpdates = 4;
+ sourceStats.numUpdates = 2;
+ sum(target, source);
+ assertThat(targetStats.numUpdates, is(6L));
+ }
+
+ @Test public void sumDeletes() throws Exception {
+ sourceStats.numDeletes = 5;
+ sum(target, source);
+ assertThat(targetStats.numDeletes, is(5L));
+
+ targetStats.numDeletes = 4;
+ sourceStats.numDeletes = 2;
+ sum(target, source);
+ assertThat(targetStats.numDeletes, is(6L));
+ }
+}
\ No newline at end of file
diff --git a/commons-android/src/test/resources/robolectric.properties b/commons-android/src/test/resources/robolectric.properties
new file mode 100644
index 00000000..d1b5f601
--- /dev/null
+++ b/commons-android/src/test/resources/robolectric.properties
@@ -0,0 +1 @@
+sdk = 25
\ No newline at end of file
diff --git a/commons-java/README.md b/commons-java/README.md
new file mode 100644
index 00000000..4ee923c0
--- /dev/null
+++ b/commons-java/README.md
@@ -0,0 +1 @@
+Biblioteca pessoal com algumas funções comuns e úteis, que não dependem do Android Framework.
\ No newline at end of file
diff --git a/commons-java/build.gradle b/commons-java/build.gradle
new file mode 100644
index 00000000..5e8de4e7
--- /dev/null
+++ b/commons-java/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'java'
+
+dependencies {
+ implementation "com.google.code.gson:gson:${gson_version}"
+
+ testImplementation("junit:junit:${junit_version}", { exclude group: 'org.hamcrest' })
+ testImplementation "org.hamcrest:hamcrest-junit:${hamcrest_version}"
+ testImplementation "org.mockito:mockito-core:${mockito_version}"
+}
+
+sourceCompatibility = "${java_source_compatibility}"
+targetCompatibility = "${java_target_compatibility}"
diff --git a/commons-java/src/main/java/net/hugonardo/java/commons/Objects.java b/commons-java/src/main/java/net/hugonardo/java/commons/Objects.java
new file mode 100644
index 00000000..2dc3117d
--- /dev/null
+++ b/commons-java/src/main/java/net/hugonardo/java/commons/Objects.java
@@ -0,0 +1,30 @@
+package net.hugonardo.java.commons;
+
+import com.google.gson.GsonBuilder;
+
+public class Objects {
+
+ public static boolean equals(Object var0, Object var1) {
+ return var0 == var1 || var0 != null && var0.equals(var1);
+ }
+
+ public static boolean notEquals(Object var0, Object var1) {
+ return !equals(var0, var1);
+ }
+
+ public static boolean isNull(Object value) {
+ return value == null;
+ }
+
+ public static boolean isNotNull(Object value) {
+ return !isNull(value);
+ }
+
+ public static String toString(Object o) {
+ String prefix = o.getClass().getSimpleName() + ": ";
+ return prefix + new GsonBuilder()
+ .serializeNulls()
+ .create()
+ .toJson(o);
+ }
+}
diff --git a/commons-java/src/main/java/net/hugonardo/java/commons/collections/Collections.java b/commons-java/src/main/java/net/hugonardo/java/commons/collections/Collections.java
new file mode 100644
index 00000000..df7dd6f7
--- /dev/null
+++ b/commons-java/src/main/java/net/hugonardo/java/commons/collections/Collections.java
@@ -0,0 +1,31 @@
+package net.hugonardo.java.commons.collections;
+
+import java.util.Collection;
+
+public final class Collections {
+
+ public static boolean isEmpty(Collection collection) {
+ return collection == null || collection.isEmpty();
+ }
+
+ public static boolean isNotEmpty(Collection collection) {
+ return !isEmpty(collection);
+ }
+
+ public static Operations on {@link CharSequence} that are
+ * {@code null} safe. {@code CharSequenceUtils} instances should NOT be constructed in
+ * standard programming. This constructor is public to permit tools that require a JavaBean
+ * instance to operate. Returns a new {@code CharSequence} that is a subsequence of this
+ * sequence starting with the {@code char} value at the specified index. This provides the {@code CharSequence} equivalent to {@link String#substring(int)}.
+ * The length (in {@code char}) of the returned sequence is {@code length() - start},
+ * so if {@code start == end} then an empty sequence is returned. Finds the first index in the {@code CharSequence} that matches the
+ * specified character. Finds the last index in the {@code CharSequence} that matches the
+ * specified character. Operations on {@link String} that are
+ * {@code null} safe. The {@code StringUtils} class defines certain words related to
+ * String handling. {@code StringUtils} handles {@code null} input Strings quietly.
+ * That is to say that a {@code null} input will return {@code null}.
+ * Where a {@code boolean} or {@code int} is being returned
+ * details vary by method. A side effect of the {@code null} handling is that a
+ * {@code NullPointerException} should be considered a bug in
+ * {@code StringUtils}. Methods in this class give sample code to explain their operation.
+ * The symbol {@code *} is used to indicate any input including {@code null}. #ThreadSafe# The maximum size to which the padding constant(s) can expand. {@code StringUtils} instances should NOT be constructed in
+ * standard programming. Instead, the class should be used as
+ * {@code StringUtils.trim(" foo ");}. This constructor is public to permit tools that require a JavaBean
+ * instance to operate. Checks if a CharSequence is empty ("") or null. NOTE: This method changed in Lang version 2.0.
+ * It no longer trims the CharSequence.
+ * That functionality is available in isBlank(). Checks if a CharSequence is not empty ("") and not null. Checks if a CharSequence is whitespace, empty ("") or null. Checks if a CharSequence is not empty (""), not null and not whitespace only. Compares two CharSequences, returning {@code true} if they represent
+ * equal sequences of characters. {@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is case sensitive. Compares two CharSequences, returning {@code true} if they represent
+ * equal sequences of characters, ignoring case. {@code null}s are handled without exceptions. Two {@code null}
+ * references are considered equal. Comparison is case insensitive. Removes control characters (char <= 32) from both
+ * ends of this String, handling {@code null} by returning
+ * {@code null}. The String is trimmed using {@link String#trim()}.
+ * Trim removes start and end characters <= 32.
+ * To strip whitespace use {@link #strip(String)}. To trim your choice of characters, use the
+ * {@link #strip(String, String)} methods. Removes control characters (char <= 32) from both
+ * ends of this String returning {@code null} if the String is
+ * empty ("") after the trim or if it is {@code null}.
+ *
+ * The String is trimmed using {@link String#trim()}.
+ * Trim removes start and end characters <= 32.
+ * To strip whitespace use {@link #stripToNull(String)}. Removes control characters (char <= 32) from both
+ * ends of this String returning an empty String ("") if the String
+ * is empty ("") after the trim or if it is {@code null}.
+ *
+ * The String is trimmed using {@link String#trim()}.
+ * Trim removes start and end characters <= 32.
+ * To strip whitespace use {@link #stripToEmpty(String)}. Strips whitespace from the start and end of a String. This is similar to {@link #trim(String)} but removes whitespace.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}. A {@code null} input String returns {@code null}. Strips whitespace from the start and end of a String returning
+ * {@code null} if the String is empty ("") after the strip. This is similar to {@link #trimToNull(String)} but removes whitespace.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}. Strips whitespace from the start and end of a String returning
+ * an empty String if {@code null} input. This is similar to {@link #trimToEmpty(String)} but removes whitespace.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}. Strips any of a set of characters from the start and end of a String.
+ * This is similar to {@link String#trim()} but allows the characters
+ * to be stripped to be controlled. A {@code null} input String returns {@code null}.
+ * An empty string ("") input returns the empty string. If the stripChars String is {@code null}, whitespace is
+ * stripped as defined by {@link Character#isWhitespace(char)}.
+ * Alternatively use {@link #strip(String)}. Strips any of a set of characters from the start of a String. A {@code null} input String returns {@code null}.
+ * An empty string ("") input returns the empty string. If the stripChars String is {@code null}, whitespace is
+ * stripped as defined by {@link Character#isWhitespace(char)}. Strips any of a set of characters from the end of a String. A {@code null} input String returns {@code null}.
+ * An empty string ("") input returns the empty string. If the stripChars String is {@code null}, whitespace is
+ * stripped as defined by {@link Character#isWhitespace(char)}. Strips whitespace from the start and end of every String in an array.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}. A new array is returned each time, except for length zero.
+ * A {@code null} array will return {@code null}.
+ * An empty array will return itself.
+ * A {@code null} array entry will be ignored. Strips any of a set of characters from the start and end of every
+ * String in an array. Whitespace is defined by {@link Character#isWhitespace(char)}. A new array is returned each time, except for length zero.
+ * A {@code null} array will return {@code null}.
+ * An empty array will return itself.
+ * A {@code null} array entry will be ignored.
+ * A {@code null} stripChars will strip whitespace as defined by
+ * {@link Character#isWhitespace(char)}. Removes diacritics (~= accents) from a string. The case will not be altered. For instance, 'à' will be replaced by 'a'. Note that ligatures will be left as is. Compare two Strings lexicographically, as per {@link String#compareTo(String)}, returning : This is a {@code null} safe version of : {@code null} value is considered less than non-{@code null} value.
+ * Two {@code null} references are considered equal. Compare two Strings lexicographically, as per {@link String#compareTo(String)}, returning : This is a {@code null} safe version of : {@code null} inputs are handled according to the {@code nullIsLess} parameter.
+ * Two {@code null} references are considered equal. Compare two Strings lexicographically, ignoring case differences,
+ * as per {@link String#compareToIgnoreCase(String)}, returning : This is a {@code null} safe version of : {@code null} value is considered less than non-{@code null} value.
+ * Two {@code null} references are considered equal.
+ * Comparison is case insensitive. Compare two Strings lexicographically, ignoring case differences,
+ * as per {@link String#compareToIgnoreCase(String)}, returning : This is a {@code null} safe version of : {@code null} inputs are handled according to the {@code nullIsLess} parameter.
+ * Two {@code null} references are considered equal.
+ * Comparison is case insensitive. Checks that the CharSequence does not contain certain characters. A {@code null} CharSequence will return {@code true}.
+ * A {@code null} invalid character array will return {@code true}.
+ * An empty CharSequence (length()=0) always returns true. Checks that the CharSequence does not contain certain characters. A {@code null} CharSequence will return {@code true}.
+ * A {@code null} invalid character array will return {@code true}.
+ * An empty String ("") always returns true. Gets a substring from the specified String avoiding exceptions. A negative start position can be used to start {@code n}
+ * characters from the end of the String. A {@code null} String will return {@code null}.
+ * An empty ("") String will return "". Gets a substring from the specified String avoiding exceptions. A negative start position can be used to start/end {@code n}
+ * characters from the end of the String. The returned substring starts with the character in the {@code start}
+ * position and ends before the {@code end} position. All position counting is
+ * zero-based -- i.e., to start at the beginning of the string use
+ * {@code start = 0}. Negative start and end positions can be used to
+ * specify offsets relative to the end of the String. If {@code start} is not strictly to the left of {@code end}, ""
+ * is returned. Gets the leftmost {@code len} characters of a String. If {@code len} characters are not available, or the
+ * String is {@code null}, the String will be returned without
+ * an exception. An empty String is returned if len is negative. Gets the rightmost {@code len} characters of a String. If {@code len} characters are not available, or the String
+ * is {@code null}, the String will be returned without an
+ * an exception. An empty String is returned if len is negative. Gets {@code len} characters from the middle of a String. If {@code len} characters are not available, the remainder
+ * of the String will be returned without an exception. If the
+ * String is {@code null}, {@code null} will be returned.
+ * An empty String is returned if len is negative or exceeds the
+ * length of {@code str}. Gets the substring before the first occurrence of a separator.
+ * The separator is not returned. A {@code null} string input will return {@code null}.
+ * An empty ("") string input will return the empty string.
+ * A {@code null} separator will return the input string. If nothing is found, the string input is returned. Gets the substring after the first occurrence of a separator.
+ * The separator is not returned. A {@code null} string input will return {@code null}.
+ * An empty ("") string input will return the empty string.
+ * A {@code null} separator will return the empty string if the
+ * input string is not {@code null}. If nothing is found, the empty string is returned. Gets the substring before the last occurrence of a separator.
+ * The separator is not returned. A {@code null} string input will return {@code null}.
+ * An empty ("") string input will return the empty string.
+ * An empty or {@code null} separator will return the input string. If nothing is found, the string input is returned. Gets the substring after the last occurrence of a separator.
+ * The separator is not returned. A {@code null} string input will return {@code null}.
+ * An empty ("") string input will return the empty string.
+ * An empty or {@code null} separator will return the empty string if
+ * the input string is not {@code null}. If nothing is found, the empty string is returned. Gets the String that is nested in between two instances of the
+ * same String. A {@code null} input String returns {@code null}.
+ * A {@code null} tag returns {@code null}. Gets the String that is nested in between two Strings.
+ * Only the first match is returned. A {@code null} input String returns {@code null}.
+ * A {@code null} open/close returns {@code null} (no match).
+ * An empty ("") open and close returns an empty string. Joins the elements of the provided array into a single String
+ * containing the provided list of elements. No separator is added to the joined String.
+ * Null objects or empty strings within the array are represented by
+ * empty strings.
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ *
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ *
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ *
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ *
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ *
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ *
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ *
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ *
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ *
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ *
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ *
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ *
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ *
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * Joins the elements of the provided array into a single String
+ * containing the provided list of elements. No delimiter is added before or after the list.
+ * A {@code null} separator is the same as an empty String ("").
+ * Null objects or empty strings within the array are represented by
+ * empty strings. Joins the elements of the provided array into a single String
+ * containing the provided list of elements. No delimiter is added before or after the list.
+ * A {@code null} separator is the same as an empty String ("").
+ * Null objects or empty strings within the array are represented by
+ * empty strings. Deletes all whitespaces from a String as defined by
+ * {@link Character#isWhitespace(char)}. Removes a substring only if it is at the beginning of a source string,
+ * otherwise returns the source string. A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ * A {@code null} search string will return the source string. Removes a substring only if it is at the end of a source string,
+ * otherwise returns the source string. A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ * A {@code null} search string will return the source string. Removes all occurrences of a substring from within the source string. A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ * A {@code null} remove string will return the source string.
+ * An empty ("") remove string will return the source string. Removes all occurrences of a character from within the source string. A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string. Removes each substring of the text String that matches the given regular expression. A {@code null} reference passed to this method is a no-op. Unlike in the {@link #removePattern(String, String)} method, the {@link Pattern#DOTALL} option
+ * is NOT automatically added.
+ * To use the DOTALL option prepend Removes the first substring of the text string that matches the given regular expression. A {@code null} reference passed to this method is a no-op. The {@link Pattern#DOTALL} option is NOT automatically added.
+ * To use the DOTALL option prepend Replaces a String with another String inside a larger String, once. A {@code null} reference passed to this method is a no-op. Replaces each substring of the source String that matches the given regular expression with the given
+ * replacement using the {@link Pattern#DOTALL} option. DOTALL is also know as single-line mode in Perl. A {@code null} reference passed to this method is a no-op. Removes each substring of the source String that matches the given regular expression using the DOTALL option.
+ * A {@code null} reference passed to this method is a no-op. Replaces each substring of the text String that matches the given regular expression
+ * with the given replacement. A {@code null} reference passed to this method is a no-op. Unlike in the {@link #replacePattern(String, String, String)} method, the {@link Pattern#DOTALL} option
+ * is NOT automatically added.
+ * To use the DOTALL option prepend Replaces the first substring of the text string that matches the given regular expression
+ * with the given replacement. A {@code null} reference passed to this method is a no-op. The {@link Pattern#DOTALL} option is NOT automatically added.
+ * To use the DOTALL option prepend Replaces all occurrences of a String within another String. A {@code null} reference passed to this method is a no-op. Replaces a String with another String inside a larger String,
+ * for the first {@code max} values of the search String. A {@code null} reference passed to this method is a no-op.
+ * Replaces all occurrences of Strings within another String.
+ *
+ * A {@code null} reference passed to this method is a no-op, or if
+ * any "search string" or "string to replace" is null, that replace will be
+ * ignored. This will not repeat. For repeating replaces, call the
+ * overloaded method.
+ *
+ * Replaces all occurrences of Strings within another String.
+ *
+ * A {@code null} reference passed to this method is a no-op, or if
+ * any "search string" or "string to replace" is null, that replace will be
+ * ignored.
+ *
+ * Replace all occurrences of Strings within another String.
+ * This is a private recursive helper method for {@link #replaceEachRepeatedly(String, String[], String[])} and
+ * {@link #replaceEach(String, String[], String[])}
+ *
+ * A {@code null} reference passed to this method is a no-op, or if
+ * any "search string" or "string to replace" is null, that replace will be
+ * ignored.
+ * Replaces all occurrences of a character in a String with another.
+ * This is a null-safe version of {@link String#replace(char, char)}. A {@code null} string input returns {@code null}.
+ * An empty ("") string input returns an empty string. Replaces multiple characters in a String in one go.
+ * This method can also be used to delete characters. For example: A {@code null} string input returns {@code null}.
+ * An empty ("") string input returns an empty string.
+ * A null or empty set of search characters returns the input string. The length of the search characters should normally equal the length
+ * of the replace characters.
+ * If the search characters is longer, then the extra search characters
+ * are deleted.
+ * If the search characters is shorter, then the extra replace characters
+ * are ignored. Overlays part of a String with another String. A {@code null} string input returns {@code null}.
+ * A negative index is treated as zero.
+ * An index greater than the string length is treated as the string length.
+ * The start index is always the smaller of the two indices. Removes {@code separator} from the end of
+ * {@code str} if it's there, otherwise leave it alone. NOTE: This method changed in version 2.0.
+ * It now more closely matches Perl chomp.
+ * For the previous behavior, use {@link #substringBeforeLast(String, String)}.
+ * This method uses {@link String#endsWith(String)}. Repeat a String {@code repeat} times to form a
+ * new String. Repeat a String {@code repeat} times to form a
+ * new String, with a String separator injected each time. Returns padding using the specified delimiter repeated
+ * to a given length. Note: this method doesn't not support padding with
+ * Unicode Supplementary Characters
+ * as they require a pair of {@code char}s to be represented.
+ * If you are needing to support full I18N of your applications
+ * consider using {@link #repeat(String, int)} instead.
+ * Right pad a String with spaces (' '). The String is padded to the size of {@code size}. Right pad a String with a specified character. The String is padded to the size of {@code size}. Right pad a String with a specified String. The String is padded to the size of {@code size}. Left pad a String with spaces (' '). The String is padded to the size of {@code size}. Left pad a String with a specified character. Pad to a size of {@code size}. Left pad a String with a specified String. Pad to a size of {@code size}. Centers a String in a larger String of size {@code size}
+ * using the space character (' '). If the size is less than the String length, the String is returned.
+ * A {@code null} String returns {@code null}.
+ * A negative size is treated as zero. Equivalent to {@code center(str, size, " ")}. Centers a String in a larger String of size {@code size}.
+ * Uses a supplied character as the value to pad the String with. If the size is less than the String length, the String is returned.
+ * A {@code null} String returns {@code null}.
+ * A negative size is treated as zero. Centers a String in a larger String of size {@code size}.
+ * Uses a supplied String as the value to pad the String with. If the size is less than the String length, the String is returned.
+ * A {@code null} String returns {@code null}.
+ * A negative size is treated as zero. Converts a String to upper case as per {@link String#toUpperCase()}. A {@code null} input String returns {@code null}. Note: As described in the documentation for {@link String#toUpperCase()},
+ * the result of this method is affected by the current locale.
+ * For platform-independent case transformations, the method {@link #lowerCase(String, Locale)}
+ * should be used with a specific locale (e.g. {@link Locale#ENGLISH}). Converts a String to upper case as per {@link String#toUpperCase(Locale)}. A {@code null} input String returns {@code null}. Converts a String to lower case as per {@link String#toLowerCase()}. A {@code null} input String returns {@code null}. Note: As described in the documentation for {@link String#toLowerCase()},
+ * the result of this method is affected by the current locale.
+ * For platform-independent case transformations, the method {@link #lowerCase(String, Locale)}
+ * should be used with a specific locale (e.g. {@link Locale#ENGLISH}). Converts a String to lower case as per {@link String#toLowerCase(Locale)}. A {@code null} input String returns {@code null}. Capitalizes a String changing the first character to title case as
+ * per {@link Character#toTitleCase(char)}. No other characters are changed. Uncapitalizes a String, changing the first character to lower case as
+ * per {@link Character#toLowerCase(char)}. No other characters are changed. Swaps the case of a String changing upper and title case to
+ * lower case, and lower case to upper case. NOTE: This method changed in Lang version 2.0.
+ * It no longer performs a word based algorithm.
+ * If you only use ASCII, you will notice no change.
+ * That functionality is available in org.apache.commons.lang3.text.WordUtils. Counts how many times the char appears in the given string. A {@code null} or empty ("") String input returns {@code 0}. Checks if the CharSequence contains only Unicode letters. {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}. Checks if the CharSequence contains only Unicode letters and
+ * space (' '). {@code null} will return {@code false}
+ * An empty CharSequence (length()=0) will return {@code true}. Checks if the CharSequence contains only Unicode letters or digits. {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}. Checks if the CharSequence contains only Unicode letters, digits
+ * or space ({@code ' '}). {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}. Checks if the CharSequence contains only Unicode digits.
+ * A decimal point is not a Unicode digit and returns false. {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}. Note that the method does not allow for a leading sign, either positive or negative.
+ * Also, if a String passes the numeric test, it may still generate a NumberFormatException
+ * when parsed by Integer.parseInt or Long.parseLong, e.g. if the value is outside the range
+ * for int or long respectively. Checks if the CharSequence contains only Unicode digits or space
+ * ({@code ' '}).
+ * A decimal point is not a Unicode digit and returns false. {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}. Checks if the CharSequence contains only whitespace. {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}. Checks if the CharSequence contains only lowercase characters. {@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}. Checks if the CharSequence contains only uppercase characters. {@code null} will return {@code false}.
+ * An empty String (length()=0) will return {@code false}. Returns either the passed in String,
+ * or if the String is {@code null}, an empty String (""). Returns either the passed in String, or if the String is
+ * {@code null}, the value of {@code defaultStr}. Returns either the passed in CharSequence, or if the CharSequence is
+ * whitespace, empty ("") or {@code null}, the value of {@code defaultStr}. Returns either the passed in CharSequence, or if the CharSequence is
+ * empty or {@code null}, the value of {@code defaultStr}. Rotate (circular shift) a String of {@code shift} characters. Reverses a String as per {@link StringBuilder#reverse()}. A {@code null} String returns {@code null}. Abbreviates a String using ellipses. This will turn
+ * "Now is the time for all good men" into "Now is the time for..." Specifically: Abbreviates a String using ellipses. This will turn
+ * "Now is the time for all good men" into "...is the time for..." Works like {@code abbreviate(String, int)}, but allows you to specify
+ * a "left edge" offset. Note that this left edge is not necessarily going to
+ * be the leftmost character in the result, or the first character following the
+ * ellipses, but it will appear somewhere in the result.
+ *
+ * In no case will it return a String of length greater than
+ * {@code maxWidth}. Abbreviates a String to the length passed, replacing the middle characters with the supplied
+ * replacement String. This abbreviation only occurs if the following criteria is met: Otherwise, the returned String will be the same as the supplied String for abbreviation.
+ * Compares two Strings, and returns the portion where they differ.
+ * More precisely, return the remainder of the second String,
+ * starting from where it's different from the first. This means that
+ * the difference between "abc" and "ab" is the empty String and not "c". For example,
+ * {@code difference("i am a machine", "i am a robot") -> "robot"}. Compares two CharSequences, and returns the index at which the
+ * CharSequences begin to differ. For example,
+ * {@code indexOfDifference("i am a machine", "i am a robot") -> 7} Compares all CharSequences in an array and returns the index at which the
+ * CharSequences begin to differ. For example,
+ * Compares all Strings in an array and returns the initial sequence of
+ * characters that is common to all of them. For example,
+ * Find the Levenshtein distance between two Strings. This is the number of changes needed to change one String into
+ * another, where each change is a single character modification (deletion,
+ * insertion or substitution). The previous implementation of the Levenshtein distance algorithm
+ * was from http://www.merriampark.com/ld.htm Chas Emerick has written an implementation in Java, which avoids an OutOfMemoryError
+ * which can occur when my Java implementation is used with very large strings. Find the Levenshtein distance between two Strings if it's less than or equal to a given
+ * threshold. This is the number of changes needed to change one String into
+ * another, where each change is a single character modification (deletion,
+ * insertion or substitution). This implementation follows from Algorithms on Strings, Trees and Sequences by Dan Gusfield
+ * and Chas Emerick's implementation of the Levenshtein distance algorithm from
+ * http://www.merriampark.com/ld.htm Find the Jaro Winkler Distance which indicates the similarity score between two Strings. The Jaro measure is the weighted sum of percentage of matched characters from each file and transposed characters.
+ * Winkler increased this measure for matching initial characters. This implementation is based on the Jaro Winkler similarity algorithm
+ * from http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance. Find the Fuzzy Distance which indicates the similarity score between two Strings. This string matching algorithm is similar to the algorithms of editors such as Sublime Text,
+ * TextMate, Atom and others. One point is given for every matched character. Subsequent
+ * matches yield two bonus points. A higher score indicates a higher similarity.
+ * Similar to http://www.w3.org/TR/xpath/#function-normalize
+ * -space
+ *
+ * The function returns the argument string with whitespace normalized by using
+ *
+ * Java's regexp pattern \s defines whitespace as [ \t\n\x0B\f\r]
+ *
+ * For reference:
+ * The difference is that Java's whitespace includes vertical tab and form feed, which this functional will also
+ * normalize. Additionally
+ * Wraps a string with a char.
+ *
+ * Wraps a String with another String.
+ *
+ * A {@code null} input String returns {@code null}.
+ *
+ *
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ @Override
+ public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType,
+ String[] requiredFeatures, Bundle options) throws NetworkErrorException {
+ log("FeatherAccountAuthenticator#addAccount()");
+
+ final Intent intent = new Intent(mContext, provideAccountAuthenticatorActivityClass());
+ intent.putExtra(FeatherAccountAuthenticatorActivity.ARG_ACCOUNT_TYPE, accountType);
+ intent.putExtra(FeatherAccountAuthenticatorActivity.ARG_AUTH_TOKEN_TYPE, authTokenType);
+ intent.putExtra(FeatherAccountAuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT, true);
+ intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
+ if (options != null) {
+ intent.putExtra(FeatherAccountAuthenticatorActivity.ARG_ACCOUNT_OPTIONS, options);
+ }
+
+ return makeReturnableBundle(intent);
+ }
+
+ /**
+ * Returns a Bundle that contains the Intent of the activity that can be used to edit the
+ * properties. In order to indicate success the activity should call response.setResult()
+ * with a non-null Bundle.
+ *
+ * @param response used to set the result for the request. If the Constants.INTENT_KEY
+ * is set in the bundle then this response field is to be used for sending future
+ * results if and when the Intent is started.
+ * @param accountType the AccountType whose properties are to be edited.
+ * @return a Bundle containing the result or the Intent to start to continue the request.
+ * If this is null then the request is considered to still be active and the result should
+ * sent later using response.
+ */
+ @Override
+ public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
+ log("FeatherAccountAuthenticator#editProperties()");
+
+ final Intent intent = new Intent(mContext, provideAccountAuthenticatorActivityClass());
+ intent.putExtra(FeatherAccountAuthenticatorActivity.ARG_ACCOUNT_TYPE, accountType);
+ intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
+
+ return makeReturnableBundle(intent);
+ }
+
+ /**
+ * Update the locally stored credentials for an account.
+ *
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account whose credentials are to be updated, will never be null
+ * @param authTokenType the type of auth token to retrieve after updating the credentials,
+ * may be null
+ * @param options a Bundle of authenticator-specific options, may be null
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ *
+ *
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ @Override
+ public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType,
+ Bundle options) throws NetworkErrorException {
+ log("FeatherAccountAuthenticator#updateCredentials()");
+ final Intent intent = new Intent(mContext, provideAccountAuthenticatorActivityClass());
+ intent.putExtra(FeatherAccountAuthenticatorActivity.ARG_ACCOUNT_NAME, account.name);
+ intent.putExtra(FeatherAccountAuthenticatorActivity.ARG_AUTH_TOKEN_TYPE, authTokenType);
+ intent.putExtra(FeatherAccountAuthenticatorActivity.ARG_CONFIRM_CREDENTIALS, false);
+
+ return makeReturnableBundle(intent);
+ }
+
+ /**
+ * Gets an authtoken for an account.
+ *
+ *
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ @Override
+ public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)
+ throws NetworkErrorException {
+ log("FeatherAccountAuthenticator#confirmCredentials()");
+
+ if (options != null && options.containsKey(AccountManager.KEY_PASSWORD)) {
+ final String password = options.getString(AccountManager.KEY_PASSWORD);
+ final boolean verified = confirmPassword(account.name, password);
+ final Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, verified);
+ return result;
+ }
+
+ // Launch AuthenticatorActivity to confirm credentials
+ final Intent intent = new Intent(mContext, provideAccountAuthenticatorActivityClass());
+ intent.putExtra(FeatherAccountAuthenticatorActivity.ARG_ACCOUNT_NAME, account.name);
+ intent.putExtra(FeatherAccountAuthenticatorActivity.ARG_CONFIRM_CREDENTIALS, true);
+ intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(AccountManager.KEY_INTENT, intent);
+ return bundle;
+ }
+
+ /**
+ * Checks if the account supports all the specified authenticator specific features.
+ *
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account to check, will never be null
+ * @param features an array of features to check, will never be null
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ *
+ *
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ @Override
+ public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)
+ throws NetworkErrorException {
+ log("FeatherAccountAuthenticator#hasFeatures()");
+ return null;
+ }
+}
diff --git a/commons-android/src/main/java/net/hugonardo/android/commons/accounts/FeatherAccountAuthenticatorActivity.java b/commons-android/src/main/java/net/hugonardo/android/commons/accounts/FeatherAccountAuthenticatorActivity.java
new file mode 100644
index 00000000..af1f1cbf
--- /dev/null
+++ b/commons-android/src/main/java/net/hugonardo/android/commons/accounts/FeatherAccountAuthenticatorActivity.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.hugonardo.android.commons.accounts;
+
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorActivity;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+
+/**
+ * Base class for implementing an Activity that is used to help implement an
+ * AbstractAccountAuthenticator. If the AbstractAccountAuthenticator needs to use an activity
+ * to handle the request then it can have the activity extend FeatherAccountAuthenticatorActivity.
+ * The AbstractAccountAuthenticator passes in the response to the intent using the following:
+ *
+ * intent.putExtra({@link AccountManager#KEY_ACCOUNT_AUTHENTICATOR_RESPONSE}, response);
+ *
+ * The activity then sets the result that is to be handed to the response via
+ * {@link #setAccountAuthenticatorResult(android.os.Bundle)}.
+ * This result will be sent as the result of the request when the activity finishes. If this
+ * is never set or if it is set to null then error {@link AccountManager#ERROR_CODE_CANCELED}
+ * will be called on the response.
+ *
+ * @see AccountAuthenticatorActivity
+ */
+public class FeatherAccountAuthenticatorActivity extends AppCompatActivity {
+
+ /**
+ * The Intent extra to store username, or a name to identify the account.
+ *
+ * @see FeatherAccountAuthenticator#updateCredentials(AccountAuthenticatorResponse, Account, String, Bundle)
+ * @see FeatherAccountAuthenticator#confirmCredentials(AccountAuthenticatorResponse, Account, Bundle)
+ * @see FeatherAccountAuthenticator#editProperties(AccountAuthenticatorResponse, String)
+ */
+ public final static String ARG_ACCOUNT_NAME = "ACCOUNT_NAME";
+
+ /**
+ * The Intent extra to store account type.
+ */
+ public final static String ARG_ACCOUNT_TYPE = "ACCOUNT_TYPE";
+
+ /**
+ * The Intent extra to store authTokenType.
+ *
+ * @see FeatherAccountAuthenticator#addAccount(AccountAuthenticatorResponse, String, String, String[], Bundle)
+ */
+ public final static String ARG_AUTH_TOKEN_TYPE = "AUTH_TOKEN_TYPE";
+
+ /**
+ * The Intent extra to store a bundle of account options.
+ */
+ public final static String ARG_ACCOUNT_OPTIONS = "ACCOUNT_OPTIONS";
+
+ public final static String ARG_IS_ADDING_NEW_ACCOUNT = "IS_ADDING_ACCOUNT";
+
+ /**
+ * The Intent flag to confirm credentials.
+ */
+ public static final String ARG_CONFIRM_CREDENTIALS = "CONFIRM_CREDENTIALS";
+
+ private AccountAuthenticatorResponse mAccountAuthenticatorResponse = null;
+ private Bundle mResultBundle = null;
+
+ /**
+ * Set the result that is to be sent as the result of the request that caused this
+ * Activity to be launched. If result is null or this method is never called then
+ * the request will be canceled.
+ *
+ * @param result this is returned as the result of the AbstractAccountAuthenticator request
+ */
+ public final void setAccountAuthenticatorResult(Bundle result) {
+ mResultBundle = result;
+ }
+
+ /**
+ * Retreives the AccountAuthenticatorResponse from either the intent of the icicle, if the
+ * icicle is non-zero.
+ *
+ * @param icicle the save instance data of this Activity, may be null
+ */
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mAccountAuthenticatorResponse =
+ getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
+
+ if (mAccountAuthenticatorResponse != null) {
+ mAccountAuthenticatorResponse.onRequestContinued();
+ }
+ }
+
+ /**
+ * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present.
+ */
+ public void finish() {
+ if (mAccountAuthenticatorResponse != null) {
+ // send the result bundle back if set, otherwise send an error.
+ if (mResultBundle != null) {
+ mAccountAuthenticatorResponse.onResult(mResultBundle);
+ } else {
+ mAccountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
+ }
+ mAccountAuthenticatorResponse = null;
+ }
+ super.finish();
+ }
+}
diff --git a/commons-android/src/main/java/net/hugonardo/android/commons/accounts/FeatherAuthenticationService.java b/commons-android/src/main/java/net/hugonardo/android/commons/accounts/FeatherAuthenticationService.java
new file mode 100644
index 00000000..af01ed04
--- /dev/null
+++ b/commons-android/src/main/java/net/hugonardo/android/commons/accounts/FeatherAuthenticationService.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package net.hugonardo.android.commons.accounts;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.support.annotation.NonNull;
+
+import timber.log.Timber;
+
+/**
+ * Service to handle Account authentication. It instantiates the authenticator
+ * and returns its IBinder.
+ */
+public abstract class FeatherAuthenticationService extends Service {
+
+ private AbstractAccountAuthenticator mAuthenticator;
+
+ @NonNull
+ protected abstract AbstractAccountAuthenticator provideAccountAuthenticator(@NonNull Context context);
+
+ protected abstract void registerTokenTypes(TokenTypes tokenTypes);
+
+ private void log(String msg) {
+ Timber.v(msg);
+ }
+
+ @Override
+ public void onCreate() {
+ log("SampleSyncAdapter Authentication Service started.");
+ mAuthenticator = provideAccountAuthenticator(this);
+ registerTokenTypes(TokenTypes.getSingleInstance());
+ }
+
+ @Override
+ public void onDestroy() {
+ log("SampleSyncAdapter Authentication Service stopped.");
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ log("getBinder()... returning the FeatherAccountAuthenticator binder for intent " + intent);
+ return mAuthenticator.getIBinder();
+ }
+}
diff --git a/commons-android/src/main/java/net/hugonardo/android/commons/accounts/TokenTypes.java b/commons-android/src/main/java/net/hugonardo/android/commons/accounts/TokenTypes.java
new file mode 100644
index 00000000..355f29c3
--- /dev/null
+++ b/commons-android/src/main/java/net/hugonardo/android/commons/accounts/TokenTypes.java
@@ -0,0 +1,42 @@
+package net.hugonardo.android.commons.accounts;
+
+import android.support.annotation.NonNull;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class TokenTypes {
+
+ private static final TokenTypes sSingleInstance = new TokenTypes();
+
+ /**
+ * Key é a chave do TokenType;
+ * Value é o label do TokenType.
+ */
+ private final Map
+ * public Obj fillFromCursor(Cursor cursor) {
+ * // desserialize
+ * return this;
+ * }
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * StringUtils.isEmpty(null) = true
+ * StringUtils.isEmpty("") = true
+ * StringUtils.isEmpty(" ") = false
+ * StringUtils.isEmpty("bob") = false
+ * StringUtils.isEmpty(" bob ") = false
+ *
+ *
+ *
+ * StringUtils.isNotEmpty(null) = false
+ * StringUtils.isNotEmpty("") = false
+ * StringUtils.isNotEmpty(" ") = true
+ * StringUtils.isNotEmpty("bob") = true
+ * StringUtils.isNotEmpty(" bob ") = true
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if the CharSequence is not empty and not null
+ * @since 3.0 Changed signature from isNotEmpty(String) to isNotEmpty(CharSequence)
+ */
+ public static boolean isNotEmpty(final CharSequence cs) {
+ return !isEmpty(cs);
+ }
+
+ /**
+ *
+ * StringUtils.isBlank(null) = true
+ * StringUtils.isBlank("") = true
+ * StringUtils.isBlank(" ") = true
+ * StringUtils.isBlank("bob") = false
+ * StringUtils.isBlank(" bob ") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if the CharSequence is null, empty or whitespace
+ * @since 2.0
+ * @since 3.0 Changed signature from isBlank(String) to isBlank(CharSequence)
+ */
+ public static boolean isBlank(final CharSequence cs) {
+ int strLen;
+ if (cs == null || (strLen = cs.length()) == 0) {
+ return true;
+ }
+ for (int i = 0; i < strLen; i++) {
+ if (Character.isWhitespace(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
+ * StringUtils.isNotBlank(null) = false
+ * StringUtils.isNotBlank("") = false
+ * StringUtils.isNotBlank(" ") = false
+ * StringUtils.isNotBlank("bob") = true
+ * StringUtils.isNotBlank(" bob ") = true
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if the CharSequence is
+ * not empty and not null and not whitespace
+ * @since 2.0
+ * @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence)
+ */
+ public static boolean isNotBlank(final CharSequence cs) {
+ return !isBlank(cs);
+ }
+
+ // Equals
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.equals(null, null) = true
+ * StringUtils.equals(null, "abc") = false
+ * StringUtils.equals("abc", null) = false
+ * StringUtils.equals("abc", "abc") = true
+ * StringUtils.equals("abc", "ABC") = false
+ *
+ *
+ * @see Object#equals(Object)
+ * @param cs1 the first CharSequence, may be {@code null}
+ * @param cs2 the second CharSequence, may be {@code null}
+ * @return {@code true} if the CharSequences are equal (case-sensitive), or both {@code null}
+ * @since 3.0 Changed signature from equals(String, String) to equals(CharSequence, CharSequence)
+ */
+ public static boolean equals(final CharSequence cs1, final CharSequence cs2) {
+ if (cs1 == cs2) {
+ return true;
+ }
+ if (cs1 == null || cs2 == null) {
+ return false;
+ }
+ if (cs1.length() != cs2.length()) {
+ return false;
+ }
+ if (cs1 instanceof String && cs2 instanceof String) {
+ return cs1.equals(cs2);
+ }
+ return CharSequenceUtils.regionMatches(cs1, false, 0, cs2, 0, cs1.length());
+ }
+
+ /**
+ *
+ * StringUtils.equalsIgnoreCase(null, null) = true
+ * StringUtils.equalsIgnoreCase(null, "abc") = false
+ * StringUtils.equalsIgnoreCase("abc", null) = false
+ * StringUtils.equalsIgnoreCase("abc", "abc") = true
+ * StringUtils.equalsIgnoreCase("abc", "ABC") = true
+ *
+ *
+ * @param str1 the first CharSequence, may be null
+ * @param str2 the second CharSequence, may be null
+ * @return {@code true} if the CharSequence are equal, case insensitive, or
+ * both {@code null}
+ * @since 3.0 Changed signature from equalsIgnoreCase(String, String) to equalsIgnoreCase(CharSequence, CharSequence)
+ */
+ public static boolean equalsIgnoreCase(final CharSequence str1, final CharSequence str2) {
+ if (str1 == null || str2 == null) {
+ return str1 == str2;
+ } else if (str1 == str2) {
+ return true;
+ } else if (str1.length() != str2.length()) {
+ return false;
+ } else {
+ return CharSequenceUtils.regionMatches(str1, true, 0, str2, 0, str1.length());
+ }
+ }
+
+ // Trim
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.trim(null) = null
+ * StringUtils.trim("") = ""
+ * StringUtils.trim(" ") = ""
+ * StringUtils.trim("abc") = "abc"
+ * StringUtils.trim(" abc ") = "abc"
+ *
+ *
+ * @param str the String to be trimmed, may be null
+ * @return the trimmed string, {@code null} if null String input
+ */
+ public static String trim(final String str) {
+ return str == null ? null : str.trim();
+ }
+
+ /**
+ *
+ * StringUtils.trimToNull(null) = null
+ * StringUtils.trimToNull("") = null
+ * StringUtils.trimToNull(" ") = null
+ * StringUtils.trimToNull("abc") = "abc"
+ * StringUtils.trimToNull(" abc ") = "abc"
+ *
+ *
+ * @param str the String to be trimmed, may be null
+ * @return the trimmed String,
+ * {@code null} if only chars <= 32, empty or null String input
+ * @since 2.0
+ */
+ public static String trimToNull(final String str) {
+ final String ts = trim(str);
+ return isEmpty(ts) ? null : ts;
+ }
+
+ /**
+ *
+ * StringUtils.trimToEmpty(null) = ""
+ * StringUtils.trimToEmpty("") = ""
+ * StringUtils.trimToEmpty(" ") = ""
+ * StringUtils.trimToEmpty("abc") = "abc"
+ * StringUtils.trimToEmpty(" abc ") = "abc"
+ *
+ *
+ * @param str the String to be trimmed, may be null
+ * @return the trimmed String, or an empty String if {@code null} input
+ * @since 2.0
+ */
+ public static String trimToEmpty(final String str) {
+ return str == null ? EMPTY : str.trim();
+ }
+
+ // Stripping
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.strip(null) = null
+ * StringUtils.strip("") = ""
+ * StringUtils.strip(" ") = ""
+ * StringUtils.strip("abc") = "abc"
+ * StringUtils.strip(" abc") = "abc"
+ * StringUtils.strip("abc ") = "abc"
+ * StringUtils.strip(" abc ") = "abc"
+ * StringUtils.strip(" ab c ") = "ab c"
+ *
+ *
+ * @param str the String to remove whitespace from, may be null
+ * @return the stripped String, {@code null} if null String input
+ */
+ public static String strip(final String str) {
+ return strip(str, null);
+ }
+
+ /**
+ *
+ * StringUtils.stripToNull(null) = null
+ * StringUtils.stripToNull("") = null
+ * StringUtils.stripToNull(" ") = null
+ * StringUtils.stripToNull("abc") = "abc"
+ * StringUtils.stripToNull(" abc") = "abc"
+ * StringUtils.stripToNull("abc ") = "abc"
+ * StringUtils.stripToNull(" abc ") = "abc"
+ * StringUtils.stripToNull(" ab c ") = "ab c"
+ *
+ *
+ * @param str the String to be stripped, may be null
+ * @return the stripped String,
+ * {@code null} if whitespace, empty or null String input
+ * @since 2.0
+ */
+ public static String stripToNull(String str) {
+ if (str == null) {
+ return null;
+ }
+ str = strip(str, null);
+ return str.isEmpty() ? null : str;
+ }
+
+ /**
+ *
+ * StringUtils.stripToEmpty(null) = ""
+ * StringUtils.stripToEmpty("") = ""
+ * StringUtils.stripToEmpty(" ") = ""
+ * StringUtils.stripToEmpty("abc") = "abc"
+ * StringUtils.stripToEmpty(" abc") = "abc"
+ * StringUtils.stripToEmpty("abc ") = "abc"
+ * StringUtils.stripToEmpty(" abc ") = "abc"
+ * StringUtils.stripToEmpty(" ab c ") = "ab c"
+ *
+ *
+ * @param str the String to be stripped, may be null
+ * @return the trimmed String, or an empty String if {@code null} input
+ * @since 2.0
+ */
+ public static String stripToEmpty(final String str) {
+ return str == null ? EMPTY : strip(str, null);
+ }
+
+ /**
+ *
+ * StringUtils.strip(null, *) = null
+ * StringUtils.strip("", *) = ""
+ * StringUtils.strip("abc", null) = "abc"
+ * StringUtils.strip(" abc", null) = "abc"
+ * StringUtils.strip("abc ", null) = "abc"
+ * StringUtils.strip(" abc ", null) = "abc"
+ * StringUtils.strip(" abcyx", "xyz") = " abc"
+ *
+ *
+ * @param str the String to remove characters from, may be null
+ * @param stripChars the characters to remove, null treated as whitespace
+ * @return the stripped String, {@code null} if null String input
+ */
+ public static String strip(String str, final String stripChars) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ str = stripStart(str, stripChars);
+ return stripEnd(str, stripChars);
+ }
+
+ /**
+ *
+ * StringUtils.stripStart(null, *) = null
+ * StringUtils.stripStart("", *) = ""
+ * StringUtils.stripStart("abc", "") = "abc"
+ * StringUtils.stripStart("abc", null) = "abc"
+ * StringUtils.stripStart(" abc", null) = "abc"
+ * StringUtils.stripStart("abc ", null) = "abc "
+ * StringUtils.stripStart(" abc ", null) = "abc "
+ * StringUtils.stripStart("yxabc ", "xyz") = "abc "
+ *
+ *
+ * @param str the String to remove characters from, may be null
+ * @param stripChars the characters to remove, null treated as whitespace
+ * @return the stripped String, {@code null} if null String input
+ */
+ public static String stripStart(final String str, final String stripChars) {
+ int strLen;
+ if (str == null || (strLen = str.length()) == 0) {
+ return str;
+ }
+ int start = 0;
+ if (stripChars == null) {
+ while (start != strLen && Character.isWhitespace(str.charAt(start))) {
+ start++;
+ }
+ } else if (stripChars.isEmpty()) {
+ return str;
+ } else {
+ while (start != strLen && stripChars.indexOf(str.charAt(start)) != INDEX_NOT_FOUND) {
+ start++;
+ }
+ }
+ return str.substring(start);
+ }
+
+ /**
+ *
+ * StringUtils.stripEnd(null, *) = null
+ * StringUtils.stripEnd("", *) = ""
+ * StringUtils.stripEnd("abc", "") = "abc"
+ * StringUtils.stripEnd("abc", null) = "abc"
+ * StringUtils.stripEnd(" abc", null) = " abc"
+ * StringUtils.stripEnd("abc ", null) = "abc"
+ * StringUtils.stripEnd(" abc ", null) = " abc"
+ * StringUtils.stripEnd(" abcyx", "xyz") = " abc"
+ * StringUtils.stripEnd("120.00", ".0") = "12"
+ *
+ *
+ * @param str the String to remove characters from, may be null
+ * @param stripChars the set of characters to remove, null treated as whitespace
+ * @return the stripped String, {@code null} if null String input
+ */
+ public static String stripEnd(final String str, final String stripChars) {
+ int end;
+ if (str == null || (end = str.length()) == 0) {
+ return str;
+ }
+
+ if (stripChars == null) {
+ while (end != 0 && Character.isWhitespace(str.charAt(end - 1))) {
+ end--;
+ }
+ } else if (stripChars.isEmpty()) {
+ return str;
+ } else {
+ while (end != 0 && stripChars.indexOf(str.charAt(end - 1)) != INDEX_NOT_FOUND) {
+ end--;
+ }
+ }
+ return str.substring(0, end);
+ }
+
+ // StripAll
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.stripAll(null) = null
+ * StringUtils.stripAll([]) = []
+ * StringUtils.stripAll(["abc", " abc"]) = ["abc", "abc"]
+ * StringUtils.stripAll(["abc ", null]) = ["abc", null]
+ *
+ *
+ * @param strs the array to remove whitespace from, may be null
+ * @return the stripped Strings, {@code null} if null array input
+ */
+ public static String[] stripAll(final String... strs) {
+ return stripAll(strs, null);
+ }
+
+ /**
+ *
+ * StringUtils.stripAll(null, *) = null
+ * StringUtils.stripAll([], *) = []
+ * StringUtils.stripAll(["abc", " abc"], null) = ["abc", "abc"]
+ * StringUtils.stripAll(["abc ", null], null) = ["abc", null]
+ * StringUtils.stripAll(["abc ", null], "yz") = ["abc ", null]
+ * StringUtils.stripAll(["yabcz", null], "yz") = ["abc", null]
+ *
+ *
+ * @param strs the array to remove characters from, may be null
+ * @param stripChars the characters to remove, null treated as whitespace
+ * @return the stripped Strings, {@code null} if null array input
+ */
+ public static String[] stripAll(final String[] strs, final String stripChars) {
+ int strsLen;
+ if (strs == null || (strsLen = strs.length) == 0) {
+ return strs;
+ }
+ final String[] newArr = new String[strsLen];
+ for (int i = 0; i < strsLen; i++) {
+ newArr[i] = strip(strs[i], stripChars);
+ }
+ return newArr;
+ }
+
+ /**
+ *
+ * StringUtils.stripAccents(null) = null
+ * StringUtils.stripAccents("") = ""
+ * StringUtils.stripAccents("control") = "control"
+ * StringUtils.stripAccents("éclair") = "eclair"
+ *
+ *
+ * @param input String to be stripped
+ * @return input text with diacritics removed
+ *
+ * @since 3.0
+ */
+ // See also Lucene's ASCIIFoldingFilter (Lucene 2.9) that replaces accented characters by their unaccented equivalent (and uncommitted bug fix: https://issues.apache.org/jira/browse/LUCENE-1343?focusedCommentId=12858907&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#action_12858907).
+ public static String stripAccents(final String input) {
+ if(input == null) {
+ return null;
+ }
+ final Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");//$NON-NLS-1$
+ final StringBuilder decomposed = new StringBuilder(Normalizer.normalize(input, Normalizer.Form.NFD));
+ convertRemainingAccentCharacters(decomposed);
+ // Note that this doesn't correctly remove ligatures...
+ return pattern.matcher(decomposed).replaceAll(StringUtils.EMPTY);
+ }
+
+ private static void convertRemainingAccentCharacters(StringBuilder decomposed) {
+ for (int i = 0; i < decomposed.length(); i++) {
+ if (decomposed.charAt(i) == '\u0141') {
+ decomposed.deleteCharAt(i);
+ decomposed.insert(i, 'L');
+ } else if (decomposed.charAt(i) == '\u0142') {
+ decomposed.deleteCharAt(i);
+ decomposed.insert(i, 'l');
+ }
+ }
+ }
+
+ // Equals
+ //-----------------------------------------------------------------------
+
+ // Compare
+ //-----------------------------------------------------------------------
+ /**
+ *
+ *
+ *
+ *
+ *
+ * str1.compareTo(str2)
+ * StringUtils.compare(null, null) = 0
+ * StringUtils.compare(null , "a") < 0
+ * StringUtils.compare("a", null) > 0
+ * StringUtils.compare("abc", "abc") = 0
+ * StringUtils.compare("a", "b") < 0
+ * StringUtils.compare("b", "a") > 0
+ * StringUtils.compare("a", "B") > 0
+ * StringUtils.compare("ab", "abc") < 0
+ *
+ *
+ * @see #compare(String, String, boolean)
+ * @see String#compareTo(String)
+ * @param str1 the String to compare from
+ * @param str2 the String to compare to
+ * @return < 0, 0, > 0, if {@code str1} is respectively less, equal ou greater than {@code str2}
+ * @since 3.5
+ */
+ public static int compare(final String str1, final String str2) {
+ return compare(str1, str2, true);
+ }
+
+ /**
+ *
+ *
+ *
+ *
+ *
+ * str1.compareTo(str2)
+ * StringUtils.compare(null, null, *) = 0
+ * StringUtils.compare(null , "a", true) < 0
+ * StringUtils.compare(null , "a", false) > 0
+ * StringUtils.compare("a", null, true) > 0
+ * StringUtils.compare("a", null, false) < 0
+ * StringUtils.compare("abc", "abc", *) = 0
+ * StringUtils.compare("a", "b", *) < 0
+ * StringUtils.compare("b", "a", *) > 0
+ * StringUtils.compare("a", "B", *) > 0
+ * StringUtils.compare("ab", "abc", *) < 0
+ *
+ *
+ * @see String#compareTo(String)
+ * @param str1 the String to compare from
+ * @param str2 the String to compare to
+ * @param nullIsLess whether consider {@code null} value less than non-{@code null} value
+ * @return < 0, 0, > 0, if {@code str1} is respectively less, equal ou greater than {@code str2}
+ * @since 3.5
+ */
+ public static int compare(final String str1, final String str2, final boolean nullIsLess) {
+ if (str1 == str2) {
+ return 0;
+ }
+ if (str1 == null) {
+ return nullIsLess ? -1 : 1;
+ }
+ if (str2 == null) {
+ return nullIsLess ? 1 : - 1;
+ }
+ return str1.compareTo(str2);
+ }
+
+ /**
+ *
+ *
+ *
+ *
+ *
+ * str1.compareToIgnoreCase(str2)
+ * StringUtils.compareIgnoreCase(null, null) = 0
+ * StringUtils.compareIgnoreCase(null , "a") < 0
+ * StringUtils.compareIgnoreCase("a", null) > 0
+ * StringUtils.compareIgnoreCase("abc", "abc") = 0
+ * StringUtils.compareIgnoreCase("abc", "ABC") = 0
+ * StringUtils.compareIgnoreCase("a", "b") < 0
+ * StringUtils.compareIgnoreCase("b", "a") > 0
+ * StringUtils.compareIgnoreCase("a", "B") < 0
+ * StringUtils.compareIgnoreCase("A", "b") < 0
+ * StringUtils.compareIgnoreCase("ab", "ABC") < 0
+ *
+ *
+ * @see #compareIgnoreCase(String, String, boolean)
+ * @see String#compareToIgnoreCase(String)
+ * @param str1 the String to compare from
+ * @param str2 the String to compare to
+ * @return < 0, 0, > 0, if {@code str1} is respectively less, equal ou greater than {@code str2},
+ * ignoring case differences.
+ * @since 3.5
+ */
+ public static int compareIgnoreCase(final String str1, final String str2) {
+ return compareIgnoreCase(str1, str2, true);
+ }
+
+ /**
+ *
+ *
+ *
+ *
+ *
+ * str1.compareToIgnoreCase(str2)
+ * StringUtils.compareIgnoreCase(null, null, *) = 0
+ * StringUtils.compareIgnoreCase(null , "a", true) < 0
+ * StringUtils.compareIgnoreCase(null , "a", false) > 0
+ * StringUtils.compareIgnoreCase("a", null, true) > 0
+ * StringUtils.compareIgnoreCase("a", null, false) < 0
+ * StringUtils.compareIgnoreCase("abc", "abc", *) = 0
+ * StringUtils.compareIgnoreCase("abc", "ABC", *) = 0
+ * StringUtils.compareIgnoreCase("a", "b", *) < 0
+ * StringUtils.compareIgnoreCase("b", "a", *) > 0
+ * StringUtils.compareIgnoreCase("a", "B", *) < 0
+ * StringUtils.compareIgnoreCase("A", "b", *) < 0
+ * StringUtils.compareIgnoreCase("ab", "abc", *) < 0
+ *
+ *
+ * @see String#compareToIgnoreCase(String)
+ * @param str1 the String to compare from
+ * @param str2 the String to compare to
+ * @param nullIsLess whether consider {@code null} value less than non-{@code null} value
+ * @return < 0, 0, > 0, if {@code str1} is respectively less, equal ou greater than {@code str2},
+ * ignoring case differences.
+ * @since 3.5
+ */
+ public static int compareIgnoreCase(final String str1, final String str2, final boolean nullIsLess) {
+ if (str1 == str2) {
+ return 0;
+ }
+ if (str1 == null) {
+ return nullIsLess ? -1 : 1;
+ }
+ if (str2 == null) {
+ return nullIsLess ? 1 : - 1;
+ }
+ return str1.compareToIgnoreCase(str2);
+ }
+
+ // IndexOf
+ //-----------------------------------------------------------------------
+
+ // LastIndexOf
+ //-----------------------------------------------------------------------
+
+ // Contains
+ //-----------------------------------------------------------------------
+
+ /**
+ * Check whether the given CharSequence contains any whitespace characters.
+ * @param seq the CharSequence to check (may be {@code null})
+ * @return {@code true} if the CharSequence is not empty and
+ * contains at least 1 whitespace character
+ * @see Character#isWhitespace
+ * @since 3.0
+ */
+ // From org.springframework.util.StringUtils, under Apache License 2.0
+ public static boolean containsWhitespace(final CharSequence seq) {
+ if (isEmpty(seq)) {
+ return false;
+ }
+ final int strLen = seq.length();
+ for (int i = 0; i < strLen; i++) {
+ if (Character.isWhitespace(seq.charAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // IndexOfAny chars
+ //-----------------------------------------------------------------------
+
+ // ContainsAny
+ //-----------------------------------------------------------------------
+
+ // IndexOfAnyBut chars
+ //-----------------------------------------------------------------------
+
+ // ContainsOnly
+ //-----------------------------------------------------------------------
+
+ // ContainsNone
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.containsNone(null, *) = true
+ * StringUtils.containsNone(*, null) = true
+ * StringUtils.containsNone("", *) = true
+ * StringUtils.containsNone("ab", '') = true
+ * StringUtils.containsNone("abab", 'xyz') = true
+ * StringUtils.containsNone("ab1", 'xyz') = true
+ * StringUtils.containsNone("abz", 'xyz') = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @param searchChars an array of invalid chars, may be null
+ * @return true if it contains none of the invalid chars, or is null
+ * @since 2.0
+ * @since 3.0 Changed signature from containsNone(String, char[]) to containsNone(CharSequence, char...)
+ */
+ public static boolean containsNone(final CharSequence cs, final char... searchChars) {
+ if (cs == null || searchChars == null) {
+ return true;
+ }
+ final int csLen = cs.length();
+ final int csLast = csLen - 1;
+ final int searchLen = searchChars.length;
+ final int searchLast = searchLen - 1;
+ for (int i = 0; i < csLen; i++) {
+ final char ch = cs.charAt(i);
+ for (int j = 0; j < searchLen; j++) {
+ if (searchChars[j] == ch) {
+ if (Character.isHighSurrogate(ch)) {
+ if (j == searchLast) {
+ // missing low surrogate, fine, like String.indexOf(String)
+ return false;
+ }
+ if (i < csLast && searchChars[j + 1] == cs.charAt(i + 1)) {
+ return false;
+ }
+ } else {
+ // ch is in the Basic Multilingual Plane
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
+ * StringUtils.containsNone(null, *) = true
+ * StringUtils.containsNone(*, null) = true
+ * StringUtils.containsNone("", *) = true
+ * StringUtils.containsNone("ab", "") = true
+ * StringUtils.containsNone("abab", "xyz") = true
+ * StringUtils.containsNone("ab1", "xyz") = true
+ * StringUtils.containsNone("abz", "xyz") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @param invalidChars a String of invalid chars, may be null
+ * @return true if it contains none of the invalid chars, or is null
+ * @since 2.0
+ * @since 3.0 Changed signature from containsNone(String, String) to containsNone(CharSequence, String)
+ */
+ public static boolean containsNone(final CharSequence cs, final String invalidChars) {
+ if (cs == null || invalidChars == null) {
+ return true;
+ }
+ return containsNone(cs, invalidChars.toCharArray());
+ }
+
+ // IndexOfAny strings
+ //-----------------------------------------------------------------------
+
+ // Substring
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.substring(null, *) = null
+ * StringUtils.substring("", *) = ""
+ * StringUtils.substring("abc", 0) = "abc"
+ * StringUtils.substring("abc", 2) = "c"
+ * StringUtils.substring("abc", 4) = ""
+ * StringUtils.substring("abc", -2) = "bc"
+ * StringUtils.substring("abc", -4) = "abc"
+ *
+ *
+ * @param str the String to get the substring from, may be null
+ * @param start the position to start from, negative means
+ * count back from the end of the String by this many characters
+ * @return substring from start position, {@code null} if null String input
+ */
+ public static String substring(final String str, int start) {
+ if (str == null) {
+ return null;
+ }
+
+ // handle negatives, which means last n characters
+ if (start < 0) {
+ start = str.length() + start; // remember start is negative
+ }
+
+ if (start < 0) {
+ start = 0;
+ }
+ if (start > str.length()) {
+ return EMPTY;
+ }
+
+ return str.substring(start);
+ }
+
+ /**
+ *
+ * StringUtils.substring(null, *, *) = null
+ * StringUtils.substring("", * , *) = "";
+ * StringUtils.substring("abc", 0, 2) = "ab"
+ * StringUtils.substring("abc", 2, 0) = ""
+ * StringUtils.substring("abc", 2, 4) = "c"
+ * StringUtils.substring("abc", 4, 6) = ""
+ * StringUtils.substring("abc", 2, 2) = ""
+ * StringUtils.substring("abc", -2, -1) = "b"
+ * StringUtils.substring("abc", -4, 2) = "ab"
+ *
+ *
+ * @param str the String to get the substring from, may be null
+ * @param start the position to start from, negative means
+ * count back from the end of the String by this many characters
+ * @param end the position to end at (exclusive), negative means
+ * count back from the end of the String by this many characters
+ * @return substring from start position to end position,
+ * {@code null} if null String input
+ */
+ public static String substring(final String str, int start, int end) {
+ if (str == null) {
+ return null;
+ }
+
+ // handle negatives
+ if (end < 0) {
+ end = str.length() + end; // remember end is negative
+ }
+ if (start < 0) {
+ start = str.length() + start; // remember start is negative
+ }
+
+ // check length next
+ if (end > str.length()) {
+ end = str.length();
+ }
+
+ // if start is greater than end, return ""
+ if (start > end) {
+ return EMPTY;
+ }
+
+ if (start < 0) {
+ start = 0;
+ }
+ if (end < 0) {
+ end = 0;
+ }
+
+ return str.substring(start, end);
+ }
+
+ // Left/Right/Mid
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.left(null, *) = null
+ * StringUtils.left(*, -ve) = ""
+ * StringUtils.left("", *) = ""
+ * StringUtils.left("abc", 0) = ""
+ * StringUtils.left("abc", 2) = "ab"
+ * StringUtils.left("abc", 4) = "abc"
+ *
+ *
+ * @param str the String to get the leftmost characters from, may be null
+ * @param len the length of the required String
+ * @return the leftmost characters, {@code null} if null String input
+ */
+ public static String left(final String str, final int len) {
+ if (str == null) {
+ return null;
+ }
+ if (len < 0) {
+ return EMPTY;
+ }
+ if (str.length() <= len) {
+ return str;
+ }
+ return str.substring(0, len);
+ }
+
+ /**
+ *
+ * StringUtils.right(null, *) = null
+ * StringUtils.right(*, -ve) = ""
+ * StringUtils.right("", *) = ""
+ * StringUtils.right("abc", 0) = ""
+ * StringUtils.right("abc", 2) = "bc"
+ * StringUtils.right("abc", 4) = "abc"
+ *
+ *
+ * @param str the String to get the rightmost characters from, may be null
+ * @param len the length of the required String
+ * @return the rightmost characters, {@code null} if null String input
+ */
+ public static String right(final String str, final int len) {
+ if (str == null) {
+ return null;
+ }
+ if (len < 0) {
+ return EMPTY;
+ }
+ if (str.length() <= len) {
+ return str;
+ }
+ return str.substring(str.length() - len);
+ }
+
+ /**
+ *
+ * StringUtils.mid(null, *, *) = null
+ * StringUtils.mid(*, *, -ve) = ""
+ * StringUtils.mid("", 0, *) = ""
+ * StringUtils.mid("abc", 0, 2) = "ab"
+ * StringUtils.mid("abc", 0, 4) = "abc"
+ * StringUtils.mid("abc", 2, 4) = "c"
+ * StringUtils.mid("abc", 4, 2) = ""
+ * StringUtils.mid("abc", -2, 2) = "ab"
+ *
+ *
+ * @param str the String to get the characters from, may be null
+ * @param pos the position to start from, negative treated as zero
+ * @param len the length of the required String
+ * @return the middle characters, {@code null} if null String input
+ */
+ public static String mid(final String str, int pos, final int len) {
+ if (str == null) {
+ return null;
+ }
+ if (len < 0 || pos > str.length()) {
+ return EMPTY;
+ }
+ if (pos < 0) {
+ pos = 0;
+ }
+ if (str.length() <= pos + len) {
+ return str.substring(pos);
+ }
+ return str.substring(pos, pos + len);
+ }
+
+ // SubStringAfter/SubStringBefore
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.substringBefore(null, *) = null
+ * StringUtils.substringBefore("", *) = ""
+ * StringUtils.substringBefore("abc", "a") = ""
+ * StringUtils.substringBefore("abcba", "b") = "a"
+ * StringUtils.substringBefore("abc", "c") = "ab"
+ * StringUtils.substringBefore("abc", "d") = "abc"
+ * StringUtils.substringBefore("abc", "") = ""
+ * StringUtils.substringBefore("abc", null) = "abc"
+ *
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the String to search for, may be null
+ * @return the substring before the first occurrence of the separator,
+ * {@code null} if null String input
+ * @since 2.0
+ */
+ public static String substringBefore(final String str, final String separator) {
+ if (isEmpty(str) || separator == null) {
+ return str;
+ }
+ if (separator.isEmpty()) {
+ return EMPTY;
+ }
+ final int pos = str.indexOf(separator);
+ if (pos == INDEX_NOT_FOUND) {
+ return str;
+ }
+ return str.substring(0, pos);
+ }
+
+ /**
+ *
+ * StringUtils.substringAfter(null, *) = null
+ * StringUtils.substringAfter("", *) = ""
+ * StringUtils.substringAfter(*, null) = ""
+ * StringUtils.substringAfter("abc", "a") = "bc"
+ * StringUtils.substringAfter("abcba", "b") = "cba"
+ * StringUtils.substringAfter("abc", "c") = ""
+ * StringUtils.substringAfter("abc", "d") = ""
+ * StringUtils.substringAfter("abc", "") = "abc"
+ *
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the String to search for, may be null
+ * @return the substring after the first occurrence of the separator,
+ * {@code null} if null String input
+ * @since 2.0
+ */
+ public static String substringAfter(final String str, final String separator) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ if (separator == null) {
+ return EMPTY;
+ }
+ final int pos = str.indexOf(separator);
+ if (pos == INDEX_NOT_FOUND) {
+ return EMPTY;
+ }
+ return str.substring(pos + separator.length());
+ }
+
+ /**
+ *
+ * StringUtils.substringBeforeLast(null, *) = null
+ * StringUtils.substringBeforeLast("", *) = ""
+ * StringUtils.substringBeforeLast("abcba", "b") = "abc"
+ * StringUtils.substringBeforeLast("abc", "c") = "ab"
+ * StringUtils.substringBeforeLast("a", "a") = ""
+ * StringUtils.substringBeforeLast("a", "z") = "a"
+ * StringUtils.substringBeforeLast("a", null) = "a"
+ * StringUtils.substringBeforeLast("a", "") = "a"
+ *
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the String to search for, may be null
+ * @return the substring before the last occurrence of the separator,
+ * {@code null} if null String input
+ * @since 2.0
+ */
+ public static String substringBeforeLast(final String str, final String separator) {
+ if (isEmpty(str) || isEmpty(separator)) {
+ return str;
+ }
+ final int pos = str.lastIndexOf(separator);
+ if (pos == INDEX_NOT_FOUND) {
+ return str;
+ }
+ return str.substring(0, pos);
+ }
+
+ /**
+ *
+ * StringUtils.substringAfterLast(null, *) = null
+ * StringUtils.substringAfterLast("", *) = ""
+ * StringUtils.substringAfterLast(*, "") = ""
+ * StringUtils.substringAfterLast(*, null) = ""
+ * StringUtils.substringAfterLast("abc", "a") = "bc"
+ * StringUtils.substringAfterLast("abcba", "b") = "a"
+ * StringUtils.substringAfterLast("abc", "c") = ""
+ * StringUtils.substringAfterLast("a", "a") = ""
+ * StringUtils.substringAfterLast("a", "z") = ""
+ *
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the String to search for, may be null
+ * @return the substring after the last occurrence of the separator,
+ * {@code null} if null String input
+ * @since 2.0
+ */
+ public static String substringAfterLast(final String str, final String separator) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ if (isEmpty(separator)) {
+ return EMPTY;
+ }
+ final int pos = str.lastIndexOf(separator);
+ if (pos == INDEX_NOT_FOUND || pos == str.length() - separator.length()) {
+ return EMPTY;
+ }
+ return str.substring(pos + separator.length());
+ }
+
+ // Substring between
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.substringBetween(null, *) = null
+ * StringUtils.substringBetween("", "") = ""
+ * StringUtils.substringBetween("", "tag") = null
+ * StringUtils.substringBetween("tagabctag", null) = null
+ * StringUtils.substringBetween("tagabctag", "") = ""
+ * StringUtils.substringBetween("tagabctag", "tag") = "abc"
+ *
+ *
+ * @param str the String containing the substring, may be null
+ * @param tag the String before and after the substring, may be null
+ * @return the substring, {@code null} if no match
+ * @since 2.0
+ */
+ public static String substringBetween(final String str, final String tag) {
+ return substringBetween(str, tag, tag);
+ }
+
+ /**
+ *
+ * StringUtils.substringBetween("wx[b]yz", "[", "]") = "b"
+ * StringUtils.substringBetween(null, *, *) = null
+ * StringUtils.substringBetween(*, null, *) = null
+ * StringUtils.substringBetween(*, *, null) = null
+ * StringUtils.substringBetween("", "", "") = ""
+ * StringUtils.substringBetween("", "", "]") = null
+ * StringUtils.substringBetween("", "[", "]") = null
+ * StringUtils.substringBetween("yabcz", "", "") = ""
+ * StringUtils.substringBetween("yabcz", "y", "z") = "abc"
+ * StringUtils.substringBetween("yabczyabcz", "y", "z") = "abc"
+ *
+ *
+ * @param str the String containing the substring, may be null
+ * @param open the String before the substring, may be null
+ * @param close the String after the substring, may be null
+ * @return the substring, {@code null} if no match
+ * @since 2.0
+ */
+ public static String substringBetween(final String str, final String open, final String close) {
+ if (str == null || open == null || close == null) {
+ return null;
+ }
+ final int start = str.indexOf(open);
+ if (start != INDEX_NOT_FOUND) {
+ final int end = str.indexOf(close, start + open.length());
+ if (end != INDEX_NOT_FOUND) {
+ return str.substring(start + open.length(), end);
+ }
+ }
+ return null;
+ }
+
+ // Nested extraction
+ //-----------------------------------------------------------------------
+
+ // Splitting
+ //-----------------------------------------------------------------------
+
+ // -----------------------------------------------------------------------
+
+ // Joining
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.join(null) = null
+ * StringUtils.join([]) = ""
+ * StringUtils.join([null]) = ""
+ * StringUtils.join(["a", "b", "c"]) = "abc"
+ * StringUtils.join([null, "", "a"]) = "a"
+ *
+ *
+ * @param
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ *
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final long[] array, final char separator) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, separator, 0, array.length);
+ }
+
+ /**
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ *
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final int[] array, final char separator) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, separator, 0, array.length);
+ }
+
+ /**
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ *
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final short[] array, final char separator) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, separator, 0, array.length);
+ }
+
+ /**
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ *
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final byte[] array, final char separator) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, separator, 0, array.length);
+ }
+
+ /**
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ *
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final char[] array, final char separator) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, separator, 0, array.length);
+ }
+
+ /**
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ *
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final float[] array, final char separator) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, separator, 0, array.length);
+ }
+
+ /**
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ *
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final double[] array, final char separator) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, separator, 0, array.length);
+ }
+
+ /**
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ *
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in an end index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final long[] array, final char separator, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ final int noOfItems = endIndex - startIndex;
+ if (noOfItems <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder buf = new StringBuilder(noOfItems * 16);
+ for (int i = startIndex; i < endIndex; i++) {
+ if (i > startIndex) {
+ buf.append(separator);
+ }
+ buf.append(array[i]);
+ }
+ return buf.toString();
+ }
+
+ /**
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ *
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in an end index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final int[] array, final char separator, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ final int noOfItems = endIndex - startIndex;
+ if (noOfItems <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder buf = new StringBuilder(noOfItems * 16);
+ for (int i = startIndex; i < endIndex; i++) {
+ if (i > startIndex) {
+ buf.append(separator);
+ }
+ buf.append(array[i]);
+ }
+ return buf.toString();
+ }
+
+ /**
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ *
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in an end index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final byte[] array, final char separator, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ final int noOfItems = endIndex - startIndex;
+ if (noOfItems <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder buf = new StringBuilder(noOfItems * 16);
+ for (int i = startIndex; i < endIndex; i++) {
+ if (i > startIndex) {
+ buf.append(separator);
+ }
+ buf.append(array[i]);
+ }
+ return buf.toString();
+ }
+
+ /**
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ *
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in an end index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final short[] array, final char separator, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ final int noOfItems = endIndex - startIndex;
+ if (noOfItems <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder buf = new StringBuilder(noOfItems * 16);
+ for (int i = startIndex; i < endIndex; i++) {
+ if (i > startIndex) {
+ buf.append(separator);
+ }
+ buf.append(array[i]);
+ }
+ return buf.toString();
+ }
+
+ /**
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ *
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in an end index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final char[] array, final char separator, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ final int noOfItems = endIndex - startIndex;
+ if (noOfItems <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder buf = new StringBuilder(noOfItems * 16);
+ for (int i = startIndex; i < endIndex; i++) {
+ if (i > startIndex) {
+ buf.append(separator);
+ }
+ buf.append(array[i]);
+ }
+ return buf.toString();
+ }
+
+ /**
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ *
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in an end index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final double[] array, final char separator, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ final int noOfItems = endIndex - startIndex;
+ if (noOfItems <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder buf = new StringBuilder(noOfItems * 16);
+ for (int i = startIndex; i < endIndex; i++) {
+ if (i > startIndex) {
+ buf.append(separator);
+ }
+ buf.append(array[i]);
+ }
+ return buf.toString();
+ }
+
+ /**
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ *
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in an end index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final float[] array, final char separator, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ final int noOfItems = endIndex - startIndex;
+ if (noOfItems <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder buf = new StringBuilder(noOfItems * 16);
+ for (int i = startIndex; i < endIndex; i++) {
+ if (i > startIndex) {
+ buf.append(separator);
+ }
+ buf.append(array[i]);
+ }
+ return buf.toString();
+ }
+
+
+ /**
+ *
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join(["a", "b", "c"], "--") = "a--b--c"
+ * StringUtils.join(["a", "b", "c"], null) = "abc"
+ * StringUtils.join(["a", "b", "c"], "") = "abc"
+ * StringUtils.join([null, "", "a"], ',') = ",,a"
+ *
+ *
+ * @param array the array of values to join together, may be null
+ * @param separator the separator character to use, null treated as ""
+ * @return the joined String, {@code null} if null array input
+ */
+ public static String join(final Object[] array, final String separator) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, separator, 0, array.length);
+ }
+
+ /**
+ *
+ * StringUtils.join(null, *, *, *) = null
+ * StringUtils.join([], *, *, *) = ""
+ * StringUtils.join([null], *, *, *) = ""
+ * StringUtils.join(["a", "b", "c"], "--", 0, 3) = "a--b--c"
+ * StringUtils.join(["a", "b", "c"], "--", 1, 3) = "b--c"
+ * StringUtils.join(["a", "b", "c"], "--", 2, 3) = "c"
+ * StringUtils.join(["a", "b", "c"], "--", 2, 2) = ""
+ * StringUtils.join(["a", "b", "c"], null, 0, 3) = "abc"
+ * StringUtils.join(["a", "b", "c"], "", 0, 3) = "abc"
+ * StringUtils.join([null, "", "a"], ',', 0, 3) = ",,a"
+ *
+ *
+ * @param array the array of values to join together, may be null
+ * @param separator the separator character to use, null treated as ""
+ * @param startIndex the first index to start joining from.
+ * @param endIndex the index to stop joining from (exclusive).
+ * @return the joined String, {@code null} if null array input; or the empty string
+ * if {@code endIndex - startIndex <= 0}. The number of joined entries is given by
+ * {@code endIndex - startIndex}
+ * @throws ArrayIndexOutOfBoundsException ife
+ * {@code startIndex < 0} or
+ * {@code startIndex >= array.length()} or
+ * {@code endIndex < 0} or
+ * {@code endIndex > array.length()}
+ */
+ public static String join(final Object[] array, String separator, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ if (separator == null) {
+ separator = EMPTY;
+ }
+
+ // endIndex - startIndex > 0: Len = NofStrings *(len(firstString) + len(separator))
+ // (Assuming that all Strings are roughly equally long)
+ final int noOfItems = endIndex - startIndex;
+ if (noOfItems <= 0) {
+ return EMPTY;
+ }
+
+ final StringBuilder buf = new StringBuilder(noOfItems * 16);
+
+ for (int i = startIndex; i < endIndex; i++) {
+ if (i > startIndex) {
+ buf.append(separator);
+ }
+ if (array[i] != null) {
+ buf.append(array[i]);
+ }
+ }
+ return buf.toString();
+ }
+
+ // Delete
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.deleteWhitespace(null) = null
+ * StringUtils.deleteWhitespace("") = ""
+ * StringUtils.deleteWhitespace("abc") = "abc"
+ * StringUtils.deleteWhitespace(" ab c ") = "abc"
+ *
+ *
+ * @param str the String to delete whitespace from, may be null
+ * @return the String without whitespaces, {@code null} if null String input
+ */
+ public static String deleteWhitespace(final String str) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ final int sz = str.length();
+ final char[] chs = new char[sz];
+ int count = 0;
+ for (int i = 0; i < sz; i++) {
+ if (!Character.isWhitespace(str.charAt(i))) {
+ chs[count++] = str.charAt(i);
+ }
+ }
+ if (count == sz) {
+ return str;
+ }
+ return new String(chs, 0, count);
+ }
+
+ // Remove
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.removeStart(null, *) = null
+ * StringUtils.removeStart("", *) = ""
+ * StringUtils.removeStart(*, null) = *
+ * StringUtils.removeStart("www.domain.com", "www.") = "domain.com"
+ * StringUtils.removeStart("domain.com", "www.") = "domain.com"
+ * StringUtils.removeStart("www.domain.com", "domain") = "www.domain.com"
+ * StringUtils.removeStart("abc", "") = "abc"
+ *
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.1
+ */
+ public static String removeStart(final String str, final String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ if (str.startsWith(remove)){
+ return str.substring(remove.length());
+ }
+ return str;
+ }
+
+ /**
+ *
+ * StringUtils.removeEnd(null, *) = null
+ * StringUtils.removeEnd("", *) = ""
+ * StringUtils.removeEnd(*, null) = *
+ * StringUtils.removeEnd("www.domain.com", ".com.") = "www.domain.com"
+ * StringUtils.removeEnd("www.domain.com", ".com") = "www.domain"
+ * StringUtils.removeEnd("www.domain.com", "domain") = "www.domain.com"
+ * StringUtils.removeEnd("abc", "") = "abc"
+ *
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.1
+ */
+ public static String removeEnd(final String str, final String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ if (str.endsWith(remove)) {
+ return str.substring(0, str.length() - remove.length());
+ }
+ return str;
+ }
+
+ /**
+ *
+ * StringUtils.remove(null, *) = null
+ * StringUtils.remove("", *) = ""
+ * StringUtils.remove(*, null) = *
+ * StringUtils.remove(*, "") = *
+ * StringUtils.remove("queued", "ue") = "qd"
+ * StringUtils.remove("queued", "zz") = "queued"
+ *
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.1
+ */
+ public static String remove(final String str, final String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ return replace(str, remove, EMPTY, -1);
+ }
+
+ /**
+ *
+ * StringUtils.remove(null, *) = null
+ * StringUtils.remove("", *) = ""
+ * StringUtils.remove("queued", 'u') = "qeed"
+ * StringUtils.remove("queued", 'z') = "queued"
+ *
+ *
+ * @param str the source String to search, may be null
+ * @param remove the char to search for and remove, may be null
+ * @return the substring with the char removed if found,
+ * {@code null} if null String input
+ * @since 2.1
+ */
+ public static String remove(final String str, final char remove) {
+ if (isEmpty(str) || str.indexOf(remove) == INDEX_NOT_FOUND) {
+ return str;
+ }
+ final char[] chars = str.toCharArray();
+ int pos = 0;
+ for (int i = 0; i < chars.length; i++) {
+ if (chars[i] != remove) {
+ chars[pos++] = chars[i];
+ }
+ }
+ return new String(chars, 0, pos);
+ }
+
+ /**
+ *
+ *
+ *
+ * "(?s)" to the regex.
+ * DOTALL is also know as single-line mode in Perl.
+ * StringUtils.removeAll(null, *) = null
+ * StringUtils.removeAll("any", null) = "any"
+ * StringUtils.removeAll("any", "") = "any"
+ * StringUtils.removeAll("any", ".*") = ""
+ * StringUtils.removeAll("any", ".+") = ""
+ * StringUtils.removeAll("abc", ".?") = ""
+ * StringUtils.removeAll("A<__>\n<__>B", "<.*>") = "A\nB"
+ * StringUtils.removeAll("A<__>\n<__>B", "(?s)<.*>") = "AB"
+ * StringUtils.removeAll("ABCabc123abc", "[a-z]") = "ABC123"
+ *
+ *
+ * @param text text to remove from, may be null
+ * @param regex the regular expression to which this string is to be matched
+ * @return the text with any removes processed,
+ * {@code null} if null String input
+ *
+ * @throws java.util.regex.PatternSyntaxException
+ * if the regular expression's syntax is invalid
+ *
+ * @see #replaceAll(String, String, String)
+ * @see #removePattern(String, String)
+ * @see String#replaceAll(String, String)
+ * @see Pattern
+ * @see Pattern#DOTALL
+ * @since 3.5
+ */
+ public static String removeAll(final String text, final String regex) {
+ return replaceAll(text, regex, StringUtils.EMPTY);
+ }
+
+ /**
+ *
+ *
+ *
+ * "(?s)" to the regex.
+ * DOTALL is also know as single-line mode in Perl.
+ * StringUtils.removeFirst(null, *) = null
+ * StringUtils.removeFirst("any", null) = "any"
+ * StringUtils.removeFirst("any", "") = "any"
+ * StringUtils.removeFirst("any", ".*") = ""
+ * StringUtils.removeFirst("any", ".+") = ""
+ * StringUtils.removeFirst("abc", ".?") = "bc"
+ * StringUtils.removeFirst("A<__>\n<__>B", "<.*>") = "A\n<__>B"
+ * StringUtils.removeFirst("A<__>\n<__>B", "(?s)<.*>") = "AB"
+ * StringUtils.removeFirst("ABCabc123", "[a-z]") = "ABCbc123"
+ * StringUtils.removeFirst("ABCabc123abc", "[a-z]+") = "ABC123abc"
+ *
+ *
+ * @param text text to remove from, may be null
+ * @param regex the regular expression to which this string is to be matched
+ * @return the text with the first replacement processed,
+ * {@code null} if null String input
+ *
+ * @throws java.util.regex.PatternSyntaxException
+ * if the regular expression's syntax is invalid
+ *
+ * @see #replaceFirst(String, String, String)
+ * @see String#replaceFirst(String, String)
+ * @see Pattern
+ * @see Pattern#DOTALL
+ * @since 3.5
+ */
+ public static String removeFirst(final String text, final String regex) {
+ return replaceFirst(text, regex, StringUtils.EMPTY);
+ }
+
+ // Replacing
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.replaceOnce(null, *, *) = null
+ * StringUtils.replaceOnce("", *, *) = ""
+ * StringUtils.replaceOnce("any", null, *) = "any"
+ * StringUtils.replaceOnce("any", *, null) = "any"
+ * StringUtils.replaceOnce("any", "", *) = "any"
+ * StringUtils.replaceOnce("aba", "a", null) = "aba"
+ * StringUtils.replaceOnce("aba", "a", "") = "ba"
+ * StringUtils.replaceOnce("aba", "a", "z") = "zba"
+ *
+ *
+ * @see #replace(String text, String searchString, String replacement, int max)
+ * @param text text to search and replace in, may be null
+ * @param searchString the String to search for, may be null
+ * @param replacement the String to replace with, may be null
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ */
+ public static String replaceOnce(final String text, final String searchString, final String replacement) {
+ return replace(text, searchString, replacement, 1);
+ }
+
+ /**
+ *
+ *
+ *
+ *
+ * StringUtils.replacePattern(null, *, *) = null
+ * StringUtils.replacePattern("any", null, *) = "any"
+ * StringUtils.replacePattern("any", *, null) = "any"
+ * StringUtils.replacePattern("", "", "zzz") = "zzz"
+ * StringUtils.replacePattern("", ".*", "zzz") = "zzz"
+ * StringUtils.replacePattern("", ".+", "zzz") = ""
+ * StringUtils.replacePattern("<__>\n<__>", "<.*>", "z") = "z"
+ * StringUtils.replacePattern("ABCabc123", "[a-z]", "_") = "ABC___123"
+ * StringUtils.replacePattern("ABCabc123", "[^A-Z0-9]+", "_") = "ABC_123"
+ * StringUtils.replacePattern("ABCabc123", "[^A-Z0-9]+", "") = "ABC123"
+ * StringUtils.replacePattern("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2") = "Lorem_ipsum_dolor_sit"
+ *
+ *
+ * @param source
+ * the source string
+ * @param regex
+ * the regular expression to which this string is to be matched
+ * @param replacement
+ * the string to be substituted for each match
+ * @return The resulting {@code String}
+ * @see #replaceAll(String, String, String)
+ * @see String#replaceAll(String, String)
+ * @see Pattern#DOTALL
+ * @since 3.2
+ * @since 3.5 Changed {@code null} reference passed to this method is a no-op.
+ */
+ public static String replacePattern(final String source, final String regex, final String replacement) {
+ if (source == null || regex == null|| replacement == null ) {
+ return source;
+ }
+ return Pattern.compile(regex, Pattern.DOTALL).matcher(source).replaceAll(replacement);
+ }
+
+ /**
+ *
+ *
+ *
+ *
+ * StringUtils.removePattern(null, *) = null
+ * StringUtils.removePattern("any", null) = "any"
+ * StringUtils.removePattern("A<__>\n<__>B", "<.*>") = "AB"
+ * StringUtils.removePattern("ABCabc123", "[a-z]") = "ABC123"
+ *
+ *
+ * @param source
+ * the source string
+ * @param regex
+ * the regular expression to which this string is to be matched
+ * @return The resulting {@code String}
+ * @see #replacePattern(String, String, String)
+ * @see String#replaceAll(String, String)
+ * @see Pattern#DOTALL
+ * @since 3.2
+ * @since 3.5 Changed {@code null} reference passed to this method is a no-op.
+ */
+ public static String removePattern(final String source, final String regex) {
+ return replacePattern(source, regex, StringUtils.EMPTY);
+ }
+
+ /**
+ *
+ *
+ *
+ * "(?s)" to the regex.
+ * DOTALL is also know as single-line mode in Perl.
+ * StringUtils.replaceAll(null, *, *) = null
+ * StringUtils.replaceAll("any", null, *) = "any"
+ * StringUtils.replaceAll("any", *, null) = "any"
+ * StringUtils.replaceAll("", "", "zzz") = "zzz"
+ * StringUtils.replaceAll("", ".*", "zzz") = "zzz"
+ * StringUtils.replaceAll("", ".+", "zzz") = ""
+ * StringUtils.replaceAll("abc", "", "ZZ") = "ZZaZZbZZcZZ"
+ * StringUtils.replaceAll("<__>\n<__>", "<.*>", "z") = "z\nz"
+ * StringUtils.replaceAll("<__>\n<__>", "(?s)<.*>", "z") = "z"
+ * StringUtils.replaceAll("ABCabc123", "[a-z]", "_") = "ABC___123"
+ * StringUtils.replaceAll("ABCabc123", "[^A-Z0-9]+", "_") = "ABC_123"
+ * StringUtils.replaceAll("ABCabc123", "[^A-Z0-9]+", "") = "ABC123"
+ * StringUtils.replaceAll("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2") = "Lorem_ipsum_dolor_sit"
+ *
+ *
+ * @param text text to search and replace in, may be null
+ * @param regex the regular expression to which this string is to be matched
+ * @param replacement the string to be substituted for each match
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ *
+ * @throws java.util.regex.PatternSyntaxException
+ * if the regular expression's syntax is invalid
+ *
+ * @see #replacePattern(String, String, String)
+ * @see String#replaceAll(String, String)
+ * @see Pattern
+ * @see Pattern#DOTALL
+ * @since 3.5
+ */
+ public static String replaceAll(final String text, final String regex, final String replacement) {
+ if (text == null || regex == null|| replacement == null ) {
+ return text;
+ }
+ return text.replaceAll(regex, replacement);
+ }
+
+ /**
+ *
+ *
+ *
+ * "(?s)" to the regex.
+ * DOTALL is also know as single-line mode in Perl.
+ * StringUtils.replaceFirst(null, *, *) = null
+ * StringUtils.replaceFirst("any", null, *) = "any"
+ * StringUtils.replaceFirst("any", *, null) = "any"
+ * StringUtils.replaceFirst("", "", "zzz") = "zzz"
+ * StringUtils.replaceFirst("", ".*", "zzz") = "zzz"
+ * StringUtils.replaceFirst("", ".+", "zzz") = ""
+ * StringUtils.replaceFirst("abc", "", "ZZ") = "ZZabc"
+ * StringUtils.replaceFirst("<__>\n<__>", "<.*>", "z") = "z\n<__>"
+ * StringUtils.replaceFirst("<__>\n<__>", "(?s)<.*>", "z") = "z"
+ * StringUtils.replaceFirst("ABCabc123", "[a-z]", "_") = "ABC_bc123"
+ * StringUtils.replaceFirst("ABCabc123abc", "[^A-Z0-9]+", "_") = "ABC_123abc"
+ * StringUtils.replaceFirst("ABCabc123abc", "[^A-Z0-9]+", "") = "ABC123abc"
+ * StringUtils.replaceFirst("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2") = "Lorem_ipsum dolor sit"
+ *
+ *
+ * @param text text to search and replace in, may be null
+ * @param regex the regular expression to which this string is to be matched
+ * @param replacement the string to be substituted for the first match
+ * @return the text with the first replacement processed,
+ * {@code null} if null String input
+ *
+ * @throws java.util.regex.PatternSyntaxException
+ * if the regular expression's syntax is invalid
+ *
+ * @see String#replaceFirst(String, String)
+ * @see Pattern
+ * @see Pattern#DOTALL
+ * @since 3.5
+ */
+ public static String replaceFirst(final String text, final String regex, final String replacement) {
+ if (text == null || regex == null|| replacement == null ) {
+ return text;
+ }
+ return text.replaceFirst(regex, replacement);
+ }
+
+ /**
+ *
+ * StringUtils.replace(null, *, *) = null
+ * StringUtils.replace("", *, *) = ""
+ * StringUtils.replace("any", null, *) = "any"
+ * StringUtils.replace("any", *, null) = "any"
+ * StringUtils.replace("any", "", *) = "any"
+ * StringUtils.replace("aba", "a", null) = "aba"
+ * StringUtils.replace("aba", "a", "") = "b"
+ * StringUtils.replace("aba", "a", "z") = "zbz"
+ *
+ *
+ * @see #replace(String text, String searchString, String replacement, int max)
+ * @param text text to search and replace in, may be null
+ * @param searchString the String to search for, may be null
+ * @param replacement the String to replace it with, may be null
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ */
+ public static String replace(final String text, final String searchString, final String replacement) {
+ return replace(text, searchString, replacement, -1);
+ }
+
+ /**
+ *
+ * StringUtils.replace(null, *, *, *) = null
+ * StringUtils.replace("", *, *, *) = ""
+ * StringUtils.replace("any", null, *, *) = "any"
+ * StringUtils.replace("any", *, null, *) = "any"
+ * StringUtils.replace("any", "", *, *) = "any"
+ * StringUtils.replace("any", *, *, 0) = "any"
+ * StringUtils.replace("abaa", "a", null, -1) = "abaa"
+ * StringUtils.replace("abaa", "a", "", -1) = "b"
+ * StringUtils.replace("abaa", "a", "z", 0) = "abaa"
+ * StringUtils.replace("abaa", "a", "z", 1) = "zbaa"
+ * StringUtils.replace("abaa", "a", "z", 2) = "zbza"
+ * StringUtils.replace("abaa", "a", "z", -1) = "zbzz"
+ *
+ *
+ * @param text text to search and replace in, may be null
+ * @param searchString the String to search for, may be null
+ * @param replacement the String to replace it with, may be null
+ * @param max maximum number of values to replace, or {@code -1} if no maximum
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ */
+ public static String replace(final String text, final String searchString, final String replacement, int max) {
+ if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) {
+ return text;
+ }
+ int start = 0;
+ int end = text.indexOf(searchString, start);
+ if (end == INDEX_NOT_FOUND) {
+ return text;
+ }
+ final int replLength = searchString.length();
+ int increase = replacement.length() - replLength;
+ increase = increase < 0 ? 0 : increase;
+ increase *= max < 0 ? 16 : max > 64 ? 64 : max;
+ final StringBuilder buf = new StringBuilder(text.length() + increase);
+ while (end != INDEX_NOT_FOUND) {
+ buf.append(text.substring(start, end)).append(replacement);
+ start = end + replLength;
+ if (--max == 0) {
+ break;
+ }
+ end = text.indexOf(searchString, start);
+ }
+ buf.append(text.substring(start));
+ return buf.toString();
+ }
+
+ /**
+ *
+ * StringUtils.replaceEach(null, *, *) = null
+ * StringUtils.replaceEach("", *, *) = ""
+ * StringUtils.replaceEach("aba", null, null) = "aba"
+ * StringUtils.replaceEach("aba", new String[0], null) = "aba"
+ * StringUtils.replaceEach("aba", null, new String[0]) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, null) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}) = "b"
+ * StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}) = "aba"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}) = "wcte"
+ * (example of how it does not repeat)
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}) = "dcte"
+ *
+ *
+ * @param text
+ * text to search and replace in, no-op if null
+ * @param searchList
+ * the Strings to search for, no-op if null
+ * @param replacementList
+ * the Strings to replace them with, no-op if null
+ * @return the text with any replacements processed, {@code null} if
+ * null String input
+ * @throws IllegalArgumentException
+ * if the lengths of the arrays are not the same (null is ok,
+ * and/or size 0)
+ * @since 2.4
+ */
+ public static String replaceEach(final String text, final String[] searchList, final String[] replacementList) {
+ return replaceEach(text, searchList, replacementList, false, 0);
+ }
+
+ /**
+ *
+ * StringUtils.replaceEachRepeatedly(null, *, *) = null
+ * StringUtils.replaceEachRepeatedly("", *, *) = ""
+ * StringUtils.replaceEachRepeatedly("aba", null, null) = "aba"
+ * StringUtils.replaceEachRepeatedly("aba", new String[0], null) = "aba"
+ * StringUtils.replaceEachRepeatedly("aba", null, new String[0]) = "aba"
+ * StringUtils.replaceEachRepeatedly("aba", new String[]{"a"}, null) = "aba"
+ * StringUtils.replaceEachRepeatedly("aba", new String[]{"a"}, new String[]{""}) = "b"
+ * StringUtils.replaceEachRepeatedly("aba", new String[]{null}, new String[]{"a"}) = "aba"
+ * StringUtils.replaceEachRepeatedly("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}) = "wcte"
+ * (example of how it repeats)
+ * StringUtils.replaceEachRepeatedly("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}) = "tcte"
+ * StringUtils.replaceEachRepeatedly("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}) = IllegalStateException
+ *
+ *
+ * @param text
+ * text to search and replace in, no-op if null
+ * @param searchList
+ * the Strings to search for, no-op if null
+ * @param replacementList
+ * the Strings to replace them with, no-op if null
+ * @return the text with any replacements processed, {@code null} if
+ * null String input
+ * @throws IllegalStateException
+ * if the search is repeating and there is an endless loop due
+ * to outputs of one being inputs to another
+ * @throws IllegalArgumentException
+ * if the lengths of the arrays are not the same (null is ok,
+ * and/or size 0)
+ * @since 2.4
+ */
+ public static String replaceEachRepeatedly(final String text, final String[] searchList, final String[] replacementList) {
+ // timeToLive should be 0 if not used or nothing to replace, else it's
+ // the length of the replace array
+ final int timeToLive = searchList == null ? 0 : searchList.length;
+ return replaceEach(text, searchList, replacementList, true, timeToLive);
+ }
+
+ /**
+ *
+ * StringUtils.replaceEach(null, *, *, *, *) = null
+ * StringUtils.replaceEach("", *, *, *, *) = ""
+ * StringUtils.replaceEach("aba", null, null, *, *) = "aba"
+ * StringUtils.replaceEach("aba", new String[0], null, *, *) = "aba"
+ * StringUtils.replaceEach("aba", null, new String[0], *, *) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, null, *, *) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *, >=0) = "b"
+ * StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}, *, >=0) = "aba"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}, *, >=0) = "wcte"
+ * (example of how it repeats)
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, false, >=0) = "dcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, true, >=2) = "tcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, *, *) = IllegalStateException
+ *
+ *
+ * @param text
+ * text to search and replace in, no-op if null
+ * @param searchList
+ * the Strings to search for, no-op if null
+ * @param replacementList
+ * the Strings to replace them with, no-op if null
+ * @param repeat if true, then replace repeatedly
+ * until there are no more possible replacements or timeToLive < 0
+ * @param timeToLive
+ * if less than 0 then there is a circular reference and endless
+ * loop
+ * @return the text with any replacements processed, {@code null} if
+ * null String input
+ * @throws IllegalStateException
+ * if the search is repeating and there is an endless loop due
+ * to outputs of one being inputs to another
+ * @throws IllegalArgumentException
+ * if the lengths of the arrays are not the same (null is ok,
+ * and/or size 0)
+ * @since 2.4
+ */
+ private static String replaceEach(
+ final String text, final String[] searchList, final String[] replacementList, final boolean repeat, final int timeToLive) {
+
+ // mchyzer Performance note: This creates very few new objects (one major goal)
+ // let me know if there are performance requests, we can create a harness to measure
+
+ if (text == null || text.isEmpty() || searchList == null ||
+ searchList.length == 0 || replacementList == null || replacementList.length == 0) {
+ return text;
+ }
+
+ // if recursing, this shouldn't be less than 0
+ if (timeToLive < 0) {
+ throw new IllegalStateException("Aborting to protect against StackOverflowError - " +
+ "output of one loop is the input of another");
+ }
+
+ final int searchLength = searchList.length;
+ final int replacementLength = replacementList.length;
+
+ // make sure lengths are ok, these need to be equal
+ if (searchLength != replacementLength) {
+ throw new IllegalArgumentException("Search and Replace array lengths don't match: "
+ + searchLength
+ + " vs "
+ + replacementLength);
+ }
+
+ // keep track of which still have matches
+ final boolean[] noMoreMatchesForReplIndex = new boolean[searchLength];
+
+ // index on index that the match was found
+ int textIndex = -1;
+ int replaceIndex = -1;
+ int tempIndex = -1;
+
+ // index of replace array that will replace the search string found
+ // NOTE: logic duplicated below START
+ for (int i = 0; i < searchLength; i++) {
+ if (noMoreMatchesForReplIndex[i] || searchList[i] == null ||
+ searchList[i].isEmpty() || replacementList[i] == null) {
+ continue;
+ }
+ tempIndex = text.indexOf(searchList[i]);
+
+ // see if we need to keep searching for this
+ if (tempIndex == -1) {
+ noMoreMatchesForReplIndex[i] = true;
+ } else {
+ if (textIndex == -1 || tempIndex < textIndex) {
+ textIndex = tempIndex;
+ replaceIndex = i;
+ }
+ }
+ }
+ // NOTE: logic mostly below END
+
+ // no search strings found, we are done
+ if (textIndex == -1) {
+ return text;
+ }
+
+ int start = 0;
+
+ // get a good guess on the size of the result buffer so it doesn't have to double if it goes over a bit
+ int increase = 0;
+
+ // count the replacement text elements that are larger than their corresponding text being replaced
+ for (int i = 0; i < searchList.length; i++) {
+ if (searchList[i] == null || replacementList[i] == null) {
+ continue;
+ }
+ final int greater = replacementList[i].length() - searchList[i].length();
+ if (greater > 0) {
+ increase += 3 * greater; // assume 3 matches
+ }
+ }
+ // have upper-bound at 20% increase, then let Java take over
+ increase = Math.min(increase, text.length() / 5);
+
+ final StringBuilder buf = new StringBuilder(text.length() + increase);
+
+ while (textIndex != -1) {
+
+ for (int i = start; i < textIndex; i++) {
+ buf.append(text.charAt(i));
+ }
+ buf.append(replacementList[replaceIndex]);
+
+ start = textIndex + searchList[replaceIndex].length();
+
+ textIndex = -1;
+ replaceIndex = -1;
+ tempIndex = -1;
+ // find the next earliest match
+ // NOTE: logic mostly duplicated above START
+ for (int i = 0; i < searchLength; i++) {
+ if (noMoreMatchesForReplIndex[i] || searchList[i] == null ||
+ searchList[i].isEmpty() || replacementList[i] == null) {
+ continue;
+ }
+ tempIndex = text.indexOf(searchList[i], start);
+
+ // see if we need to keep searching for this
+ if (tempIndex == -1) {
+ noMoreMatchesForReplIndex[i] = true;
+ } else {
+ if (textIndex == -1 || tempIndex < textIndex) {
+ textIndex = tempIndex;
+ replaceIndex = i;
+ }
+ }
+ }
+ // NOTE: logic duplicated above END
+
+ }
+ final int textLength = text.length();
+ for (int i = start; i < textLength; i++) {
+ buf.append(text.charAt(i));
+ }
+ final String result = buf.toString();
+ if (!repeat) {
+ return result;
+ }
+
+ return replaceEach(result, searchList, replacementList, repeat, timeToLive - 1);
+ }
+
+ // Replace, character based
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.replaceChars(null, *, *) = null
+ * StringUtils.replaceChars("", *, *) = ""
+ * StringUtils.replaceChars("abcba", 'b', 'y') = "aycya"
+ * StringUtils.replaceChars("abcba", 'z', 'y') = "abcba"
+ *
+ *
+ * @param str String to replace characters in, may be null
+ * @param searchChar the character to search for, may be null
+ * @param replaceChar the character to replace, may be null
+ * @return modified String, {@code null} if null string input
+ * @since 2.0
+ */
+ public static String replaceChars(final String str, final char searchChar, final char replaceChar) {
+ if (str == null) {
+ return null;
+ }
+ return str.replace(searchChar, replaceChar);
+ }
+
+ /**
+ *
+ * replaceChars("hello", "ho", "jy") = jelly.
+ * StringUtils.replaceChars(null, *, *) = null
+ * StringUtils.replaceChars("", *, *) = ""
+ * StringUtils.replaceChars("abc", null, *) = "abc"
+ * StringUtils.replaceChars("abc", "", *) = "abc"
+ * StringUtils.replaceChars("abc", "b", null) = "ac"
+ * StringUtils.replaceChars("abc", "b", "") = "ac"
+ * StringUtils.replaceChars("abcba", "bc", "yz") = "ayzya"
+ * StringUtils.replaceChars("abcba", "bc", "y") = "ayya"
+ * StringUtils.replaceChars("abcba", "bc", "yzx") = "ayzya"
+ *
+ *
+ * @param str String to replace characters in, may be null
+ * @param searchChars a set of characters to search for, may be null
+ * @param replaceChars a set of characters to replace, may be null
+ * @return modified String, {@code null} if null string input
+ * @since 2.0
+ */
+ public static String replaceChars(final String str, final String searchChars, String replaceChars) {
+ if (isEmpty(str) || isEmpty(searchChars)) {
+ return str;
+ }
+ if (replaceChars == null) {
+ replaceChars = EMPTY;
+ }
+ boolean modified = false;
+ final int replaceCharsLength = replaceChars.length();
+ final int strLength = str.length();
+ final StringBuilder buf = new StringBuilder(strLength);
+ for (int i = 0; i < strLength; i++) {
+ final char ch = str.charAt(i);
+ final int index = searchChars.indexOf(ch);
+ if (index >= 0) {
+ modified = true;
+ if (index < replaceCharsLength) {
+ buf.append(replaceChars.charAt(index));
+ }
+ } else {
+ buf.append(ch);
+ }
+ }
+ if (modified) {
+ return buf.toString();
+ }
+ return str;
+ }
+
+ // Overlay
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.overlay(null, *, *, *) = null
+ * StringUtils.overlay("", "abc", 0, 0) = "abc"
+ * StringUtils.overlay("abcdef", null, 2, 4) = "abef"
+ * StringUtils.overlay("abcdef", "", 2, 4) = "abef"
+ * StringUtils.overlay("abcdef", "", 4, 2) = "abef"
+ * StringUtils.overlay("abcdef", "zzzz", 2, 4) = "abzzzzef"
+ * StringUtils.overlay("abcdef", "zzzz", 4, 2) = "abzzzzef"
+ * StringUtils.overlay("abcdef", "zzzz", -1, 4) = "zzzzef"
+ * StringUtils.overlay("abcdef", "zzzz", 2, 8) = "abzzzz"
+ * StringUtils.overlay("abcdef", "zzzz", -2, -3) = "zzzzabcdef"
+ * StringUtils.overlay("abcdef", "zzzz", 8, 10) = "abcdefzzzz"
+ *
+ *
+ * @param str the String to do overlaying in, may be null
+ * @param overlay the String to overlay, may be null
+ * @param start the position to start overlaying at
+ * @param end the position to stop overlaying before
+ * @return overlayed String, {@code null} if null String input
+ * @since 2.0
+ */
+ public static String overlay(final String str, String overlay, int start, int end) {
+ if (str == null) {
+ return null;
+ }
+ if (overlay == null) {
+ overlay = EMPTY;
+ }
+ final int len = str.length();
+ if (start < 0) {
+ start = 0;
+ }
+ if (start > len) {
+ start = len;
+ }
+ if (end < 0) {
+ end = 0;
+ }
+ if (end > len) {
+ end = len;
+ }
+ if (start > end) {
+ final int temp = start;
+ start = end;
+ end = temp;
+ }
+ return new StringBuilder(len + start - end + overlay.length() + 1)
+ .append(str.substring(0, start))
+ .append(overlay)
+ .append(str.substring(end))
+ .toString();
+ }
+
+ // Chomping
+ //-----------------------------------------------------------------------
+
+ /**
+ *
+ * StringUtils.chomp(null, *) = null
+ * StringUtils.chomp("", *) = ""
+ * StringUtils.chomp("foobar", "bar") = "foo"
+ * StringUtils.chomp("foobar", "baz") = "foobar"
+ * StringUtils.chomp("foo", "foo") = ""
+ * StringUtils.chomp("foo ", "foo") = "foo "
+ * StringUtils.chomp(" foo", "foo") = " "
+ * StringUtils.chomp("foo", "foooo") = "foo"
+ * StringUtils.chomp("foo", "") = "foo"
+ * StringUtils.chomp("foo", null) = "foo"
+ *
+ *
+ * @param str the String to chomp from, may be null
+ * @param separator separator String, may be null
+ * @return String without trailing separator, {@code null} if null String input
+ * @deprecated This feature will be removed in Lang 4.0, use {@link StringUtils#removeEnd(String, String)} instead
+ */
+ @Deprecated
+ public static String chomp(final String str, final String separator) {
+ return removeEnd(str,separator);
+ }
+
+ // Chopping
+ //-----------------------------------------------------------------------
+
+ // Conversion
+ //-----------------------------------------------------------------------
+
+ // Padding
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.repeat(null, 2) = null
+ * StringUtils.repeat("", 0) = ""
+ * StringUtils.repeat("", 2) = ""
+ * StringUtils.repeat("a", 3) = "aaa"
+ * StringUtils.repeat("ab", 2) = "abab"
+ * StringUtils.repeat("a", -2) = ""
+ *
+ *
+ * @param str the String to repeat, may be null
+ * @param repeat number of times to repeat str, negative treated as zero
+ * @return a new String consisting of the original String repeated,
+ * {@code null} if null String input
+ */
+ public static String repeat(final String str, final int repeat) {
+ // Performance tuned for 2.0 (JDK1.4)
+
+ if (str == null) {
+ return null;
+ }
+ if (repeat <= 0) {
+ return EMPTY;
+ }
+ final int inputLength = str.length();
+ if (repeat == 1 || inputLength == 0) {
+ return str;
+ }
+ if (inputLength == 1 && repeat <= PAD_LIMIT) {
+ return repeat(str.charAt(0), repeat);
+ }
+
+ final int outputLength = inputLength * repeat;
+ switch (inputLength) {
+ case 1 :
+ return repeat(str.charAt(0), repeat);
+ case 2 :
+ final char ch0 = str.charAt(0);
+ final char ch1 = str.charAt(1);
+ final char[] output2 = new char[outputLength];
+ for (int i = repeat * 2 - 2; i >= 0; i--, i--) {
+ output2[i] = ch0;
+ output2[i + 1] = ch1;
+ }
+ return new String(output2);
+ default :
+ final StringBuilder buf = new StringBuilder(outputLength);
+ for (int i = 0; i < repeat; i++) {
+ buf.append(str);
+ }
+ return buf.toString();
+ }
+ }
+
+ /**
+ *
+ * StringUtils.repeat(null, null, 2) = null
+ * StringUtils.repeat(null, "x", 2) = null
+ * StringUtils.repeat("", null, 0) = ""
+ * StringUtils.repeat("", "", 2) = ""
+ * StringUtils.repeat("", "x", 3) = "xxx"
+ * StringUtils.repeat("?", ", ", 3) = "?, ?, ?"
+ *
+ *
+ * @param str the String to repeat, may be null
+ * @param separator the String to inject, may be null
+ * @param repeat number of times to repeat str, negative treated as zero
+ * @return a new String consisting of the original String repeated,
+ * {@code null} if null String input
+ * @since 2.5
+ */
+ public static String repeat(final String str, final String separator, final int repeat) {
+ if(str == null || separator == null) {
+ return repeat(str, repeat);
+ }
+ // given that repeat(String, int) is quite optimized, better to rely on it than try and splice this into it
+ final String result = repeat(str + separator, repeat);
+ return removeEnd(result, separator);
+ }
+
+ /**
+ *
+ * StringUtils.repeat('e', 0) = ""
+ * StringUtils.repeat('e', 3) = "eee"
+ * StringUtils.repeat('e', -2) = ""
+ *
+ *
+ *
+ * StringUtils.rightPad(null, *) = null
+ * StringUtils.rightPad("", 3) = " "
+ * StringUtils.rightPad("bat", 3) = "bat"
+ * StringUtils.rightPad("bat", 5) = "bat "
+ * StringUtils.rightPad("bat", 1) = "bat"
+ * StringUtils.rightPad("bat", -1) = "bat"
+ *
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @return right padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ */
+ public static String rightPad(final String str, final int size) {
+ return rightPad(str, size, ' ');
+ }
+
+ /**
+ *
+ * StringUtils.rightPad(null, *, *) = null
+ * StringUtils.rightPad("", 3, 'z') = "zzz"
+ * StringUtils.rightPad("bat", 3, 'z') = "bat"
+ * StringUtils.rightPad("bat", 5, 'z') = "batzz"
+ * StringUtils.rightPad("bat", 1, 'z') = "bat"
+ * StringUtils.rightPad("bat", -1, 'z') = "bat"
+ *
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @param padChar the character to pad with
+ * @return right padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ * @since 2.0
+ */
+ public static String rightPad(final String str, final int size, final char padChar) {
+ if (str == null) {
+ return null;
+ }
+ final int pads = size - str.length();
+ if (pads <= 0) {
+ return str; // returns original String when possible
+ }
+ if (pads > PAD_LIMIT) {
+ return rightPad(str, size, String.valueOf(padChar));
+ }
+ return str.concat(repeat(padChar, pads));
+ }
+
+ /**
+ *
+ * StringUtils.rightPad(null, *, *) = null
+ * StringUtils.rightPad("", 3, "z") = "zzz"
+ * StringUtils.rightPad("bat", 3, "yz") = "bat"
+ * StringUtils.rightPad("bat", 5, "yz") = "batyz"
+ * StringUtils.rightPad("bat", 8, "yz") = "batyzyzy"
+ * StringUtils.rightPad("bat", 1, "yz") = "bat"
+ * StringUtils.rightPad("bat", -1, "yz") = "bat"
+ * StringUtils.rightPad("bat", 5, null) = "bat "
+ * StringUtils.rightPad("bat", 5, "") = "bat "
+ *
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @param padStr the String to pad with, null or empty treated as single space
+ * @return right padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ */
+ public static String rightPad(final String str, final int size, String padStr) {
+ if (str == null) {
+ return null;
+ }
+ if (isEmpty(padStr)) {
+ padStr = SPACE;
+ }
+ final int padLen = padStr.length();
+ final int strLen = str.length();
+ final int pads = size - strLen;
+ if (pads <= 0) {
+ return str; // returns original String when possible
+ }
+ if (padLen == 1 && pads <= PAD_LIMIT) {
+ return rightPad(str, size, padStr.charAt(0));
+ }
+
+ if (pads == padLen) {
+ return str.concat(padStr);
+ } else if (pads < padLen) {
+ return str.concat(padStr.substring(0, pads));
+ } else {
+ final char[] padding = new char[pads];
+ final char[] padChars = padStr.toCharArray();
+ for (int i = 0; i < pads; i++) {
+ padding[i] = padChars[i % padLen];
+ }
+ return str.concat(new String(padding));
+ }
+ }
+
+ /**
+ *
+ * StringUtils.leftPad(null, *) = null
+ * StringUtils.leftPad("", 3) = " "
+ * StringUtils.leftPad("bat", 3) = "bat"
+ * StringUtils.leftPad("bat", 5) = " bat"
+ * StringUtils.leftPad("bat", 1) = "bat"
+ * StringUtils.leftPad("bat", -1) = "bat"
+ *
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @return left padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ */
+ public static String leftPad(final String str, final int size) {
+ return leftPad(str, size, ' ');
+ }
+
+ /**
+ *
+ * StringUtils.leftPad(null, *, *) = null
+ * StringUtils.leftPad("", 3, 'z') = "zzz"
+ * StringUtils.leftPad("bat", 3, 'z') = "bat"
+ * StringUtils.leftPad("bat", 5, 'z') = "zzbat"
+ * StringUtils.leftPad("bat", 1, 'z') = "bat"
+ * StringUtils.leftPad("bat", -1, 'z') = "bat"
+ *
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @param padChar the character to pad with
+ * @return left padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ * @since 2.0
+ */
+ public static String leftPad(final String str, final int size, final char padChar) {
+ if (str == null) {
+ return null;
+ }
+ final int pads = size - str.length();
+ if (pads <= 0) {
+ return str; // returns original String when possible
+ }
+ if (pads > PAD_LIMIT) {
+ return leftPad(str, size, String.valueOf(padChar));
+ }
+ return repeat(padChar, pads).concat(str);
+ }
+
+ /**
+ *
+ * StringUtils.leftPad(null, *, *) = null
+ * StringUtils.leftPad("", 3, "z") = "zzz"
+ * StringUtils.leftPad("bat", 3, "yz") = "bat"
+ * StringUtils.leftPad("bat", 5, "yz") = "yzbat"
+ * StringUtils.leftPad("bat", 8, "yz") = "yzyzybat"
+ * StringUtils.leftPad("bat", 1, "yz") = "bat"
+ * StringUtils.leftPad("bat", -1, "yz") = "bat"
+ * StringUtils.leftPad("bat", 5, null) = " bat"
+ * StringUtils.leftPad("bat", 5, "") = " bat"
+ *
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @param padStr the String to pad with, null or empty treated as single space
+ * @return left padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ */
+ public static String leftPad(final String str, final int size, String padStr) {
+ if (str == null) {
+ return null;
+ }
+ if (isEmpty(padStr)) {
+ padStr = SPACE;
+ }
+ final int padLen = padStr.length();
+ final int strLen = str.length();
+ final int pads = size - strLen;
+ if (pads <= 0) {
+ return str; // returns original String when possible
+ }
+ if (padLen == 1 && pads <= PAD_LIMIT) {
+ return leftPad(str, size, padStr.charAt(0));
+ }
+
+ if (pads == padLen) {
+ return padStr.concat(str);
+ } else if (pads < padLen) {
+ return padStr.substring(0, pads).concat(str);
+ } else {
+ final char[] padding = new char[pads];
+ final char[] padChars = padStr.toCharArray();
+ for (int i = 0; i < pads; i++) {
+ padding[i] = padChars[i % padLen];
+ }
+ return new String(padding).concat(str);
+ }
+ }
+
+ /**
+ * Gets a CharSequence length or {@code 0} if the CharSequence is
+ * {@code null}.
+ *
+ * @param cs
+ * a CharSequence or {@code null}
+ * @return CharSequence length or {@code 0} if the CharSequence is
+ * {@code null}.
+ * @since 2.4
+ * @since 3.0 Changed signature from length(String) to length(CharSequence)
+ */
+ public static int length(final CharSequence cs) {
+ return cs == null ? 0 : cs.length();
+ }
+
+ // Centering
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.center(null, *) = null
+ * StringUtils.center("", 4) = " "
+ * StringUtils.center("ab", -1) = "ab"
+ * StringUtils.center("ab", 4) = " ab "
+ * StringUtils.center("abcd", 2) = "abcd"
+ * StringUtils.center("a", 4) = " a "
+ *
+ *
+ * @param str the String to center, may be null
+ * @param size the int size of new String, negative treated as zero
+ * @return centered String, {@code null} if null String input
+ */
+ public static String center(final String str, final int size) {
+ return center(str, size, ' ');
+ }
+
+ /**
+ *
+ * StringUtils.center(null, *, *) = null
+ * StringUtils.center("", 4, ' ') = " "
+ * StringUtils.center("ab", -1, ' ') = "ab"
+ * StringUtils.center("ab", 4, ' ') = " ab "
+ * StringUtils.center("abcd", 2, ' ') = "abcd"
+ * StringUtils.center("a", 4, ' ') = " a "
+ * StringUtils.center("a", 4, 'y') = "yayy"
+ *
+ *
+ * @param str the String to center, may be null
+ * @param size the int size of new String, negative treated as zero
+ * @param padChar the character to pad the new String with
+ * @return centered String, {@code null} if null String input
+ * @since 2.0
+ */
+ public static String center(String str, final int size, final char padChar) {
+ if (str == null || size <= 0) {
+ return str;
+ }
+ final int strLen = str.length();
+ final int pads = size - strLen;
+ if (pads <= 0) {
+ return str;
+ }
+ str = leftPad(str, strLen + pads / 2, padChar);
+ str = rightPad(str, size, padChar);
+ return str;
+ }
+
+ /**
+ *
+ * StringUtils.center(null, *, *) = null
+ * StringUtils.center("", 4, " ") = " "
+ * StringUtils.center("ab", -1, " ") = "ab"
+ * StringUtils.center("ab", 4, " ") = " ab "
+ * StringUtils.center("abcd", 2, " ") = "abcd"
+ * StringUtils.center("a", 4, " ") = " a "
+ * StringUtils.center("a", 4, "yz") = "yayz"
+ * StringUtils.center("abc", 7, null) = " abc "
+ * StringUtils.center("abc", 7, "") = " abc "
+ *
+ *
+ * @param str the String to center, may be null
+ * @param size the int size of new String, negative treated as zero
+ * @param padStr the String to pad the new String with, must not be null or empty
+ * @return centered String, {@code null} if null String input
+ * @throws IllegalArgumentException if padStr is {@code null} or empty
+ */
+ public static String center(String str, final int size, String padStr) {
+ if (str == null || size <= 0) {
+ return str;
+ }
+ if (isEmpty(padStr)) {
+ padStr = SPACE;
+ }
+ final int strLen = str.length();
+ final int pads = size - strLen;
+ if (pads <= 0) {
+ return str;
+ }
+ str = leftPad(str, strLen + pads / 2, padStr);
+ str = rightPad(str, size, padStr);
+ return str;
+ }
+
+ // Case conversion
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.upperCase(null) = null
+ * StringUtils.upperCase("") = ""
+ * StringUtils.upperCase("aBc") = "ABC"
+ *
+ *
+ *
+ * StringUtils.upperCase(null, Locale.ENGLISH) = null
+ * StringUtils.upperCase("", Locale.ENGLISH) = ""
+ * StringUtils.upperCase("aBc", Locale.ENGLISH) = "ABC"
+ *
+ *
+ * @param str the String to upper case, may be null
+ * @param locale the locale that defines the case transformation rules, must not be null
+ * @return the upper cased String, {@code null} if null String input
+ * @since 2.5
+ */
+ public static String upperCase(final String str, final Locale locale) {
+ if (str == null) {
+ return null;
+ }
+ return str.toUpperCase(locale);
+ }
+
+ /**
+ *
+ * StringUtils.lowerCase(null) = null
+ * StringUtils.lowerCase("") = ""
+ * StringUtils.lowerCase("aBc") = "abc"
+ *
+ *
+ *
+ * StringUtils.lowerCase(null, Locale.ENGLISH) = null
+ * StringUtils.lowerCase("", Locale.ENGLISH) = ""
+ * StringUtils.lowerCase("aBc", Locale.ENGLISH) = "abc"
+ *
+ *
+ * @param str the String to lower case, may be null
+ * @param locale the locale that defines the case transformation rules, must not be null
+ * @return the lower cased String, {@code null} if null String input
+ * @since 2.5
+ */
+ public static String lowerCase(final String str, final Locale locale) {
+ if (str == null) {
+ return null;
+ }
+ return str.toLowerCase(locale);
+ }
+
+ /**
+ *
+ * StringUtils.capitalize(null) = null
+ * StringUtils.capitalize("") = ""
+ * StringUtils.capitalize("cat") = "Cat"
+ * StringUtils.capitalize("cAt") = "CAt"
+ * StringUtils.capitalize("'cat'") = "'cat'"
+ *
+ *
+ * @param str the String to capitalize, may be null
+ * @return the capitalized String, {@code null} if null String input
+ * @see #uncapitalize(String)
+ * @since 2.0
+ */
+ public static String capitalize(final String str) {
+ int strLen;
+ if (str == null || (strLen = str.length()) == 0) {
+ return str;
+ }
+
+ final char firstChar = str.charAt(0);
+ final char newChar = Character.toTitleCase(firstChar);
+ if (firstChar == newChar) {
+ // already capitalized
+ return str;
+ }
+
+ char[] newChars = new char[strLen];
+ newChars[0] = newChar;
+ str.getChars(1,strLen, newChars, 1);
+ return String.valueOf(newChars);
+ }
+
+ /**
+ *
+ * StringUtils.uncapitalize(null) = null
+ * StringUtils.uncapitalize("") = ""
+ * StringUtils.uncapitalize("cat") = "cat"
+ * StringUtils.uncapitalize("Cat") = "cat"
+ * StringUtils.uncapitalize("CAT") = "cAT"
+ *
+ *
+ * @param str the String to uncapitalize, may be null
+ * @return the uncapitalized String, {@code null} if null String input
+ * @see #capitalize(String)
+ * @since 2.0
+ */
+ public static String uncapitalize(final String str) {
+ int strLen;
+ if (str == null || (strLen = str.length()) == 0) {
+ return str;
+ }
+
+ final char firstChar = str.charAt(0);
+ final char newChar = Character.toLowerCase(firstChar);
+ if (firstChar == newChar) {
+ // already uncapitalized
+ return str;
+ }
+
+ char[] newChars = new char[strLen];
+ newChars[0] = newChar;
+ str.getChars(1,strLen, newChars, 1);
+ return String.valueOf(newChars);
+ }
+
+ /**
+ *
+ *
+ *
+ *
+ * StringUtils.swapCase(null) = null
+ * StringUtils.swapCase("") = ""
+ * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
+ *
+ *
+ *
+ * StringUtils.countMatches(null, *) = 0
+ * StringUtils.countMatches("", *) = 0
+ * StringUtils.countMatches("abba", 0) = 0
+ * StringUtils.countMatches("abba", 'a') = 2
+ * StringUtils.countMatches("abba", 'b') = 2
+ * StringUtils.countMatches("abba", 'x') = 0
+ *
+ *
+ * @param str the CharSequence to check, may be null
+ * @param ch the char to count
+ * @return the number of occurrences, 0 if the CharSequence is {@code null}
+ * @since 3.4
+ */
+ public static int countMatches(final CharSequence str, final char ch) {
+ if (isEmpty(str)) {
+ return 0;
+ }
+ int count = 0;
+ // We could also call str.toCharArray() for faster look ups but that would generate more garbage.
+ for (int i = 0; i < str.length(); i++) {
+ if (ch == str.charAt(i)) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ // Character Tests
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.isAlpha(null) = false
+ * StringUtils.isAlpha("") = false
+ * StringUtils.isAlpha(" ") = false
+ * StringUtils.isAlpha("abc") = true
+ * StringUtils.isAlpha("ab2c") = false
+ * StringUtils.isAlpha("ab-c") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains letters, and is non-null
+ * @since 3.0 Changed signature from isAlpha(String) to isAlpha(CharSequence)
+ * @since 3.0 Changed "" to return false and not true
+ */
+ public static boolean isAlpha(final CharSequence cs) {
+ if (isEmpty(cs)) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isLetter(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
+ * StringUtils.isAlphaSpace(null) = false
+ * StringUtils.isAlphaSpace("") = true
+ * StringUtils.isAlphaSpace(" ") = true
+ * StringUtils.isAlphaSpace("abc") = true
+ * StringUtils.isAlphaSpace("ab c") = true
+ * StringUtils.isAlphaSpace("ab2c") = false
+ * StringUtils.isAlphaSpace("ab-c") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains letters and space,
+ * and is non-null
+ * @since 3.0 Changed signature from isAlphaSpace(String) to isAlphaSpace(CharSequence)
+ */
+ public static boolean isAlphaSpace(final CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isLetter(cs.charAt(i)) == false && cs.charAt(i) != ' ') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
+ * StringUtils.isAlphanumeric(null) = false
+ * StringUtils.isAlphanumeric("") = false
+ * StringUtils.isAlphanumeric(" ") = false
+ * StringUtils.isAlphanumeric("abc") = true
+ * StringUtils.isAlphanumeric("ab c") = false
+ * StringUtils.isAlphanumeric("ab2c") = true
+ * StringUtils.isAlphanumeric("ab-c") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains letters or digits,
+ * and is non-null
+ * @since 3.0 Changed signature from isAlphanumeric(String) to isAlphanumeric(CharSequence)
+ * @since 3.0 Changed "" to return false and not true
+ */
+ public static boolean isAlphanumeric(final CharSequence cs) {
+ if (isEmpty(cs)) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isLetterOrDigit(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
+ * StringUtils.isAlphanumericSpace(null) = false
+ * StringUtils.isAlphanumericSpace("") = true
+ * StringUtils.isAlphanumericSpace(" ") = true
+ * StringUtils.isAlphanumericSpace("abc") = true
+ * StringUtils.isAlphanumericSpace("ab c") = true
+ * StringUtils.isAlphanumericSpace("ab2c") = true
+ * StringUtils.isAlphanumericSpace("ab-c") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains letters, digits or space,
+ * and is non-null
+ * @since 3.0 Changed signature from isAlphanumericSpace(String) to isAlphanumericSpace(CharSequence)
+ */
+ public static boolean isAlphanumericSpace(final CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isLetterOrDigit(cs.charAt(i)) == false && cs.charAt(i) != ' ') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
+ * StringUtils.isNumeric(null) = false
+ * StringUtils.isNumeric("") = false
+ * StringUtils.isNumeric(" ") = false
+ * StringUtils.isNumeric("123") = true
+ * StringUtils.isNumeric("\u0967\u0968\u0969") = true
+ * StringUtils.isNumeric("12 3") = false
+ * StringUtils.isNumeric("ab2c") = false
+ * StringUtils.isNumeric("12-3") = false
+ * StringUtils.isNumeric("12.3") = false
+ * StringUtils.isNumeric("-123") = false
+ * StringUtils.isNumeric("+123") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains digits, and is non-null
+ * @since 3.0 Changed signature from isNumeric(String) to isNumeric(CharSequence)
+ * @since 3.0 Changed "" to return false and not true
+ */
+ public static boolean isNumeric(final CharSequence cs) {
+ if (isEmpty(cs)) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (!Character.isDigit(cs.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
+ * StringUtils.isNumericSpace(null) = false
+ * StringUtils.isNumericSpace("") = true
+ * StringUtils.isNumericSpace(" ") = true
+ * StringUtils.isNumericSpace("123") = true
+ * StringUtils.isNumericSpace("12 3") = true
+ * StringUtils.isNumeric("\u0967\u0968\u0969") = true
+ * StringUtils.isNumeric("\u0967\u0968 \u0969") = true
+ * StringUtils.isNumericSpace("ab2c") = false
+ * StringUtils.isNumericSpace("12-3") = false
+ * StringUtils.isNumericSpace("12.3") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains digits or space,
+ * and is non-null
+ * @since 3.0 Changed signature from isNumericSpace(String) to isNumericSpace(CharSequence)
+ */
+ public static boolean isNumericSpace(final CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isDigit(cs.charAt(i)) == false && cs.charAt(i) != ' ') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
+ * StringUtils.isWhitespace(null) = false
+ * StringUtils.isWhitespace("") = true
+ * StringUtils.isWhitespace(" ") = true
+ * StringUtils.isWhitespace("abc") = false
+ * StringUtils.isWhitespace("ab2c") = false
+ * StringUtils.isWhitespace("ab-c") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains whitespace, and is non-null
+ * @since 2.0
+ * @since 3.0 Changed signature from isWhitespace(String) to isWhitespace(CharSequence)
+ */
+ public static boolean isWhitespace(final CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isWhitespace(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
+ * StringUtils.isAllLowerCase(null) = false
+ * StringUtils.isAllLowerCase("") = false
+ * StringUtils.isAllLowerCase(" ") = false
+ * StringUtils.isAllLowerCase("abc") = true
+ * StringUtils.isAllLowerCase("abC") = false
+ * StringUtils.isAllLowerCase("ab c") = false
+ * StringUtils.isAllLowerCase("ab1c") = false
+ * StringUtils.isAllLowerCase("ab/c") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains lowercase characters, and is non-null
+ * @since 2.5
+ * @since 3.0 Changed signature from isAllLowerCase(String) to isAllLowerCase(CharSequence)
+ */
+ public static boolean isAllLowerCase(final CharSequence cs) {
+ if (cs == null || isEmpty(cs)) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isLowerCase(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
+ * StringUtils.isAllUpperCase(null) = false
+ * StringUtils.isAllUpperCase("") = false
+ * StringUtils.isAllUpperCase(" ") = false
+ * StringUtils.isAllUpperCase("ABC") = true
+ * StringUtils.isAllUpperCase("aBC") = false
+ * StringUtils.isAllUpperCase("A C") = false
+ * StringUtils.isAllUpperCase("A1C") = false
+ * StringUtils.isAllUpperCase("A/C") = false
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains uppercase characters, and is non-null
+ * @since 2.5
+ * @since 3.0 Changed signature from isAllUpperCase(String) to isAllUpperCase(CharSequence)
+ */
+ public static boolean isAllUpperCase(final CharSequence cs) {
+ if (cs == null || isEmpty(cs)) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isUpperCase(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Defaults
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.defaultString(null) = ""
+ * StringUtils.defaultString("") = ""
+ * StringUtils.defaultString("bat") = "bat"
+ *
+ *
+ * @see String#valueOf(Object)
+ * @param str the String to check, may be null
+ * @return the passed in String, or the empty String if it
+ * was {@code null}
+ */
+ public static String defaultString(final String str) {
+ return str == null ? EMPTY : str;
+ }
+
+ /**
+ *
+ * StringUtils.defaultString(null, "NULL") = "NULL"
+ * StringUtils.defaultString("", "NULL") = ""
+ * StringUtils.defaultString("bat", "NULL") = "bat"
+ *
+ *
+ * @see String#valueOf(Object)
+ * @param str the String to check, may be null
+ * @param defaultStr the default String to return
+ * if the input is {@code null}, may be null
+ * @return the passed in String, or the default if it was {@code null}
+ */
+ public static String defaultString(final String str, final String defaultStr) {
+ return str == null ? defaultStr : str;
+ }
+
+ /**
+ *
+ * StringUtils.defaultIfBlank(null, "NULL") = "NULL"
+ * StringUtils.defaultIfBlank("", "NULL") = "NULL"
+ * StringUtils.defaultIfBlank(" ", "NULL") = "NULL"
+ * StringUtils.defaultIfBlank("bat", "NULL") = "bat"
+ * StringUtils.defaultIfBlank("", null) = null
+ *
+ * @param
+ * StringUtils.defaultIfEmpty(null, "NULL") = "NULL"
+ * StringUtils.defaultIfEmpty("", "NULL") = "NULL"
+ * StringUtils.defaultIfEmpty(" ", "NULL") = " "
+ * StringUtils.defaultIfEmpty("bat", "NULL") = "bat"
+ * StringUtils.defaultIfEmpty("", null) = null
+ *
+ * @param
+ *
+ *
+ *
+ * StringUtils.rotate(null, *) = null
+ * StringUtils.rotate("", *) = ""
+ * StringUtils.rotate("abcdefg", 0) = "abcdefg"
+ * StringUtils.rotate("abcdefg", 2) = "fgabcde"
+ * StringUtils.rotate("abcdefg", -2) = "cdefgab"
+ * StringUtils.rotate("abcdefg", 7) = "abcdefg"
+ * StringUtils.rotate("abcdefg", -7) = "abcdefg"
+ * StringUtils.rotate("abcdefg", 9) = "fgabcde"
+ * StringUtils.rotate("abcdefg", -9) = "cdefgab"
+ *
+ *
+ * @param str the String to rotate, may be null
+ * @param shift number of time to shift (positive : right shift, negative : left shift)
+ * @return the rotated String,
+ * or the original String if {@code shift == 0},
+ * or {@code null} if null String input
+ */
+ public static String rotate(String str, int shift) {
+ if (str == null) {
+ return null;
+ }
+
+ final int strLen = str.length();
+ if (shift == 0 || strLen == 0 || shift % strLen == 0) {
+ return str;
+ }
+
+ final StringBuilder builder = new StringBuilder(strLen);
+ final int offset = - (shift % strLen);
+ builder.append(substring(str, offset));
+ builder.append(substring(str, 0, offset));
+ return builder.toString();
+ }
+
+ // Reversing
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.reverse(null) = null
+ * StringUtils.reverse("") = ""
+ * StringUtils.reverse("bat") = "tab"
+ *
+ *
+ * @param str the String to reverse, may be null
+ * @return the reversed String, {@code null} if null String input
+ */
+ public static String reverse(final String str) {
+ if (str == null) {
+ return null;
+ }
+ return new StringBuilder(str).reverse().toString();
+ }
+
+ // Abbreviating
+ //-----------------------------------------------------------------------
+ /**
+ *
+ *
+ *
+ *
+ * StringUtils.abbreviate(null, *) = null
+ * StringUtils.abbreviate("", 4) = ""
+ * StringUtils.abbreviate("abcdefg", 6) = "abc..."
+ * StringUtils.abbreviate("abcdefg", 7) = "abcdefg"
+ * StringUtils.abbreviate("abcdefg", 8) = "abcdefg"
+ * StringUtils.abbreviate("abcdefg", 4) = "a..."
+ * StringUtils.abbreviate("abcdefg", 3) = IllegalArgumentException
+ *
+ *
+ * @param str the String to check, may be null
+ * @param maxWidth maximum length of result String, must be at least 4
+ * @return abbreviated String, {@code null} if null String input
+ * @throws IllegalArgumentException if the width is too small
+ * @since 2.0
+ */
+ public static String abbreviate(final String str, final int maxWidth) {
+ return abbreviate(str, 0, maxWidth);
+ }
+
+ /**
+ *
+ * StringUtils.abbreviate(null, *, *) = null
+ * StringUtils.abbreviate("", 0, 4) = ""
+ * StringUtils.abbreviate("abcdefghijklmno", -1, 10) = "abcdefg..."
+ * StringUtils.abbreviate("abcdefghijklmno", 0, 10) = "abcdefg..."
+ * StringUtils.abbreviate("abcdefghijklmno", 1, 10) = "abcdefg..."
+ * StringUtils.abbreviate("abcdefghijklmno", 4, 10) = "abcdefg..."
+ * StringUtils.abbreviate("abcdefghijklmno", 5, 10) = "...fghi..."
+ * StringUtils.abbreviate("abcdefghijklmno", 6, 10) = "...ghij..."
+ * StringUtils.abbreviate("abcdefghijklmno", 8, 10) = "...ijklmno"
+ * StringUtils.abbreviate("abcdefghijklmno", 10, 10) = "...ijklmno"
+ * StringUtils.abbreviate("abcdefghijklmno", 12, 10) = "...ijklmno"
+ * StringUtils.abbreviate("abcdefghij", 0, 3) = IllegalArgumentException
+ * StringUtils.abbreviate("abcdefghij", 5, 6) = IllegalArgumentException
+ *
+ *
+ * @param str the String to check, may be null
+ * @param offset left edge of source String
+ * @param maxWidth maximum length of result String, must be at least 4
+ * @return abbreviated String, {@code null} if null String input
+ * @throws IllegalArgumentException if the width is too small
+ * @since 2.0
+ */
+ public static String abbreviate(final String str, int offset, final int maxWidth) {
+ if (str == null) {
+ return null;
+ }
+ if (maxWidth < 4) {
+ throw new IllegalArgumentException("Minimum abbreviation width is 4");
+ }
+ if (str.length() <= maxWidth) {
+ return str;
+ }
+ if (offset > str.length()) {
+ offset = str.length();
+ }
+ if (str.length() - offset < maxWidth - 3) {
+ offset = str.length() - (maxWidth - 3);
+ }
+ final String abrevMarker = "...";
+ if (offset <= 4) {
+ return str.substring(0, maxWidth - 3) + abrevMarker;
+ }
+ if (maxWidth < 7) {
+ throw new IllegalArgumentException("Minimum abbreviation width with offset is 7");
+ }
+ if (offset + maxWidth - 3 < str.length()) {
+ return abrevMarker + abbreviate(str.substring(offset), maxWidth - 3);
+ }
+ return abrevMarker + str.substring(str.length() - (maxWidth - 3));
+ }
+
+ /**
+ *
+ *
+ *
+ * StringUtils.abbreviateMiddle(null, null, 0) = null
+ * StringUtils.abbreviateMiddle("abc", null, 0) = "abc"
+ * StringUtils.abbreviateMiddle("abc", ".", 0) = "abc"
+ * StringUtils.abbreviateMiddle("abc", ".", 3) = "abc"
+ * StringUtils.abbreviateMiddle("abcdef", ".", 4) = "ab.f"
+ *
+ *
+ * @param str the String to abbreviate, may be null
+ * @param middle the String to replace the middle characters with, may be null
+ * @param length the length to abbreviate {@code str} to.
+ * @return the abbreviated String if the above criteria is met, or the original String supplied for abbreviation.
+ * @since 2.5
+ */
+ public static String abbreviateMiddle(final String str, final String middle, final int length) {
+ if (isEmpty(str) || isEmpty(middle)) {
+ return str;
+ }
+
+ if (length >= str.length() || length < middle.length()+2) {
+ return str;
+ }
+
+ final int targetSting = length-middle.length();
+ final int startOffset = targetSting/2+targetSting%2;
+ final int endOffset = str.length()-targetSting/2;
+
+ final StringBuilder builder = new StringBuilder(length);
+ builder.append(str.substring(0,startOffset));
+ builder.append(middle);
+ builder.append(str.substring(endOffset));
+
+ return builder.toString();
+ }
+
+ // Difference
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * StringUtils.difference(null, null) = null
+ * StringUtils.difference("", "") = ""
+ * StringUtils.difference("", "abc") = "abc"
+ * StringUtils.difference("abc", "") = ""
+ * StringUtils.difference("abc", "abc") = ""
+ * StringUtils.difference("abc", "ab") = ""
+ * StringUtils.difference("ab", "abxyz") = "xyz"
+ * StringUtils.difference("abcde", "abxyz") = "xyz"
+ * StringUtils.difference("abcde", "xyz") = "xyz"
+ *
+ *
+ * @param str1 the first String, may be null
+ * @param str2 the second String, may be null
+ * @return the portion of str2 where it differs from str1; returns the
+ * empty String if they are equal
+ * @see #indexOfDifference(CharSequence,CharSequence)
+ * @since 2.0
+ */
+ public static String difference(final String str1, final String str2) {
+ if (str1 == null) {
+ return str2;
+ }
+ if (str2 == null) {
+ return str1;
+ }
+ final int at = indexOfDifference(str1, str2);
+ if (at == INDEX_NOT_FOUND) {
+ return EMPTY;
+ }
+ return str2.substring(at);
+ }
+
+ /**
+ *
+ * StringUtils.indexOfDifference(null, null) = -1
+ * StringUtils.indexOfDifference("", "") = -1
+ * StringUtils.indexOfDifference("", "abc") = 0
+ * StringUtils.indexOfDifference("abc", "") = 0
+ * StringUtils.indexOfDifference("abc", "abc") = -1
+ * StringUtils.indexOfDifference("ab", "abxyz") = 2
+ * StringUtils.indexOfDifference("abcde", "abxyz") = 2
+ * StringUtils.indexOfDifference("abcde", "xyz") = 0
+ *
+ *
+ * @param cs1 the first CharSequence, may be null
+ * @param cs2 the second CharSequence, may be null
+ * @return the index where cs1 and cs2 begin to differ; -1 if they are equal
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOfDifference(String, String) to
+ * indexOfDifference(CharSequence, CharSequence)
+ */
+ public static int indexOfDifference(final CharSequence cs1, final CharSequence cs2) {
+ if (cs1 == cs2) {
+ return INDEX_NOT_FOUND;
+ }
+ if (cs1 == null || cs2 == null) {
+ return 0;
+ }
+ int i;
+ for (i = 0; i < cs1.length() && i < cs2.length(); ++i) {
+ if (cs1.charAt(i) != cs2.charAt(i)) {
+ break;
+ }
+ }
+ if (i < cs2.length() || i < cs1.length()) {
+ return i;
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * indexOfDifference(new String[] {"i am a machine", "i am a robot"}) -> 7
+ * StringUtils.indexOfDifference(null) = -1
+ * StringUtils.indexOfDifference(new String[] {}) = -1
+ * StringUtils.indexOfDifference(new String[] {"abc"}) = -1
+ * StringUtils.indexOfDifference(new String[] {null, null}) = -1
+ * StringUtils.indexOfDifference(new String[] {"", ""}) = -1
+ * StringUtils.indexOfDifference(new String[] {"", null}) = 0
+ * StringUtils.indexOfDifference(new String[] {"abc", null, null}) = 0
+ * StringUtils.indexOfDifference(new String[] {null, null, "abc"}) = 0
+ * StringUtils.indexOfDifference(new String[] {"", "abc"}) = 0
+ * StringUtils.indexOfDifference(new String[] {"abc", ""}) = 0
+ * StringUtils.indexOfDifference(new String[] {"abc", "abc"}) = -1
+ * StringUtils.indexOfDifference(new String[] {"abc", "a"}) = 1
+ * StringUtils.indexOfDifference(new String[] {"ab", "abxyz"}) = 2
+ * StringUtils.indexOfDifference(new String[] {"abcde", "abxyz"}) = 2
+ * StringUtils.indexOfDifference(new String[] {"abcde", "xyz"}) = 0
+ * StringUtils.indexOfDifference(new String[] {"xyz", "abcde"}) = 0
+ * StringUtils.indexOfDifference(new String[] {"i am a machine", "i am a robot"}) = 7
+ *
+ *
+ * @param css array of CharSequences, entries may be null
+ * @return the index where the strings begin to differ; -1 if they are all equal
+ * @since 2.4
+ * @since 3.0 Changed signature from indexOfDifference(String...) to indexOfDifference(CharSequence...)
+ */
+ public static int indexOfDifference(final CharSequence... css) {
+ if (css == null || css.length <= 1) {
+ return INDEX_NOT_FOUND;
+ }
+ boolean anyStringNull = false;
+ boolean allStringsNull = true;
+ final int arrayLen = css.length;
+ int shortestStrLen = Integer.MAX_VALUE;
+ int longestStrLen = 0;
+
+ // find the min and max string lengths; this avoids checking to make
+ // sure we are not exceeding the length of the string each time through
+ // the bottom loop.
+ for (int i = 0; i < arrayLen; i++) {
+ if (css[i] == null) {
+ anyStringNull = true;
+ shortestStrLen = 0;
+ } else {
+ allStringsNull = false;
+ shortestStrLen = Math.min(css[i].length(), shortestStrLen);
+ longestStrLen = Math.max(css[i].length(), longestStrLen);
+ }
+ }
+
+ // handle lists containing all nulls or all empty strings
+ if (allStringsNull || longestStrLen == 0 && !anyStringNull) {
+ return INDEX_NOT_FOUND;
+ }
+
+ // handle lists containing some nulls or some empty strings
+ if (shortestStrLen == 0) {
+ return 0;
+ }
+
+ // find the position with the first difference across all strings
+ int firstDiff = -1;
+ for (int stringPos = 0; stringPos < shortestStrLen; stringPos++) {
+ final char comparisonChar = css[0].charAt(stringPos);
+ for (int arrayPos = 1; arrayPos < arrayLen; arrayPos++) {
+ if (css[arrayPos].charAt(stringPos) != comparisonChar) {
+ firstDiff = stringPos;
+ break;
+ }
+ }
+ if (firstDiff != -1) {
+ break;
+ }
+ }
+
+ if (firstDiff == -1 && shortestStrLen != longestStrLen) {
+ // we compared all of the characters up to the length of the
+ // shortest string and didn't find a match, but the string lengths
+ // vary, so return the length of the shortest string.
+ return shortestStrLen;
+ }
+ return firstDiff;
+ }
+
+ /**
+ * getCommonPrefix(new String[] {"i am a machine", "i am a robot"}) -> "i am a "
+ * StringUtils.getCommonPrefix(null) = ""
+ * StringUtils.getCommonPrefix(new String[] {}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"abc"}) = "abc"
+ * StringUtils.getCommonPrefix(new String[] {null, null}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"", ""}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"", null}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"abc", null, null}) = ""
+ * StringUtils.getCommonPrefix(new String[] {null, null, "abc"}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"", "abc"}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"abc", ""}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"abc", "abc"}) = "abc"
+ * StringUtils.getCommonPrefix(new String[] {"abc", "a"}) = "a"
+ * StringUtils.getCommonPrefix(new String[] {"ab", "abxyz"}) = "ab"
+ * StringUtils.getCommonPrefix(new String[] {"abcde", "abxyz"}) = "ab"
+ * StringUtils.getCommonPrefix(new String[] {"abcde", "xyz"}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"xyz", "abcde"}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"i am a machine", "i am a robot"}) = "i am a "
+ *
+ *
+ * @param strs array of String objects, entries may be null
+ * @return the initial sequence of characters that are common to all Strings
+ * in the array; empty String if the array is null, the elements are all null
+ * or if there is no common prefix.
+ * @since 2.4
+ */
+ public static String getCommonPrefix(final String... strs) {
+ if (strs == null || strs.length == 0) {
+ return EMPTY;
+ }
+ final int smallestIndexOfDiff = indexOfDifference(strs);
+ if (smallestIndexOfDiff == INDEX_NOT_FOUND) {
+ // all strings were identical
+ if (strs[0] == null) {
+ return EMPTY;
+ }
+ return strs[0];
+ } else if (smallestIndexOfDiff == 0) {
+ // there were no common initial characters
+ return EMPTY;
+ } else {
+ // we found a common initial character sequence
+ return strs[0].substring(0, smallestIndexOfDiff);
+ }
+ }
+
+ // Misc
+ //-----------------------------------------------------------------------
+ /**
+ *
+ * This implementation of the Levenshtein distance algorithm
+ * is from http://www.merriampark.com/ldjava.htm
+ * StringUtils.getLevenshteinDistance(null, *) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance(*, null) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance("","") = 0
+ * StringUtils.getLevenshteinDistance("","a") = 1
+ * StringUtils.getLevenshteinDistance("aaapppp", "") = 7
+ * StringUtils.getLevenshteinDistance("frog", "fog") = 1
+ * StringUtils.getLevenshteinDistance("fly", "ant") = 3
+ * StringUtils.getLevenshteinDistance("elephant", "hippo") = 7
+ * StringUtils.getLevenshteinDistance("hippo", "elephant") = 7
+ * StringUtils.getLevenshteinDistance("hippo", "zzzzzzzz") = 8
+ * StringUtils.getLevenshteinDistance("hello", "hallo") = 1
+ *
+ *
+ * @param s the first String, must not be null
+ * @param t the second String, must not be null
+ * @return result distance
+ * @throws IllegalArgumentException if either String input {@code null}
+ * @since 3.0 Changed signature from getLevenshteinDistance(String, String) to
+ * getLevenshteinDistance(CharSequence, CharSequence)
+ */
+ public static int getLevenshteinDistance(CharSequence s, CharSequence t) {
+ if (s == null || t == null) {
+ throw new IllegalArgumentException("Strings must not be null");
+ }
+
+ /*
+ The difference between this impl. and the previous is that, rather
+ than creating and retaining a matrix of size s.length() + 1 by t.length() + 1,
+ we maintain two single-dimensional arrays of length s.length() + 1. The first, d,
+ is the 'current working' distance array that maintains the newest distance cost
+ counts as we iterate through the characters of String s. Each time we increment
+ the index of String t we are comparing, d is copied to p, the second int[]. Doing so
+ allows us to retain the previous cost counts as required by the algorithm (taking
+ the minimum of the cost count to the left, up one, and diagonally up and to the left
+ of the current cost count being calculated). (Note that the arrays aren't really
+ copied anymore, just switched...this is clearly much better than cloning an array
+ or doing a System.arraycopy() each time through the outer loop.)
+
+ Effectively, the difference between the two implementations is this one does not
+ cause an out of memory condition when calculating the LD over two very large strings.
+ */
+
+ int n = s.length(); // length of s
+ int m = t.length(); // length of t
+
+ if (n == 0) {
+ return m;
+ } else if (m == 0) {
+ return n;
+ }
+
+ if (n > m) {
+ // swap the input strings to consume less memory
+ final CharSequence tmp = s;
+ s = t;
+ t = tmp;
+ n = m;
+ m = t.length();
+ }
+
+ int p[] = new int[n + 1]; //'previous' cost array, horizontally
+ int d[] = new int[n + 1]; // cost array, horizontally
+ int _d[]; //placeholder to assist in swapping p and d
+
+ // indexes into strings s and t
+ int i; // iterates through s
+ int j; // iterates through t
+
+ char t_j; // jth character of t
+
+ int cost; // cost
+
+ for (i = 0; i <= n; i++) {
+ p[i] = i;
+ }
+
+ for (j = 1; j <= m; j++) {
+ t_j = t.charAt(j - 1);
+ d[0] = j;
+
+ for (i = 1; i <= n; i++) {
+ cost = s.charAt(i - 1) == t_j ? 0 : 1;
+ // minimum of cell to the left+1, to the top+1, diagonally left and up +cost
+ d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost);
+ }
+
+ // copy current distance counts to 'previous row' distance counts
+ _d = p;
+ p = d;
+ d = _d;
+ }
+
+ // our last action in the above loop was to switch d and p, so p now
+ // actually has the most recent cost counts
+ return p[n];
+ }
+
+ /**
+ *
+ * StringUtils.getLevenshteinDistance(null, *, *) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance(*, null, *) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance(*, *, -1) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance("","", 0) = 0
+ * StringUtils.getLevenshteinDistance("aaapppp", "", 8) = 7
+ * StringUtils.getLevenshteinDistance("aaapppp", "", 7) = 7
+ * StringUtils.getLevenshteinDistance("aaapppp", "", 6)) = -1
+ * StringUtils.getLevenshteinDistance("elephant", "hippo", 7) = 7
+ * StringUtils.getLevenshteinDistance("elephant", "hippo", 6) = -1
+ * StringUtils.getLevenshteinDistance("hippo", "elephant", 7) = 7
+ * StringUtils.getLevenshteinDistance("hippo", "elephant", 6) = -1
+ *
+ *
+ * @param s the first String, must not be null
+ * @param t the second String, must not be null
+ * @param threshold the target threshold, must not be negative
+ * @return result distance, or {@code -1} if the distance would be greater than the threshold
+ * @throws IllegalArgumentException if either String input {@code null} or negative threshold
+ */
+ public static int getLevenshteinDistance(CharSequence s, CharSequence t, final int threshold) {
+ if (s == null || t == null) {
+ throw new IllegalArgumentException("Strings must not be null");
+ }
+ if (threshold < 0) {
+ throw new IllegalArgumentException("Threshold must not be negative");
+ }
+
+ /*
+ This implementation only computes the distance if it's less than or equal to the
+ threshold value, returning -1 if it's greater. The advantage is performance: unbounded
+ distance is O(nm), but a bound of k allows us to reduce it to O(km) time by only
+ computing a diagonal stripe of width 2k + 1 of the cost table.
+ It is also possible to use this to compute the unbounded Levenshtein distance by starting
+ the threshold at 1 and doubling each time until the distance is found; this is O(dm), where
+ d is the distance.
+
+ One subtlety comes from needing to ignore entries on the border of our stripe
+ eg.
+ p[] = |#|#|#|*
+ d[] = *|#|#|#|
+ We must ignore the entry to the left of the leftmost member
+ We must ignore the entry above the rightmost member
+
+ Another subtlety comes from our stripe running off the matrix if the strings aren't
+ of the same size. Since string s is always swapped to be the shorter of the two,
+ the stripe will always run off to the upper right instead of the lower left of the matrix.
+
+ As a concrete example, suppose s is of length 5, t is of length 7, and our threshold is 1.
+ In this case we're going to walk a stripe of length 3. The matrix would look like so:
+
+ 1 2 3 4 5
+ 1 |#|#| | | |
+ 2 |#|#|#| | |
+ 3 | |#|#|#| |
+ 4 | | |#|#|#|
+ 5 | | | |#|#|
+ 6 | | | | |#|
+ 7 | | | | | |
+
+ Note how the stripe leads off the table as there is no possible way to turn a string of length 5
+ into one of length 7 in edit distance of 1.
+
+ Additionally, this implementation decreases memory usage by using two
+ single-dimensional arrays and swapping them back and forth instead of allocating
+ an entire n by m matrix. This requires a few minor changes, such as immediately returning
+ when it's detected that the stripe has run off the matrix and initially filling the arrays with
+ large values so that entries we don't compute are ignored.
+
+ See Algorithms on Strings, Trees and Sequences by Dan Gusfield for some discussion.
+ */
+
+ int n = s.length(); // length of s
+ int m = t.length(); // length of t
+
+ // if one string is empty, the edit distance is necessarily the length of the other
+ if (n == 0) {
+ return m <= threshold ? m : -1;
+ } else if (m == 0) {
+ return n <= threshold ? n : -1;
+ }
+
+ if (n > m) {
+ // swap the two strings to consume less memory
+ final CharSequence tmp = s;
+ s = t;
+ t = tmp;
+ n = m;
+ m = t.length();
+ }
+
+ int p[] = new int[n + 1]; // 'previous' cost array, horizontally
+ int d[] = new int[n + 1]; // cost array, horizontally
+ int _d[]; // placeholder to assist in swapping p and d
+
+ // fill in starting table values
+ final int boundary = Math.min(n, threshold) + 1;
+ for (int i = 0; i < boundary; i++) {
+ p[i] = i;
+ }
+ // these fills ensure that the value above the rightmost entry of our
+ // stripe will be ignored in following loop iterations
+ Arrays.fill(p, boundary, p.length, Integer.MAX_VALUE);
+ Arrays.fill(d, Integer.MAX_VALUE);
+
+ // iterates through t
+ for (int j = 1; j <= m; j++) {
+ final char t_j = t.charAt(j - 1); // jth character of t
+ d[0] = j;
+
+ // compute stripe indices, constrain to array size
+ final int min = Math.max(1, j - threshold);
+ final int max = j > Integer.MAX_VALUE - threshold ? n : Math.min(n, j + threshold);
+
+ // the stripe may lead off of the table if s and t are of different sizes
+ if (min > max) {
+ return -1;
+ }
+
+ // ignore entry left of leftmost
+ if (min > 1) {
+ d[min - 1] = Integer.MAX_VALUE;
+ }
+
+ // iterates through [min, max] in s
+ for (int i = min; i <= max; i++) {
+ if (s.charAt(i - 1) == t_j) {
+ // diagonally left and up
+ d[i] = p[i - 1];
+ } else {
+ // 1 + minimum of cell to the left, to the top, diagonally left and up
+ d[i] = 1 + Math.min(Math.min(d[i - 1], p[i]), p[i - 1]);
+ }
+ }
+
+ // copy current distance counts to 'previous row' distance counts
+ _d = p;
+ p = d;
+ d = _d;
+ }
+
+ // if p[n] is greater than the threshold, there's no guarantee on it being the correct
+ // distance
+ if (p[n] <= threshold) {
+ return p[n];
+ }
+ return -1;
+ }
+
+ /**
+ *
+ * StringUtils.getJaroWinklerDistance(null, null) = IllegalArgumentException
+ * StringUtils.getJaroWinklerDistance("","") = 0.0
+ * StringUtils.getJaroWinklerDistance("","a") = 0.0
+ * StringUtils.getJaroWinklerDistance("aaapppp", "") = 0.0
+ * StringUtils.getJaroWinklerDistance("frog", "fog") = 0.93
+ * StringUtils.getJaroWinklerDistance("fly", "ant") = 0.0
+ * StringUtils.getJaroWinklerDistance("elephant", "hippo") = 0.44
+ * StringUtils.getJaroWinklerDistance("hippo", "elephant") = 0.44
+ * StringUtils.getJaroWinklerDistance("hippo", "zzzzzzzz") = 0.0
+ * StringUtils.getJaroWinklerDistance("hello", "hallo") = 0.88
+ * StringUtils.getJaroWinklerDistance("ABC Corporation", "ABC Corp") = 0.91
+ * StringUtils.getJaroWinklerDistance("D N H Enterprises Inc", "D & H Enterprises, Inc.") = 0.93
+ * StringUtils.getJaroWinklerDistance("My Gym Children's Fitness Center", "My Gym. Childrens Fitness") = 0.94
+ * StringUtils.getJaroWinklerDistance("PENNSYLVANIA", "PENNCISYLVNIA") = 0.9
+ *
+ *
+ * @param first the first String, must not be null
+ * @param second the second String, must not be null
+ * @return result distance
+ * @throws IllegalArgumentException if either String input {@code null}
+ * @since 3.3
+ */
+ public static double getJaroWinklerDistance(final CharSequence first, final CharSequence second) {
+ final double DEFAULT_SCALING_FACTOR = 0.1;
+
+ if (first == null || second == null) {
+ throw new IllegalArgumentException("Strings must not be null");
+ }
+
+ final double jaro = score(first,second);
+ final int cl = commonPrefixLength(first, second);
+ final double matchScore = Math.round((jaro + (DEFAULT_SCALING_FACTOR * cl * (1.0 - jaro))) *100.0)/100.0;
+
+ return matchScore;
+ }
+
+ /**
+ * This method returns the Jaro-Winkler score for string matching.
+ * @param first the first string to be matched
+ * @param second the second string to be machted
+ * @return matching score without scaling factor impact
+ */
+ private static double score(final CharSequence first, final CharSequence second) {
+ String shorter;
+ String longer;
+
+ // Determine which String is longer.
+ if (first.length() > second.length()) {
+ longer = first.toString().toLowerCase();
+ shorter = second.toString().toLowerCase();
+ } else {
+ longer = second.toString().toLowerCase();
+ shorter = first.toString().toLowerCase();
+ }
+
+ // Calculate the half length() distance of the shorter String.
+ final int halflength = shorter.length() / 2 + 1;
+
+ // Find the set of matching characters between the shorter and longer strings. Note that
+ // the set of matching characters may be different depending on the order of the strings.
+ final String m1 = getSetOfMatchingCharacterWithin(shorter, longer, halflength);
+ final String m2 = getSetOfMatchingCharacterWithin(longer, shorter, halflength);
+
+ // If one or both of the sets of common characters is empty, then
+ // there is no similarity between the two strings.
+ if (m1.length() == 0 || m2.length() == 0) {
+ return 0.0;
+ }
+
+ // If the set of common characters is not the same size, then
+ // there is no similarity between the two strings, either.
+ if (m1.length() != m2.length()) {
+ return 0.0;
+ }
+
+ // Calculate the number of transposition between the two sets
+ // of common characters.
+ final int transpositions = transpositions(m1, m2);
+
+ // Calculate the distance.
+ final double dist =
+ (m1.length() / ((double)shorter.length()) +
+ m2.length() / ((double)longer.length()) +
+ (m1.length() - transpositions) / ((double)m1.length())) / 3.0;
+ return dist;
+ }
+
+ /**
+ *
+ * StringUtils.getFuzzyDistance(null, null, null) = IllegalArgumentException
+ * StringUtils.getFuzzyDistance("", "", Locale.ENGLISH) = 0
+ * StringUtils.getFuzzyDistance("Workshop", "b", Locale.ENGLISH) = 0
+ * StringUtils.getFuzzyDistance("Room", "o", Locale.ENGLISH) = 1
+ * StringUtils.getFuzzyDistance("Workshop", "w", Locale.ENGLISH) = 1
+ * StringUtils.getFuzzyDistance("Workshop", "ws", Locale.ENGLISH) = 2
+ * StringUtils.getFuzzyDistance("Workshop", "wo", Locale.ENGLISH) = 4
+ * StringUtils.getFuzzyDistance("Apache Software Foundation", "asf", Locale.ENGLISH) = 3
+ *
+ *
+ * @param term a full term that should be matched against, must not be null
+ * @param query the query that will be matched against a term, must not be null
+ * @param locale This string matching logic is case insensitive. A locale is necessary to normalize
+ * both Strings to lower case.
+ * @return result score
+ * @throws IllegalArgumentException if either String input {@code null} or Locale input {@code null}
+ * @since 3.4
+ */
+ public static int getFuzzyDistance(final CharSequence term, final CharSequence query, final Locale locale) {
+ if (term == null || query == null) {
+ throw new IllegalArgumentException("Strings must not be null");
+ } else if (locale == null) {
+ throw new IllegalArgumentException("Locale must not be null");
+ }
+
+ // fuzzy logic is case insensitive. We normalize the Strings to lower
+ // case right from the start. Turning characters to lower case
+ // via Character.toLowerCase(char) is unfortunately insufficient
+ // as it does not accept a locale.
+ final String termLowerCase = term.toString().toLowerCase(locale);
+ final String queryLowerCase = query.toString().toLowerCase(locale);
+
+ // the resulting score
+ int score = 0;
+
+ // the position in the term which will be scanned next for potential
+ // query character matches
+ int termIndex = 0;
+
+ // index of the previously matched character in the term
+ int previousMatchingCharacterIndex = Integer.MIN_VALUE;
+
+ for (int queryIndex = 0; queryIndex < queryLowerCase.length(); queryIndex++) {
+ final char queryChar = queryLowerCase.charAt(queryIndex);
+
+ boolean termCharacterMatchFound = false;
+ for (; termIndex < termLowerCase.length() && !termCharacterMatchFound; termIndex++) {
+ final char termChar = termLowerCase.charAt(termIndex);
+
+ if (queryChar == termChar) {
+ // simple character matches result in one point
+ score++;
+
+ // subsequent character matches further improve
+ // the score.
+ if (previousMatchingCharacterIndex + 1 == termIndex) {
+ score += 2;
+ }
+
+ previousMatchingCharacterIndex = termIndex;
+
+ // we can leave the nested loop. Every character in the
+ // query can match at most one character in the term.
+ termCharacterMatchFound = true;
+ }
+ }
+ }
+
+ return score;
+ }
+
+ /**
+ * Gets a set of matching characters between two strings.
+ *
+ * {@link #trim(String)} to remove leading and trailing whitespace
+ * and then replacing sequences of whitespace characters by a single space.
+ *
+ *
+ *
+ * {@link #trim(String)} removes control characters (char <= 32) from both
+ * ends of this String.
+ * byte[] to a String using the specified character encoding.
+ *
+ * @param bytes
+ * the byte array to read from
+ * @param charsetName
+ * the encoding to use, if null then use the platform default
+ * @return a new String
+ * @throws UnsupportedEncodingException
+ * If the named charset is not supported
+ * @throws NullPointerException
+ * if the input is null
+ * @deprecated use {@link StringUtils#toEncodedString(byte[], Charset)} instead of String constants in your code
+ * @since 3.1
+ */
+ @Deprecated
+ public static String toString(final byte[] bytes, final String charsetName) throws UnsupportedEncodingException {
+ return charsetName != null ? new String(bytes, charsetName) : new String(bytes, Charset.defaultCharset());
+ }
+
+ /**
+ * Converts a byte[] to a String using the specified character encoding.
+ *
+ * @param bytes
+ * the byte array to read from
+ * @param charset
+ * the encoding to use, if null then use the platform default
+ * @return a new String
+ * @throws NullPointerException
+ * if {@code bytes} is null
+ * @since 3.2
+ * @since 3.3 No longer throws {@link UnsupportedEncodingException}.
+ */
+ public static String toEncodedString(final byte[] bytes, final Charset charset) {
+ return new String(bytes, charset != null ? charset : Charset.defaultCharset());
+ }
+
+ /**
+ *
+ * StringUtils.wrap(null, *) = null
+ * StringUtils.wrap("", *) = ""
+ * StringUtils.wrap("ab", '\0') = "ab"
+ * StringUtils.wrap("ab", 'x') = "xabx"
+ * StringUtils.wrap("ab", '\'') = "'ab'"
+ * StringUtils.wrap("\"ab\"", '\"') = "\"\"ab\"\""
+ *
+ *
+ * @param str
+ * the string to be wrapped, may be {@code null}
+ * @param wrapWith
+ * the char that will wrap {@code str}
+ * @return the wrapped string, or {@code null} if {@code str==null}
+ * @since 3.4
+ */
+ public static String wrap(final String str, final char wrapWith) {
+
+ if (isEmpty(str) || wrapWith == '\0') {
+ return str;
+ }
+
+ return wrapWith + str + wrapWith;
+ }
+
+ /**
+ *
+ * StringUtils.wrap(null, *) = null
+ * StringUtils.wrap("", *) = ""
+ * StringUtils.wrap("ab", null) = "ab"
+ * StringUtils.wrap("ab", "x") = "xabx"
+ * StringUtils.wrap("ab", "\"") = "\"ab\""
+ * StringUtils.wrap("\"ab\"", "\"") = "\"\"ab\"\""
+ * StringUtils.wrap("ab", "'") = "'ab'"
+ * StringUtils.wrap("'abcd'", "'") = "''abcd''"
+ * StringUtils.wrap("\"abcd\"", "'") = "'\"abcd\"'"
+ * StringUtils.wrap("'abcd'", "\"") = "\"'abcd'\""
+ *
+ *
+ * @param str
+ * the String to be wrapper, may be null
+ * @param wrapWith
+ * the String that will wrap str
+ * @return wrapped String, {@code null} if null String input
+ * @since 3.4
+ */
+ public static String wrap(final String str, final String wrapWith) {
+
+ if (isEmpty(str) || isEmpty(wrapWith)) {
+ return str;
+ }
+
+ return wrapWith.concat(str).concat(wrapWith);
+ }
+}
\ No newline at end of file
diff --git a/commons-java/src/test/java/net/hugonardo/java/commons/text/CypherTest.java b/commons-java/src/test/java/net/hugonardo/java/commons/text/CypherTest.java
new file mode 100644
index 00000000..c04dc5fa
--- /dev/null
+++ b/commons-java/src/test/java/net/hugonardo/java/commons/text/CypherTest.java
@@ -0,0 +1,29 @@
+package net.hugonardo.java.commons.text;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class CypherTest {
+
+ private Cypher mCypher;
+
+ @Before
+ public void setUp() throws Exception {
+ mCypher = new Cypher();
+ }
+
+ @Test
+ public void checkMd5() throws Exception {
+ String md5 = mCypher.toMd5("022065");
+ assertThat(md5, is("c7ec05991cc00f7c023b196b7be34d0b"));
+ }
+
+ @Test
+ public void checkSha1() throws Exception {
+ String sha1 = mCypher.toSha1("022065");
+ assertThat(sha1, is("2a55ee5b6b57f319bba05ee2d480bace55ecc73e"));
+ }
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..1244233d
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,65 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+app_version_code = 1
+
+# Android project config
+compile_sdk_version = 26
+target_sdk_version = 26
+min_sdk_version = 16
+build_tools_version = 27.0.1
+
+java_source_compatibility = 1.8
+java_target_compatibility = 1.8
+
+# compile dependencies
+android_support_version = 26.1.0
+constraint_layout_version = 1.0.2
+easypermissions_version = 0.4.2
+easy_recyclerview_version = 4.4.2
+recycler_view_flexible_divider_version = 1.4.0
+timber_version = 4.5.1
+leakcanary_version = 1.5.3
+crashlytics_version = 2.6.8
+lombok_version = 1.16.18
+jsr250_version = 1.0
+dagger_version = 2.11
+parceler_version = 1.1.9
+retrofit_version = 2.3.0
+gson_version = 2.8.2
+
+# Auto-Value extensions
+auto_value_version = 1.5.1
+auto_value_gson_version = 0.5.0
+auto_value_parcel_version = 0.2.5
+
+# test compile dependencies
+junit_version = 4.12
+hamcrest_version = 2.0.0.0
+mockito_version = 2.8.9
+robolectric_version = 3.4.2
+espresso_version = 2.2.2
+mockwebserver_version = 3.8.0
+
+# Estas configurações eu deixo em ~/.gradle/gradle.properties, pois contém informações sensíveis
+# como a senha da keystore. Mas por questão de facilidade e por ser apenas um exemplo, estou
+# deixando-o aqui no projeto, bem como a keystore que criei.
+releaseStoreFile=../keystores/release.jks
+releaseStorePassword=password
+releaseKeyAlias=gjp
+releaseKeyPassword=password
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..13372aef
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..23def79c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jan 19 11:34:48 BRST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 00000000..9d82f789
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..8a0b282a
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/icons/ic_launcher.png b/icons/ic_launcher.png
new file mode 100644
index 00000000..49c3e0ff
Binary files /dev/null and b/icons/ic_launcher.png differ
diff --git a/icons/ic_launcher_debug.png b/icons/ic_launcher_debug.png
new file mode 100644
index 00000000..a075e158
Binary files /dev/null and b/icons/ic_launcher_debug.png differ
diff --git a/icons/launcher.svg b/icons/launcher.svg
new file mode 100644
index 00000000..f8e0d49d
--- /dev/null
+++ b/icons/launcher.svg
@@ -0,0 +1,122 @@
+
+
+
+
diff --git a/icons/launcher_round.svg b/icons/launcher_round.svg
new file mode 100644
index 00000000..f0b09d21
--- /dev/null
+++ b/icons/launcher_round.svg
@@ -0,0 +1,133 @@
+
+
diff --git a/keystores/debug.keystore b/keystores/debug.keystore
new file mode 100644
index 00000000..4f8e3787
Binary files /dev/null and b/keystores/debug.keystore differ
diff --git a/keystores/release.jks b/keystores/release.jks
new file mode 100644
index 00000000..3fccfdc5
Binary files /dev/null and b/keystores/release.jks differ
diff --git a/lombok.config b/lombok.config
new file mode 100644
index 00000000..2390f4c9
--- /dev/null
+++ b/lombok.config
@@ -0,0 +1,5 @@
+lombok.anyConstructor.suppressConstructorProperties = true
+config.stopBubbling = true
+lombok.accessors.chain = true
+lombok.accessors.fluent = true
+lombok.accessors.prefix += m
diff --git a/repository/build.gradle b/repository/build.gradle
new file mode 100644
index 00000000..543f9f2f
--- /dev/null
+++ b/repository/build.gradle
@@ -0,0 +1,63 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion Integer.parseInt(compile_sdk_version)
+ buildToolsVersion "${build_tools_version}"
+
+ defaultConfig {
+ minSdkVersion Integer.parseInt(min_sdk_version)
+ targetSdkVersion Integer.parseInt(target_sdk_version)
+ }
+
+ compileOptions {
+ sourceCompatibility "${java_source_compatibility}"
+ targetCompatibility "${java_target_compatibility}"
+ }
+
+ testOptions {
+ unitTests {
+ includeAndroidResources = true
+ }
+ }
+}
+
+// Isto foi incluído aqui para funcionar injeção de dependência com Dagger 2 no teste unitário.
+// Mais informações: https://stackoverflow.com/questions/42793505/dagger-2-generated-test-component-not-recognized
+android.libraryVariants.all {
+ def aptOutputDir = new File(buildDir, "generated/source/apt/${it.unitTestVariant.dirName}")
+ it.unitTestVariant.addJavaSourceFoldersToModel(aptOutputDir)
+}
+
+dependencies {
+ implementation "com.android.support:support-annotations:${android_support_version}"
+ implementation "com.android.support:support-core-utils:${android_support_version}"
+ implementation "com.jakewharton.timber:timber:${timber_version}"
+
+ api "com.squareup.retrofit2:retrofit:${retrofit_version}"
+ api "com.squareup.retrofit2:converter-gson:${retrofit_version}"
+ api "com.google.code.gson:gson:${gson_version}"
+
+ implementation project(':commons-java')
+ implementation project(':commons-android')
+
+ compileOnly "javax.annotation:jsr250-api:${jsr250_version}"
+ implementation "com.google.dagger:dagger:${dagger_version}"
+ annotationProcessor "com.google.dagger:dagger-compiler:${dagger_version}"
+ testAnnotationProcessor "com.google.dagger:dagger-compiler:${dagger_version}"
+
+ compileOnly "org.projectlombok:lombok:${lombok_version}"
+ annotationProcessor "org.projectlombok:lombok:${lombok_version}"
+
+ compileOnly "com.google.auto.value:auto-value:${auto_value_version}"
+ annotationProcessor "com.google.auto.value:auto-value:${auto_value_version}"
+ compileOnly "com.ryanharter.auto.value:auto-value-gson:${auto_value_gson_version}"
+ annotationProcessor "com.ryanharter.auto.value:auto-value-gson:${auto_value_gson_version}"
+ compileOnly "com.ryanharter.auto.value:auto-value-parcel:${auto_value_parcel_version}"
+ annotationProcessor "com.ryanharter.auto.value:auto-value-parcel:${auto_value_parcel_version}"
+
+ testImplementation("junit:junit:${junit_version}", { exclude group: 'org.hamcrest' })
+ testImplementation "org.hamcrest:hamcrest-junit:${hamcrest_version}"
+ testImplementation "org.mockito:mockito-core:${mockito_version}"
+ testImplementation("org.robolectric:robolectric:${robolectric_version}", { exclude group: 'org.hamcrest' })
+ testImplementation "com.squareup.okhttp3:mockwebserver:${mockwebserver_version}"
+}
diff --git a/repository/src/main/AndroidManifest.xml b/repository/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..f3431c46
--- /dev/null
+++ b/repository/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+